From b59768866f09c64d15261a265481c6bb3704d258 Mon Sep 17 00:00:00 2001 From: Ancor Gonzalez Sosa Date: Wed, 20 Dec 2023 13:52:58 +0000 Subject: [PATCH 1/7] [rust] Small fixes --- rust/agama-locale-data/src/lib.rs | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/rust/agama-locale-data/src/lib.rs b/rust/agama-locale-data/src/lib.rs index 8b9dfcee4f..fccf946191 100644 --- a/rust/agama-locale-data/src/lib.rs +++ b/rust/agama-locale-data/src/lib.rs @@ -115,21 +115,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") } From 562a3a97ef70b1cde8791f1b2b811d5214ad7043 Mon Sep 17 00:00:00 2001 From: Ancor Gonzalez Sosa Date: Wed, 20 Dec 2023 13:56:17 +0000 Subject: [PATCH 2/7] [rust] Fix initialization of timezones database --- rust/agama-dbus-server/src/locale.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/rust/agama-dbus-server/src/locale.rs b/rust/agama-dbus-server/src/locale.rs index aabdfdc260..6aefaad85d 100644 --- a/rust/agama-dbus-server/src/locale.rs +++ b/rust/agama-dbus-server/src/locale.rs @@ -187,7 +187,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(); From f5c70be14e59b1840d8b38d79f51243982cd37ca Mon Sep 17 00:00:00 2001 From: Ancor Gonzalez Sosa Date: Wed, 20 Dec 2023 14:12:14 +0000 Subject: [PATCH 3/7] [rust] Add most representative country to the timezones --- rust/agama-dbus-server/src/locale.rs | 13 +++- rust/agama-dbus-server/src/locale/timezone.rs | 65 ++++++++++++++++++- rust/agama-locale-data/src/lib.rs | 22 +++++++ 3 files changed, 95 insertions(+), 5 deletions(-) diff --git a/rust/agama-dbus-server/src/locale.rs b/rust/agama-dbus-server/src/locale.rs index 6aefaad85d..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) } 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 fccf946191..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 From ad42b0f37a4ec621162598b42804150ae626a607 Mon Sep 17 00:00:00 2001 From: Ancor Gonzalez Sosa Date: Wed, 20 Dec 2023 14:13:23 +0000 Subject: [PATCH 4/7] [web] Display most representative country in timezone selector --- web/src/assets/styles/blocks.scss | 4 ++-- web/src/client/l10n.js | 7 ++++--- web/src/components/l10n/TimezoneSelector.jsx | 1 + 3 files changed, 7 insertions(+), 5 deletions(-) diff --git a/web/src/assets/styles/blocks.scss b/web/src/assets/styles/blocks.scss index 22113f29e1..acf738cbaa 100644 --- a/web/src/assets/styles/blocks.scss +++ b/web/src/assets/styles/blocks.scss @@ -385,14 +385,14 @@ ul[data-of="agama/keymaps"] { ul[data-of="agama/timezones"] { li { display: grid; - grid-template-columns: 1fr 2fr 1fr; + grid-template-columns: 1fr 2fr 1fr 1fr; > :last-child { grid-column: 1 / -1; font-size: 80%; } - > :nth-child(3) { + > :nth-child(4) { color: var(--color-gray-dimmed); text-align: end; } 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..5244d2956c 100644 --- a/web/src/components/l10n/TimezoneSelector.jsx +++ b/web/src/components/l10n/TimezoneSelector.jsx @@ -77,6 +77,7 @@ const TimezoneItem = ({ timezone, date }) => { <>
{part1}
{restParts.join('-')}
+
{timezone.country}
{time || ""}
{timezone.details}
From 25eb638f4b66a2086f86f89a666c014755aa2658 Mon Sep 17 00:00:00 2001 From: Ancor Gonzalez Sosa Date: Thu, 21 Dec 2023 12:19:54 +0100 Subject: [PATCH 5/7] Changelog entries --- rust/package/agama-cli.changes | 6 ++++++ web/package/cockpit-agama.changes | 5 +++++ 2 files changed, 11 insertions(+) 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 From ddcb94e25b4416c4e942e663435f20db88cc4ee7 Mon Sep 17 00:00:00 2001 From: Ancor Gonzalez Sosa Date: Thu, 21 Dec 2023 11:32:36 +0000 Subject: [PATCH 6/7] Update D-Bus documentation --- doc/dbus/bus/org.opensuse.Agama1.Locale.bus.xml | 5 ++++- doc/dbus/org.opensuse.Agama1.Locale.doc.xml | 5 ++++- 2 files changed, 8 insertions(+), 2 deletions(-) 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. --> - + From a1d0c0d9834cf276c511a8d8c9b2e393f8b38b22 Mon Sep 17 00:00:00 2001 From: Ancor Gonzalez Sosa Date: Thu, 21 Dec 2023 12:56:35 +0000 Subject: [PATCH 7/7] [web] Reorganize information at TimezoneSelector --- web/src/assets/styles/blocks.scss | 4 ++-- web/src/components/l10n/TimezoneSelector.jsx | 4 +--- 2 files changed, 3 insertions(+), 5 deletions(-) diff --git a/web/src/assets/styles/blocks.scss b/web/src/assets/styles/blocks.scss index acf738cbaa..8708ef2526 100644 --- a/web/src/assets/styles/blocks.scss +++ b/web/src/assets/styles/blocks.scss @@ -385,14 +385,14 @@ ul[data-of="agama/keymaps"] { ul[data-of="agama/timezones"] { li { display: grid; - grid-template-columns: 1fr 2fr 1fr 1fr; + grid-template-columns: 2fr 1fr 1fr; > :last-child { grid-column: 1 / -1; font-size: 80%; } - > :nth-child(4) { + > :nth-child(3) { color: var(--color-gray-dimmed); text-align: end; } diff --git a/web/src/components/l10n/TimezoneSelector.jsx b/web/src/components/l10n/TimezoneSelector.jsx index 5244d2956c..9cf1785810 100644 --- a/web/src/components/l10n/TimezoneSelector.jsx +++ b/web/src/components/l10n/TimezoneSelector.jsx @@ -70,13 +70,11 @@ 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}