Repository: fujiapple852/trippy (language: rust)

Issue:
Lookup GeoIp with current locale
GeoIp lookups (for `maxminddb`) supports providing a locale, this should be set to the configured locale rather than hardcoded to `en`.  If the lookup for the configured locale does not find a result then lookup again for the default `en` locale.

```
/// The default locale.
const LOCALE: &str = "en";

impl From<maxminddb::geoip2::City<'_>> for GeoIpCity {
    fn from(value: maxminddb::geoip2::City<'_>) -> Self {
        let city = value
            .city
            .as_ref()
            .and_then(|city| city.names.as_ref())
            .and_then(|names| names.get(LOCALE))
            .map(ToString::to_string);
    ... snip ...
    }
}
```


Discussion / hints:

Fix patch:
diff --git a/crates/trippy-tui/src/app.rs b/crates/trippy-tui/src/app.rs
index a0846ce8f..cae4c70ca 100644
--- a/crates/trippy-tui/src/app.rs
+++ b/crates/trippy-tui/src/app.rs
@@ -1,7 +1,7 @@
 use crate::config::{LogFormat, LogSpanEvents, Mode, TrippyConfig};
 use crate::frontend::TuiConfig;
 use crate::geoip::GeoIpLookup;
-use crate::locale::set_locale;
+use crate::locale::{locale, set_locale};
 use crate::{frontend, report};
 use anyhow::{anyhow, Error};
 use std::net::IpAddr;
@@ -18,7 +18,7 @@ pub fn run_trippy(cfg: &TrippyConfig, pid: u16) -> anyhow::Result<()> {
     set_locale(cfg.tui_locale.as_deref());
     let _guard = configure_logging(cfg);
     let resolver = start_dns_resolver(cfg)?;
-    let geoip_lookup = create_geoip_lookup(cfg)?;
+    let geoip_lookup = create_geoip_lookup(cfg, locale())?;
     let addrs = resolve_targets(cfg, &resolver)?;
     if addrs.is_empty() {
         return Err(anyhow!(
@@ -141,9 +141,9 @@ fn start_dns_resolver(cfg: &TrippyConfig) -> anyhow::Result<DnsResolver> {
     ))?)
 }
 
-fn create_geoip_lookup(cfg: &TrippyConfig) -> anyhow::Result<GeoIpLookup> {
+fn create_geoip_lookup(cfg: &TrippyConfig, locale: String) -> anyhow::Result<GeoIpLookup> {
     if let Some(path) = cfg.geoip_mmdb_file.as_ref() {
-        GeoIpLookup::from_file(path)
+        GeoIpLookup::from_file(path, locale)
     } else {
         Ok(GeoIpLookup::empty())
     }
diff --git a/crates/trippy-tui/src/geoip.rs b/crates/trippy-tui/src/geoip.rs
index 805e0faf4..841c18efe 100644
--- a/crates/trippy-tui/src/geoip.rs
+++ b/crates/trippy-tui/src/geoip.rs
@@ -210,20 +210,20 @@ impl From<ipinfo::IpInfoGeoIp> for GeoIpCity {
     }
 }
 
-impl From<maxminddb::geoip2::City<'_>> for GeoIpCity {
-    fn from(value: maxminddb::geoip2::City<'_>) -> Self {
+impl From<(maxminddb::geoip2::City<'_>, &str)> for GeoIpCity {
+    fn from((value, locale): (maxminddb::geoip2::City<'_>, &str)) -> Self {
         let city = value
             .city
             .as_ref()
             .and_then(|city| city.names.as_ref())
-            .and_then(|names| names.get(LOCALE))
+            .and_then(|names| names.get(locale).or_else(|| names.get(FALLBACK_LOCALE)))
             .map(ToString::to_string);
         let subdivision = value
             .subdivisions
             .as_ref()
             .and_then(|c| c.first())
             .and_then(|c| c.names.as_ref())
-            .and_then(|names| names.get(LOCALE))
+            .and_then(|names| names.get(locale).or_else(|| names.get(FALLBACK_LOCALE)))
             .map(ToString::to_string);
         let subdivision_code = value
             .subdivisions
@@ -235,7 +235,7 @@ impl From<maxminddb::geoip2::City<'_>> for GeoIpCity {
             .country
             .as_ref()
             .and_then(|country| country.names.as_ref())
-            .and_then(|names| names.get(LOCALE))
+            .and_then(|names| names.get(locale).or_else(|| names.get(FALLBACK_LOCALE)))
             .map(ToString::to_string);
         let country_code = value
             .country
@@ -246,7 +246,7 @@ impl From<maxminddb::geoip2::City<'_>> for GeoIpCity {
             .continent
             .as_ref()
             .and_then(|continent| continent.names.as_ref())
-            .and_then(|names| names.get(LOCALE))
+            .and_then(|names| names.get(locale).or_else(|| names.get(FALLBACK_LOCALE)))
             .map(ToString::to_string);
         let latitude = value
             .location
@@ -274,8 +274,19 @@ impl From<maxminddb::geoip2::City<'_>> for GeoIpCity {
     }
 }
 
-/// The default locale.
-const LOCALE: &str = "en";
+/// The fallback locale.
+///
+/// The `MaxMind` support documentation says:
+///
+/// > Our geolocation name data includes the names of the continent, country, city, and
+/// > subdivisions of the location of the IP address. We include the country names in
+/// > English, Simplified Chinese, Spanish, Brazilian Portuguese, Russian, Japanese, French,
+/// > and German.
+/// >
+/// > Please note: Not every place name is always available in each language. We recommend checking
+/// > English names as a default for cases where a localized name isn’t available in your preferred
+/// > language.
+const FALLBACK_LOCALE: &str = "en";
 
 /// Alias for a cache of `GeoIp` data.
 type Cache = RefCell<HashMap<IpAddr, Rc<GeoIpCity>>>;
@@ -285,16 +296,18 @@ type Cache = RefCell<HashMap<IpAddr, Rc<GeoIpCity>>>;
 pub struct GeoIpLookup {
     reader: Option<Reader<Vec<u8>>>,
     cache: Cache,
+    locale: String,
 }
 
 impl GeoIpLookup {
     /// Create a new `GeoIpLookup` from a `MaxMind` DB file.
-    pub fn from_file<P: AsRef<Path>>(path: P) -> anyhow::Result<Self> {
+    pub fn from_file<P: AsRef<Path>>(path: P, locale: String) -> anyhow::Result<Self> {
         let reader = maxminddb::Reader::open_readfile(path.as_ref())
             .context(format!("{}", path.as_ref().display()))?;
         Ok(Self {
             reader: Some(reader),
             cache: RefCell::new(HashMap::new()),
+            locale,
         })
     }
 
@@ -303,6 +316,7 @@ impl GeoIpLookup {
         Self {
             reader: None,
             cache: RefCell::new(HashMap::new()),
+            locale: FALLBACK_LOCALE.to_string(),
         }
     }
 
@@ -317,7 +331,10 @@ impl GeoIpLookup {
             let city_data = if reader.metadata.database_type.starts_with("ipinfo") {
                 GeoIpCity::from(reader.lookup::<ipinfo::IpInfoGeoIp>(addr)?)
             } else {
-                GeoIpCity::from(reader.lookup::<maxminddb::geoip2::City<'_>>(addr)?)
+                GeoIpCity::from((
+                    reader.lookup::<maxminddb::geoip2::City<'_>>(addr)?,
+                    self.locale.as_ref(),
+                ))
             };
             let geo = self.cache.borrow_mut().insert(addr, Rc::new(city_data));
             Ok(geo)
diff --git a/crates/trippy-tui/src/locale.rs b/crates/trippy-tui/src/locale.rs
index c3fee3f9b..08f5cd110 100644
--- a/crates/trippy-tui/src/locale.rs
+++ b/crates/trippy-tui/src/locale.rs
@@ -16,6 +16,11 @@ pub fn set_locale(locale: Option<&str>) {
     }
 }
 
+/// Get the current locale.
+pub fn locale() -> String {
+    rust_i18n::locale().to_string()
+}
+
 fn set_locale_inner(locale: &str) {
     let all_locales = rust_i18n::available_locales!();
     if all_locales.contains(&locale) {


Test patch: