Description: Use packaged maxminddb 0.30
 based on a patch from plugwash
Last-Updated: 2026-05-15
--- a/Cargo.toml
+++ b/Cargo.toml
@@ -56 +56 @@
-maxminddb = "0.25.0"
+maxminddb = "0.30"
--- a/crates/trippy-tui/src/geoip.rs
+++ b/crates/trippy-tui/src/geoip.rs
@@ -215,6 +215 @@
-        let city = value
-            .city
-            .as_ref()
-            .and_then(|city| city.names.as_ref())
-            .and_then(|names| names.get(locale).or_else(|| names.get(FALLBACK_LOCALE)))
-            .map(ToString::to_string);
+        let city = localized_name(&value.city.names, locale);
@@ -223,5 +218,2 @@
-            .as_ref()
-            .and_then(|c| c.first())
-            .and_then(|c| c.names.as_ref())
-            .and_then(|names| names.get(locale).or_else(|| names.get(FALLBACK_LOCALE)))
-            .map(ToString::to_string);
+            .first()
+            .and_then(|c| localized_name(&c.names, locale));
@@ -230,33 +222,8 @@
-            .as_ref()
-            .and_then(|c| c.first())
-            .and_then(|c| c.iso_code.as_ref())
-            .map(ToString::to_string);
-        let country = value
-            .country
-            .as_ref()
-            .and_then(|country| country.names.as_ref())
-            .and_then(|names| names.get(locale).or_else(|| names.get(FALLBACK_LOCALE)))
-            .map(ToString::to_string);
-        let country_code = value
-            .country
-            .as_ref()
-            .and_then(|country| country.iso_code.as_ref())
-            .map(ToString::to_string);
-        let continent = value
-            .continent
-            .as_ref()
-            .and_then(|continent| continent.names.as_ref())
-            .and_then(|names| names.get(locale).or_else(|| names.get(FALLBACK_LOCALE)))
-            .map(ToString::to_string);
-        let latitude = value
-            .location
-            .as_ref()
-            .and_then(|location| location.latitude);
-        let longitude = value
-            .location
-            .as_ref()
-            .and_then(|location| location.longitude);
-        let accuracy_radius = value
-            .location
-            .as_ref()
-            .and_then(|location| location.accuracy_radius);
+            .first()
+            .and_then(|c| c.iso_code.as_ref().map(ToString::to_string));
+        let country = localized_name(&value.country.names, locale);
+        let country_code = value.country.iso_code.map(ToString::to_string);
+        let continent = localized_name(&value.continent.names, locale);
+        let latitude = value.location.latitude;
+        let longitude = value.location.longitude;
+        let accuracy_radius = value.location.accuracy_radius;
@@ -292 +259 @@
-type Cache = RefCell<HashMap<IpAddr, Rc<GeoIpCity>>>;
+type Cache = RefCell<HashMap<IpAddr, Option<Rc<GeoIpCity>>>>;
@@ -329 +296 @@
-                return Ok(Some(geo.clone()));
+                return Ok(geo.clone());
@@ -331,2 +298,5 @@
-            let city_data = if reader.metadata.database_type.starts_with("ipinfo") {
-                GeoIpCity::from(reader.lookup::<ipinfo::IpInfoGeoIp>(addr)?)
+            let lookup_result = reader.lookup(addr)?;
+            let city_data = if reader.metadata().database_type.starts_with("ipinfo") {
+                lookup_result
+                    .decode::<ipinfo::IpInfoGeoIp>()?
+                    .map(GeoIpCity::from)
@@ -334,4 +304,3 @@
-                GeoIpCity::from((
-                    reader.lookup::<maxminddb::geoip2::City<'_>>(addr)?,
-                    self.locale.as_ref(),
-                ))
+                lookup_result
+                    .decode::<maxminddb::geoip2::City<'_>>()?
+                    .map(|city| GeoIpCity::from((city, self.locale.as_ref())))
@@ -339,2 +308,3 @@
-            let geo = self.cache.borrow_mut().insert(addr, Rc::new(city_data));
-            Ok(geo)
+            let cached = city_data.map(Rc::new);
+            self.cache.borrow_mut().insert(addr, cached.clone());
+            Ok(cached)
@@ -342,0 +313,29 @@
+        }
+    }
+}
+
+fn localized_name(names: &maxminddb::geoip2::Names<'_>, locale: &str) -> Option<String> {
+    lookup_locale(names, locale)
+        .or_else(|| lookup_locale(names, FALLBACK_LOCALE))
+        .map(ToString::to_string)
+}
+
+/// Map a Trippy locale code to the closest `maxminddb` locale field.
+///
+/// - `pt*` (e.g. `pt`, `pt-BR`, `pt-PT`) use `brazilian_portuguese`
+/// - `zh*` (e.g. `zh`, `zh-TW`) use `simplified_chinese`
+/// - Other languages that are supported map directly (`en`, `de`, `es`, `fr`, `ja`, `ru`).
+fn lookup_locale<'a>(names: &maxminddb::geoip2::Names<'a>, code: &str) -> Option<&'a str> {
+    if code.starts_with("pt") {
+        names.brazilian_portuguese
+    } else if code.starts_with("zh") {
+        names.simplified_chinese
+    } else {
+        match code {
+            "de" => names.german,
+            "en" => names.english,
+            "es" => names.spanish,
+            "fr" => names.french,
+            "ja" => names.japanese,
+            "ru" => names.russian,
+            _ => None,
