diff --git a/doc/dbus/bus/org.opensuse.Agama1.Locale.bus.xml b/doc/dbus/bus/org.opensuse.Agama1.Locale.bus.xml index 8f4dec9054..5170b7aaa0 100644 --- a/doc/dbus/bus/org.opensuse.Agama1.Locale.bus.xml +++ b/doc/dbus/bus/org.opensuse.Agama1.Locale.bus.xml @@ -70,9 +70,12 @@ * The timezone identifier (e.g., "Europe/Berlin"). * A list containing each part of the name in the language set by the UILocale property. + * The name, in the language set by UILocale, of the main country + associated to the timezone (typically, the name of the city that is + part of the identifier) or empty string if there is no country. --> - + diff --git a/doc/dbus/org.opensuse.Agama1.Locale.doc.xml b/doc/dbus/org.opensuse.Agama1.Locale.doc.xml index 3f0256a75e..8d1e32268b 100644 --- a/doc/dbus/org.opensuse.Agama1.Locale.doc.xml +++ b/doc/dbus/org.opensuse.Agama1.Locale.doc.xml @@ -34,9 +34,12 @@ * The timezone identifier (e.g., "Europe/Berlin"). * A list containing each part of the name in the language set by the UILocale property. + * The name, in the language set by UILocale, of the main country + associated to the timezone (typically, the name of the city that is + part of the identifier) or empty string if there is no country. --> - + diff --git a/rust/agama-dbus-server/src/locale.rs b/rust/agama-dbus-server/src/locale.rs index aabdfdc260..23cd416d7c 100644 --- a/rust/agama-dbus-server/src/locale.rs +++ b/rust/agama-dbus-server/src/locale.rs @@ -122,12 +122,21 @@ impl Locale { /// * The timezone identifier (e.g., "Europe/Berlin"). /// * A list containing each part of the name in the language set by the /// UILocale property. - fn list_timezones(&self) -> Result)>, Error> { + /// * The name, in the language set by UILocale, of the main country + /// associated to the timezone (typically, the name of the city that is + /// part of the identifier) or empty string if there is no country. + fn list_timezones(&self) -> Result, String)>, Error> { let timezones: Vec<_> = self .timezones_db .entries() .iter() - .map(|tz| (tz.code.to_string(), tz.parts.clone())) + .map(|tz| { + ( + tz.code.to_string(), + tz.parts.clone(), + tz.country.clone().unwrap_or_default(), + ) + }) .collect(); Ok(timezones) } @@ -187,7 +196,7 @@ impl Locale { }; let mut timezones_db = TimezonesDatabase::new(); - timezones_db.read(&locale)?; + timezones_db.read(&ui_locale.language)?; let mut default_timezone = DEFAULT_TIMEZONE.to_string(); if !timezones_db.exists(&default_timezone) { default_timezone = timezones_db.entries().get(0).unwrap().code.to_string(); diff --git a/rust/agama-dbus-server/src/locale/timezone.rs b/rust/agama-dbus-server/src/locale/timezone.rs index f26917b6af..d032122d66 100644 --- a/rust/agama-dbus-server/src/locale/timezone.rs +++ b/rust/agama-dbus-server/src/locale/timezone.rs @@ -1,7 +1,9 @@ //! This module provides support for reading the timezones database. use crate::error::Error; +use agama_locale_data::territory::Territories; use agama_locale_data::timezone_part::TimezoneIdParts; +use std::collections::HashMap; /// Represents a timezone, including each part as localized. #[derive(Debug)] @@ -10,6 +12,8 @@ pub struct TimezoneEntry { pub code: String, /// Localized parts (e.g., "Atlántico", "Canarias"). pub parts: Vec, + /// Localized name of the territory this timezone is associated to + pub country: Option, } #[derive(Default)] @@ -49,13 +53,26 @@ impl TimezonesDatabase { fn get_timezones(&self, ui_language: &str) -> Result, Error> { let timezones = agama_locale_data::get_timezones(); let tz_parts = agama_locale_data::get_timezone_parts()?; + let territories = agama_locale_data::get_territories()?; + let tz_countries = agama_locale_data::get_timezone_countries()?; + const COUNTRYLESS: [&str; 2] = ["UTC", "Antarctica/South_Pole"]; + let ret = timezones .into_iter() - .map(|tz| { + .filter_map(|tz| { let parts = translate_parts(&tz, ui_language, &tz_parts); - TimezoneEntry { code: tz, parts } + let country = translate_country(&tz, ui_language, &tz_countries, &territories); + match country { + None if !COUNTRYLESS.contains(&tz.as_str()) => None, + _ => Some(TimezoneEntry { + code: tz, + parts, + country, + }), + } }) .collect(); + Ok(ret) } } @@ -71,6 +88,23 @@ fn translate_parts(timezone: &str, ui_language: &str, tz_parts: &TimezoneIdParts .collect() } +fn translate_country( + timezone: &str, + lang: &str, + countries: &HashMap, + territories: &Territories, +) -> Option { + let tz = match timezone { + "Asia/Rangoon" => "Asia/Yangon", + "Europe/Kiev" => "Europe/Kyiv", + _ => timezone, + }; + let country_id = countries.get(tz)?; + let territory = territories.find_by_id(country_id)?; + let name = territory.names.name_for(lang)?; + Some(name) +} + #[cfg(test)] mod tests { use super::TimezonesDatabase; @@ -89,7 +123,32 @@ mod tests { assert_eq!( found.parts, vec!["Europa".to_string(), "Berlín".to_string()] - ) + ); + assert_eq!(found.country, Some("Alemania".to_string())); + } + + #[test] + fn test_read_timezone_without_country() { + let mut db = TimezonesDatabase::new(); + db.read("es").unwrap(); + let timezone = db + .entries() + .into_iter() + .find(|tz| tz.code == "UTC") + .unwrap(); + assert_eq!(timezone.country, None); + } + + #[test] + fn test_read_kiev_country() { + let mut db = TimezonesDatabase::new(); + db.read("en").unwrap(); + let timezone = db + .entries() + .into_iter() + .find(|tz| tz.code == "Europe/Kiev") + .unwrap(); + assert_eq!(timezone.country, Some("Ukraine".to_string())); } #[test] diff --git a/rust/agama-locale-data/src/lib.rs b/rust/agama-locale-data/src/lib.rs index 8b9dfcee4f..7c4c313660 100644 --- a/rust/agama-locale-data/src/lib.rs +++ b/rust/agama-locale-data/src/lib.rs @@ -2,6 +2,7 @@ use anyhow::Context; use flate2::bufread::GzDecoder; use quick_xml::de::Deserializer; use serde::Deserialize; +use std::collections::HashMap; use std::fs::File; use std::io::BufRead; use std::io::BufReader; @@ -92,6 +93,27 @@ pub fn get_timezone_parts() -> anyhow::Result { Ok(ret) } +/// Returns a hash mapping timezones to its main country (typically, the country of +/// the city that is used to name the timezone). The information is read from the +/// file /usr/share/zoneinfo/zone.tab. +pub fn get_timezone_countries() -> anyhow::Result> { + const FILE_PATH: &str = "/usr/share/zoneinfo/zone.tab"; + let content = std::fs::read_to_string(FILE_PATH) + .with_context(|| format!("Failed to read {}", FILE_PATH))?; + + let countries = content + .lines() + .filter_map(|line| { + if line.starts_with('#') { + return None; + } + let fields: Vec<&str> = line.split('\t').collect(); + Some((fields.get(2)?.to_string(), fields.first()?.to_string())) + }) + .collect(); + Ok(countries) +} + /// Gets list of non-deprecated timezones pub fn get_timezones() -> Vec { chrono_tz::TZ_VARIANTS @@ -115,21 +137,21 @@ mod tests { #[test] fn test_get_languages() { let result = get_languages().unwrap(); - let first = result.language.first().expect("no keyboards"); + let first = result.language.first().expect("no languages"); assert_eq!(first.id, "aa") } #[test] fn test_get_territories() { let result = get_territories().unwrap(); - let first = result.territory.first().expect("no keyboards"); + let first = result.territory.first().expect("no territories"); assert_eq!(first.id, "001") // looks strange, but it is meta id for whole world } #[test] fn test_get_timezone_parts() { let result = get_timezone_parts().unwrap(); - let first = result.timezone_part.first().expect("no keyboards"); + let first = result.timezone_part.first().expect("no timezone parts"); assert_eq!(first.id, "Abidjan") } diff --git a/rust/package/agama-cli.changes b/rust/package/agama-cli.changes index 108235b255..785d97ed1e 100644 --- a/rust/package/agama-cli.changes +++ b/rust/package/agama-cli.changes @@ -1,3 +1,9 @@ +------------------------------------------------------------------- +Thu Dec 21 11:12:45 UTC 2023 - Ancor Gonzalez Sosa + +- The result of ListTimezones includes the localized country name + for each timezone (gh#openSUSE/agama#946) + ------------------------------------------------------------------- Fri Dec 15 16:29:20 UTC 2023 - Imobach Gonzalez Sosa diff --git a/web/package/cockpit-agama.changes b/web/package/cockpit-agama.changes index 3918b965d7..2e7d3c1ac8 100644 --- a/web/package/cockpit-agama.changes +++ b/web/package/cockpit-agama.changes @@ -1,3 +1,8 @@ +------------------------------------------------------------------- +Thu Dec 21 11:18:37 UTC 2023 - Ancor Gonzalez Sosa + +- Display countries at timezone selector (gh#openSUSE/agama#946). + ------------------------------------------------------------------- Mon Dec 18 10:42:53 UTC 2023 - José Iván López González diff --git a/web/src/assets/styles/blocks.scss b/web/src/assets/styles/blocks.scss index 22113f29e1..8708ef2526 100644 --- a/web/src/assets/styles/blocks.scss +++ b/web/src/assets/styles/blocks.scss @@ -385,7 +385,7 @@ ul[data-of="agama/keymaps"] { ul[data-of="agama/timezones"] { li { display: grid; - grid-template-columns: 1fr 2fr 1fr; + grid-template-columns: 2fr 1fr 1fr; > :last-child { grid-column: 1 / -1; diff --git a/web/src/client/l10n.js b/web/src/client/l10n.js index cf9154a6dd..ec5c79b207 100644 --- a/web/src/client/l10n.js +++ b/web/src/client/l10n.js @@ -31,6 +31,7 @@ const LOCALE_PATH = "/org/opensuse/Agama1/Locale"; * @typedef {object} Timezone * @property {string} id - Timezone id (e.g., "Atlantic/Canary"). * @property {Array} parts - Name of the timezone parts (e.g., ["Atlantic", "Canary"]). + * @property {string} country - Name of the country associated to the zone or empty string (e.g., "Spain"). * @property {number} utcOffset - UTC offset. */ @@ -230,13 +231,13 @@ class L10nClient { /** * @private * - * @param {[string, Array]} dbusTimezone + * @param {[string, Array, string]} dbusTimezone * @returns {Timezone} */ - buildTimezone([id, parts]) { + buildTimezone([id, parts, country]) { const utcOffset = timezoneUTCOffset(id); - return ({ id, parts, utcOffset }); + return ({ id, parts, country, utcOffset }); } /** diff --git a/web/src/components/l10n/TimezoneSelector.jsx b/web/src/components/l10n/TimezoneSelector.jsx index 55ddc9b629..9cf1785810 100644 --- a/web/src/components/l10n/TimezoneSelector.jsx +++ b/web/src/components/l10n/TimezoneSelector.jsx @@ -70,13 +70,12 @@ const timezoneDetails = (timezone) => { * @param {Date} props.date - Date to show a time. */ const TimezoneItem = ({ timezone, date }) => { - const [part1, ...restParts] = timezone.parts; const time = timezoneTime(timezone.id, { date }) || ""; return ( <> -
{part1}
-
{restParts.join('-')}
+
{timezone.parts.join('-')}
+
{timezone.country}
{time || ""}
{timezone.details}