fix(time): icu locale requires hyphenated lang codes
The recent icu 2.0.0 update broke `icu::locale::Locale` parsing. Lang codes
containing underscores are no longer valid, so we need to hyphenate the lang
code before parsing it. I've added `replacen('_', "-", 1)` everywhere that an
`icu::locale::Locale` is parsed from a string.
To prevent the need to re-parse the locale on every view update, I've also
added `icu::locale::Locale`s for `LC_TIME` and `LC_NUMERIC` directly to
`region::Page` the moment the page is refreshed.
Closes #1384
This commit is contained in:
parent
24796fc46a
commit
6274a66ae9
2 changed files with 47 additions and 81 deletions
|
|
@ -1,8 +1,6 @@
|
||||||
// Copyright 2023 System76 <info@system76.com>
|
// Copyright 2023 System76 <info@system76.com>
|
||||||
// SPDX-License-Identifier: GPL-3.0-only
|
// SPDX-License-Identifier: GPL-3.0-only
|
||||||
|
|
||||||
use std::str::FromStr;
|
|
||||||
|
|
||||||
use chrono::{Datelike, Timelike};
|
use chrono::{Datelike, Timelike};
|
||||||
use cosmic::{
|
use cosmic::{
|
||||||
Apply, Element, Task,
|
Apply, Element, Task,
|
||||||
|
|
@ -507,14 +505,15 @@ fn timezone() -> Section<crate::pages::Message> {
|
||||||
}
|
}
|
||||||
|
|
||||||
fn locale() -> Result<Locale, Box<dyn std::error::Error>> {
|
fn locale() -> Result<Locale, Box<dyn std::error::Error>> {
|
||||||
let locale = std::env::var("LC_TIME").or_else(|_| std::env::var("LANG"))?;
|
let locale_env = std::env::var("LC_TIME").or_else(|_| std::env::var("LANG"))?;
|
||||||
let locale = locale
|
|
||||||
|
locale_env
|
||||||
.split('.')
|
.split('.')
|
||||||
.next()
|
.next()
|
||||||
.ok_or(format!("Can't split the locale {locale}"))?;
|
.ok_or(format!("Can't split the locale {locale_env}"))?
|
||||||
|
.replacen("_", "-", 1)
|
||||||
let locale = Locale::from_str(locale).map_err(|e| format!("{e:?}"))?;
|
.parse::<Locale>()
|
||||||
Ok(locale)
|
.map_err(|e| format!("{e:?}").into())
|
||||||
}
|
}
|
||||||
|
|
||||||
fn format_date(date: &DateTime<Gregorian>, military: bool, show_seconds: bool) -> String {
|
fn format_date(date: &DateTime<Gregorian>, military: bool, show_seconds: bool) -> String {
|
||||||
|
|
|
||||||
|
|
@ -3,7 +3,6 @@
|
||||||
|
|
||||||
use std::collections::{BTreeMap, BTreeSet};
|
use std::collections::{BTreeMap, BTreeSet};
|
||||||
use std::rc::Rc;
|
use std::rc::Rc;
|
||||||
use std::str::FromStr;
|
|
||||||
use std::sync::Arc;
|
use std::sync::Arc;
|
||||||
|
|
||||||
use cosmic::app::{ContextDrawer, context_drawer};
|
use cosmic::app::{ContextDrawer, context_drawer};
|
||||||
|
|
@ -32,7 +31,6 @@ pub enum Message {
|
||||||
AddLanguage(DefaultKey),
|
AddLanguage(DefaultKey),
|
||||||
AddLanguageContext,
|
AddLanguageContext,
|
||||||
AddLanguageSearch(String),
|
AddLanguageSearch(String),
|
||||||
SystemLocales(SlotMap<DefaultKey, SystemLocale>),
|
|
||||||
ExpandLanguagePopover(Option<usize>),
|
ExpandLanguagePopover(Option<usize>),
|
||||||
InstallAdditionalLanguages,
|
InstallAdditionalLanguages,
|
||||||
SelectRegion(DefaultKey),
|
SelectRegion(DefaultKey),
|
||||||
|
|
@ -115,6 +113,10 @@ pub struct Page {
|
||||||
registry: Option<locale::Registry>,
|
registry: Option<locale::Registry>,
|
||||||
expanded_source_popover: Option<usize>,
|
expanded_source_popover: Option<usize>,
|
||||||
add_language_search: String,
|
add_language_search: String,
|
||||||
|
/// Cached LC_NUMERIC locale in icu locale format.
|
||||||
|
numeric_locale: Option<Locale>,
|
||||||
|
/// Cached LC_TIME locale in icu locale format.
|
||||||
|
time_locale: Option<Locale>,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl page::Page<crate::pages::Message> for Page {
|
impl page::Page<crate::pages::Message> for Page {
|
||||||
|
|
@ -251,10 +253,6 @@ impl Page {
|
||||||
self.add_language_search = search;
|
self.add_language_search = search;
|
||||||
}
|
}
|
||||||
|
|
||||||
Message::SystemLocales(languages) => {
|
|
||||||
self.available_languages = languages;
|
|
||||||
}
|
|
||||||
|
|
||||||
Message::ExpandLanguagePopover(id) => {
|
Message::ExpandLanguagePopover(id) => {
|
||||||
self.expanded_source_popover = id;
|
self.expanded_source_popover = id;
|
||||||
}
|
}
|
||||||
|
|
@ -277,6 +275,8 @@ impl Page {
|
||||||
self.language = page_refresh.language;
|
self.language = page_refresh.language;
|
||||||
self.region = page_refresh.region;
|
self.region = page_refresh.region;
|
||||||
self.registry = Some(page_refresh.registry.0);
|
self.registry = Some(page_refresh.registry.0);
|
||||||
|
self.numeric_locale = self.icu_locale_from_env("LC_NUMERIC");
|
||||||
|
self.time_locale = self.icu_locale_from_env("LC_TIME");
|
||||||
}
|
}
|
||||||
|
|
||||||
Err(why) => {
|
Err(why) => {
|
||||||
|
|
@ -397,17 +397,21 @@ impl Page {
|
||||||
list.apply(Element::from).map(crate::pages::Message::Region)
|
list.apply(Element::from).map(crate::pages::Message::Region)
|
||||||
}
|
}
|
||||||
|
|
||||||
fn formatted_date(&self) -> String {
|
fn icu_locale_from_env(&self, key: &'static str) -> Option<Locale> {
|
||||||
let time_locale = self
|
self.system_locales
|
||||||
.system_locales
|
.get(key)
|
||||||
.get("LC_TIME")
|
|
||||||
.or_else(|| self.system_locales.get("LANG"))
|
.or_else(|| self.system_locales.get("LANG"))
|
||||||
.map_or("en_US", |locale| &locale.lang_code)
|
.map_or("en-US", |locale| &locale.lang_code)
|
||||||
.split('.')
|
.split('.')
|
||||||
.next()
|
.next()
|
||||||
.unwrap_or("en_US");
|
.unwrap_or("en-US")
|
||||||
|
.replacen('_', "-", 1)
|
||||||
|
.parse::<Locale>()
|
||||||
|
.ok()
|
||||||
|
}
|
||||||
|
|
||||||
let Ok(locale) = Locale::from_str(time_locale) else {
|
fn formatted_date(&self) -> String {
|
||||||
|
let Some(locale) = self.time_locale.as_ref() else {
|
||||||
return String::new();
|
return String::new();
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
@ -423,16 +427,7 @@ impl Page {
|
||||||
}
|
}
|
||||||
|
|
||||||
fn formatted_dates_and_times(&self) -> String {
|
fn formatted_dates_and_times(&self) -> String {
|
||||||
let time_locale = self
|
let Some(locale) = self.time_locale.as_ref() else {
|
||||||
.system_locales
|
|
||||||
.get("LC_TIME")
|
|
||||||
.or_else(|| self.system_locales.get("LANG"))
|
|
||||||
.map_or("en_US", |locale| &locale.lang_code)
|
|
||||||
.split('.')
|
|
||||||
.next()
|
|
||||||
.unwrap_or("en_US");
|
|
||||||
|
|
||||||
let Ok(locale) = Locale::from_str(time_locale) else {
|
|
||||||
return String::new();
|
return String::new();
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
@ -448,16 +443,7 @@ impl Page {
|
||||||
}
|
}
|
||||||
|
|
||||||
fn formatted_time(&self) -> String {
|
fn formatted_time(&self) -> String {
|
||||||
let time_locale = self
|
let Some(locale) = self.time_locale.as_ref() else {
|
||||||
.system_locales
|
|
||||||
.get("LC_TIME")
|
|
||||||
.or_else(|| self.system_locales.get("LANG"))
|
|
||||||
.map_or("en_US", |locale| &locale.lang_code)
|
|
||||||
.split('.')
|
|
||||||
.next()
|
|
||||||
.unwrap_or("en_US");
|
|
||||||
|
|
||||||
let Ok(locale) = Locale::from_str(time_locale) else {
|
|
||||||
return String::new();
|
return String::new();
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
@ -473,16 +459,7 @@ impl Page {
|
||||||
}
|
}
|
||||||
|
|
||||||
fn formatted_numbers(&self) -> String {
|
fn formatted_numbers(&self) -> String {
|
||||||
let numerical_locale = self
|
let Some(locale) = self.numeric_locale.as_ref() else {
|
||||||
.system_locales
|
|
||||||
.get("LC_NUMERIC")
|
|
||||||
.or_else(|| self.system_locales.get("LANG"))
|
|
||||||
.map_or("en_US", |locale| &locale.lang_code)
|
|
||||||
.split('.')
|
|
||||||
.next()
|
|
||||||
.unwrap_or("en_US");
|
|
||||||
|
|
||||||
let Ok(locale) = Locale::from_str(numerical_locale) else {
|
|
||||||
return String::new();
|
return String::new();
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
@ -621,22 +598,13 @@ mod formatting {
|
||||||
pub fn section() -> Section<crate::pages::Message> {
|
pub fn section() -> Section<crate::pages::Message> {
|
||||||
crate::slab!(descriptions {
|
crate::slab!(descriptions {
|
||||||
formatting_txt = fl!("formatting");
|
formatting_txt = fl!("formatting");
|
||||||
dates_txt = fl!("formatting", "dates");
|
dates_txt = [&fl!("formatting", "dates"), ":"].concat();
|
||||||
time_txt = fl!("formatting", "time");
|
time_txt = [&fl!("formatting", "time"), ":"].concat();
|
||||||
date_and_time_txt = fl!("formatting", "date-and-time");
|
date_and_time_txt = [&fl!("formatting", "date-and-time"), ":"].concat();
|
||||||
numbers_txt = fl!("formatting", "numbers");
|
numbers_txt = [&fl!("formatting", "numbers"), ":"].concat();
|
||||||
// measurement_txt = fl!("formatting", "measurement");
|
|
||||||
// paper_txt = fl!("formatting", "paper");
|
|
||||||
region_txt = fl!("region");
|
region_txt = fl!("region");
|
||||||
});
|
});
|
||||||
|
|
||||||
let dates_label = [&descriptions[dates_txt], ":"].concat();
|
|
||||||
let time_label = [&descriptions[time_txt], ":"].concat();
|
|
||||||
let date_and_time_label = [&descriptions[date_and_time_txt], ":"].concat();
|
|
||||||
let numbers_label = [&descriptions[numbers_txt], ":"].concat();
|
|
||||||
// let measurement_label = [&descriptions[measurement_txt], ":"].concat();
|
|
||||||
// let paper_label = [&descriptions[paper_txt], ":"].concat();
|
|
||||||
|
|
||||||
Section::default()
|
Section::default()
|
||||||
.title(fl!("formatting"))
|
.title(fl!("formatting"))
|
||||||
.descriptions(descriptions)
|
.descriptions(descriptions)
|
||||||
|
|
@ -644,17 +612,17 @@ mod formatting {
|
||||||
let desc = §ion.descriptions;
|
let desc = §ion.descriptions;
|
||||||
|
|
||||||
let dates = widget::row::with_capacity(2)
|
let dates = widget::row::with_capacity(2)
|
||||||
.push(widget::text::body(dates_label.clone()))
|
.push(widget::text::body(&desc[dates_txt]))
|
||||||
.push(widget::text::body(page.formatted_date()).font(cosmic::font::bold()))
|
.push(widget::text::body(page.formatted_date()).font(cosmic::font::bold()))
|
||||||
.spacing(4);
|
.spacing(4);
|
||||||
|
|
||||||
let time = widget::row::with_capacity(2)
|
let time = widget::row::with_capacity(2)
|
||||||
.push(widget::text::body(time_label.clone()))
|
.push(widget::text::body(&desc[time_txt]))
|
||||||
.push(widget::text::body(page.formatted_time()).font(cosmic::font::bold()))
|
.push(widget::text::body(page.formatted_time()).font(cosmic::font::bold()))
|
||||||
.spacing(4);
|
.spacing(4);
|
||||||
|
|
||||||
let dates_and_times = widget::row::with_capacity(2)
|
let dates_and_times = widget::row::with_capacity(2)
|
||||||
.push(widget::text::body(date_and_time_label.clone()))
|
.push(widget::text::body(&desc[date_and_time_txt]))
|
||||||
.push(
|
.push(
|
||||||
widget::text::body(page.formatted_dates_and_times())
|
widget::text::body(page.formatted_dates_and_times())
|
||||||
.font(cosmic::font::bold()),
|
.font(cosmic::font::bold()),
|
||||||
|
|
@ -662,7 +630,7 @@ mod formatting {
|
||||||
.spacing(4);
|
.spacing(4);
|
||||||
|
|
||||||
let numbers = widget::row::with_capacity(2)
|
let numbers = widget::row::with_capacity(2)
|
||||||
.push(widget::text::body(numbers_label.clone()))
|
.push(widget::text::body(&desc[numbers_txt]))
|
||||||
.push(widget::text::body(page.formatted_numbers()).font(cosmic::font::bold()))
|
.push(widget::text::body(page.formatted_numbers()).font(cosmic::font::bold()))
|
||||||
.spacing(4);
|
.spacing(4);
|
||||||
|
|
||||||
|
|
@ -934,18 +902,17 @@ pub async fn set_locale(lang: String, region: String) {
|
||||||
.await;
|
.await;
|
||||||
}
|
}
|
||||||
|
|
||||||
fn parse_locale(locale: String) -> Result<Locale, Box<dyn std::error::Error>> {
|
fn parse_locale(locale: &str) -> Option<Locale> {
|
||||||
let locale = locale
|
locale
|
||||||
.split('.')
|
.split('.')
|
||||||
.next()
|
.next()?
|
||||||
.ok_or(format!("Can't split the locale {locale}"))?;
|
.replacen('_', "-", 1)
|
||||||
|
.parse::<Locale>()
|
||||||
let locale = Locale::from_str(locale).map_err(|e| format!("{e:?}"))?;
|
.ok()
|
||||||
Ok(locale)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
fn get_default_24h(locale: String) -> bool {
|
fn get_default_24h(locale: &str) -> bool {
|
||||||
let Ok(locale) = parse_locale(locale) else {
|
let Some(locale) = parse_locale(locale) else {
|
||||||
return false;
|
return false;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
@ -966,8 +933,8 @@ fn get_default_24h(locale: String) -> bool {
|
||||||
formatted.contains("13")
|
formatted.contains("13")
|
||||||
}
|
}
|
||||||
|
|
||||||
fn get_default_first_day(locale: String) -> usize {
|
fn get_default_first_day(locale: &str) -> usize {
|
||||||
let Ok(locale) = parse_locale(locale) else {
|
let Some(locale) = parse_locale(locale) else {
|
||||||
return 6;
|
return 6;
|
||||||
};
|
};
|
||||||
let Ok(week_info) = week::WeekInformation::try_new(week::WeekPreferences::from(&locale)) else {
|
let Ok(week_info) = week::WeekInformation::try_new(week::WeekPreferences::from(&locale)) else {
|
||||||
|
|
@ -1000,13 +967,13 @@ fn update_time_settings_after_region_change(region: String) {
|
||||||
};
|
};
|
||||||
|
|
||||||
// Update military_time based on new locale
|
// Update military_time based on new locale
|
||||||
let new_military_time = get_default_24h(region.clone());
|
let new_military_time = get_default_24h(®ion);
|
||||||
if let Err(why) = cosmic_applet_config.set("military_time", new_military_time) {
|
if let Err(why) = cosmic_applet_config.set("military_time", new_military_time) {
|
||||||
tracing::error!(?why, "Failed to update military_time after region change");
|
tracing::error!(?why, "Failed to update military_time after region change");
|
||||||
}
|
}
|
||||||
|
|
||||||
// Update first_day_of_week based on new locale
|
// Update first_day_of_week based on new locale
|
||||||
let new_first_day = get_default_first_day(region);
|
let new_first_day = get_default_first_day(®ion);
|
||||||
if let Err(why) = cosmic_applet_config.set("first_day_of_week", new_first_day) {
|
if let Err(why) = cosmic_applet_config.set("first_day_of_week", new_first_day) {
|
||||||
tracing::error!(
|
tracing::error!(
|
||||||
?why,
|
?why,
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue