From 9b7e065b0f9f9b802720870a283ecfa31c72847c Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Guido=20G=C3=BCnther?= Date: Wed, 8 Dec 2021 14:18:14 +0100 Subject: [PATCH 1/8] popover: Move Emoji and Terminal to ui file This allows them to be translated via po so we don't have to maintain the translation list by hand or care about empty translation files. The only downside is that the "overlay layouts" in OVERLAY_NAMES need to match the ones in the ui file but since a missing one is a clean crash when selected there's little potential for subtle breackage. We could even ensure consistency by comparing the two at run time but they change only slowly this is not much of an issue (compared to how e.g. new languages can be added now). --- data/popup.ui | 13 +++++- src/popover.rs | 121 +++++++++++++------------------------------------ 2 files changed, 43 insertions(+), 91 deletions(-) diff --git a/data/popup.ui b/data/popup.ui index 214fbea6..94b15e1e 100644 --- a/data/popup.ui +++ b/data/popup.ui @@ -1,7 +1,16 @@ - - + + + Emoji + layout + emoji + + + Terminal + layout + terminal +
Keyboard Settings diff --git a/src/popover.rs b/src/popover.rs index 9a1b6902..e2b8aebf 100644 --- a/src/popover.rs +++ b/src/popover.rs @@ -20,10 +20,9 @@ use gio::SimpleActionExt; use glib::translate::FromGlibPtrNone; use glib::variant::ToVariant; #[cfg(not(feature = "gtk_v0_5"))] -use gtk::BuilderExtManual; +use gtk::prelude::*; use gtk::PopoverExt; use gtk::WidgetExt; -use std::io::Write; use ::logging::Warn; mod c { @@ -111,46 +110,6 @@ mod variants { } } -fn make_menu_builder(inputs: Vec<(&str, OwnedTranslation)>) -> gtk::Builder { - let mut xml: Vec = Vec::new(); - writeln!( - xml, - " - - -
" - ).unwrap(); - for (input_name, translation) in inputs { - writeln!( - xml, - " - - {} - layout - {} - ", - translation.0, - input_name, - ).unwrap(); - } - writeln!( - xml, - " -
-
- - Keyboard Settings - settings - -
-
-
" - ).unwrap(); - gtk::Builder::new_from_string( - &String::from_utf8(xml).expect("Bad menu definition") - ) -} - fn get_settings(schema_name: &str) -> Option { let mut error_handler = logging::Print{}; gio::SettingsSchemaSource::get_default() @@ -350,12 +309,12 @@ pub fn show( .chain(overlay_layouts) .collect(); - let translated_names = translate_layout_names(&all_layouts); - - // sorted collection of human and machine names + let translated_names = translate_layout_names(&system_layouts); + + // sorted collection of language layouts let mut human_names: Vec<(OwnedTranslation, LayoutId)> = translated_names .into_iter() - .zip(all_layouts.clone().into_iter()) + .zip(system_layouts.clone().into_iter()) .collect(); human_names.sort_unstable_by(|(tr_a, layout_a), (tr_b, layout_b)| { @@ -367,32 +326,14 @@ pub fn show( } }); - // GVariant doesn't natively support `enum`s, - // so the `choices` vector will serve as a lookup table. - let choices_with_translations: Vec<(String, (OwnedTranslation, LayoutId))> - = human_names.into_iter() - .enumerate() - .map(|(i, human_entry)| {( - format!("{}_{}", i, human_entry.1.get_name()), - human_entry, - )}).collect(); + let builder = gtk::Builder::new_from_resource("/sm/puri/squeekboard/popup.ui"); + let model: gio::Menu = builder.get_object("app-menu").unwrap(); - - let builder = make_menu_builder( - choices_with_translations.iter() - .map(|(id, (translation, _))| (id.as_str(), (*translation).clone())) - .collect() - ); - - let choices: Vec<(String, LayoutId)> - = choices_with_translations.into_iter() - .map(|(id, (_tr, layout))| (id, layout)) - .collect(); - - // Much more debuggable to populate the model & menu - // from a string representation - // than add items imperatively - let model: gio::MenuModel = builder.get_object("app-menu").unwrap(); + for (tr, l) in human_names.iter().rev() { + let detailed_action = format!("layout::{}", l.get_name()); + let item = gio::MenuItem::new(Some(&tr.0), Some(detailed_action.as_str())); + model.prepend_item (&item); + } let menu = gtk::Popover::new_from_model(Some(&window), &model); menu.set_pointing_to(>k::Rectangle { @@ -403,32 +344,36 @@ pub fn show( }); menu.set_constrain_to(gtk::PopoverConstraint::None); + let action_group = gio::SimpleActionGroup::new(); + if let Some(current_layout) = get_current_layout(manager, &system_layouts) { - let current_name_variant = choices.iter() + let current_layout_name = all_layouts.iter() .find( - |(_id, layout)| layout == ¤t_layout + |l| l.get_name() == current_layout.get_name() ).unwrap() - .0.to_variant(); + .get_name(); + log_print!(logging::Level::Debug, "Current Layout {}", current_layout_name); let layout_action = gio::SimpleAction::new_stateful( "layout", - Some(current_name_variant.type_()), - ¤t_name_variant, + Some(current_layout_name.to_variant().type_()), + ¤t_layout_name.to_variant() ); let menu_inner = menu.clone(); layout_action.connect_change_state(move |_action, state| { match state { Some(v) => { + log_print!(logging::Level::Debug, "Selected layout {}", v); v.get::() .or_print( logging::Problem::Bug, &format!("Variant is not string: {:?}", v) ) .map(|state| { - let (_id, layout) = choices.iter() + let layout = all_layouts.iter() .find( - |choices| state == choices.0 + |choices| state == choices.get_name() ).unwrap(); set_visible_layout( manager, @@ -443,20 +388,18 @@ pub fn show( }; menu_inner.popdown(); }); - - let settings_action = gio::SimpleAction::new("settings", None); - settings_action.connect_activate(move |_, _| { - let s = CString::new("region").unwrap(); - unsafe { c::popover_open_settings_panel(s.as_ptr()) }; - }); - - let action_group = gio::SimpleActionGroup::new(); action_group.add_action(&layout_action); - action_group.add_action(&settings_action); - - menu.insert_action_group("popup", Some(&action_group)); }; + let settings_action = gio::SimpleAction::new("settings", None); + settings_action.connect_activate(move |_, _| { + let s = CString::new("region").unwrap(); + unsafe { c::popover_open_settings_panel(s.as_ptr()) }; + }); + action_group.add_action(&settings_action); + + menu.insert_action_group("popup", Some(&action_group)); + menu.bind_model(Some(&model), Some("popup")); menu.popup(); } From ac833950aba3514b4ce5369bce0ed77abc30e46f Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Guido=20G=C3=BCnther?= Date: Wed, 8 Dec 2021 14:18:16 +0100 Subject: [PATCH 2/8] popover: Add translator notes MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Single words are hard to translate in context, give some indication what they do. They're automatically extracted into the pot file. #. translators: This is a emmoji keyboard layout #: data/popover.ui:6 msgid "Emoji" msgstr "…" #. translators: This is a terminal keyboard layout #: data/popover.ui:12 msgid "Terminal" msgstr "…" --- data/popup.ui | 2 ++ 1 file changed, 2 insertions(+) diff --git a/data/popup.ui b/data/popup.ui index 94b15e1e..b02e71c0 100644 --- a/data/popup.ui +++ b/data/popup.ui @@ -2,11 +2,13 @@ + Emoji layout emoji + Terminal layout terminal From 2aa9cf26eee1eb37827239e776e7f761377dcf2c Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Guido=20G=C3=BCnther?= Date: Wed, 8 Dec 2021 15:20:08 +0100 Subject: [PATCH 3/8] popover: Make the ui file match the code file name UI files are usually named like the code file. Stick to that pattern. --- data/{popup.ui => popover.ui} | 0 data/squeekboard.gresources.xml | 2 +- src/popover.rs | 2 +- 3 files changed, 2 insertions(+), 2 deletions(-) rename data/{popup.ui => popover.ui} (100%) diff --git a/data/popup.ui b/data/popover.ui similarity index 100% rename from data/popup.ui rename to data/popover.ui diff --git a/data/squeekboard.gresources.xml b/data/squeekboard.gresources.xml index 795d22b7..6c5d0213 100644 --- a/data/squeekboard.gresources.xml +++ b/data/squeekboard.gresources.xml @@ -4,7 +4,7 @@ common.css style.css style-Adwaita:dark.css - popup.ui + popover.ui icons/key-enter.svg icons/key-shift.svg icons/keyboard-mode-symbolic.svg diff --git a/src/popover.rs b/src/popover.rs index e2b8aebf..5ac6e1ab 100644 --- a/src/popover.rs +++ b/src/popover.rs @@ -326,7 +326,7 @@ pub fn show( } }); - let builder = gtk::Builder::new_from_resource("/sm/puri/squeekboard/popup.ui"); + let builder = gtk::Builder::new_from_resource("/sm/puri/squeekboard/popover.ui"); let model: gio::Menu = builder.get_object("app-menu").unwrap(); for (tr, l) in human_names.iter().rev() { From da789bcb873b296c978407c3144116b8992aaa6f Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Guido=20G=C3=BCnther?= Date: Wed, 8 Dec 2021 14:18:15 +0100 Subject: [PATCH 4/8] Remove emoji and terminal from translations Handled via po. Remove now empty files. The match with resources.rs shows that some of them weren't even used (e.g. fa-IR) showing why a switch to po is useful (which allows to maintain us that outside of code). --- data/langs/bg-BG.txt | 2 -- data/langs/cs-CZ.txt | 2 -- data/langs/de-DE.txt | 2 -- data/langs/en-US.txt | 2 -- data/langs/fa-IR.txt | 2 -- data/langs/fur-IT.txt | 1 - data/langs/he-IL.txt | 2 -- data/langs/hy-AM.txt | 1 - data/langs/ja-JP.txt | 0 data/langs/pl-PL.txt | 2 -- data/langs/ru-RU.txt | 1 - src/resources.rs | 4 ---- 12 files changed, 21 deletions(-) delete mode 100644 data/langs/de-DE.txt delete mode 100644 data/langs/en-US.txt delete mode 100644 data/langs/fa-IR.txt delete mode 100644 data/langs/ja-JP.txt delete mode 100644 data/langs/pl-PL.txt diff --git a/data/langs/bg-BG.txt b/data/langs/bg-BG.txt index ae158a07..7bafc131 100644 --- a/data/langs/bg-BG.txt +++ b/data/langs/bg-BG.txt @@ -6,7 +6,6 @@ cz Чешки de Немски dk Датски es Испански -emoji Емоджи fi Фински fr Френски gr Гръцки @@ -18,5 +17,4 @@ ru Руски se Шведски th Тайски ua Украински -terminal Терминал us Английски (САЩ) diff --git a/data/langs/cs-CZ.txt b/data/langs/cs-CZ.txt index 6c87837e..1482fe86 100644 --- a/data/langs/cs-CZ.txt +++ b/data/langs/cs-CZ.txt @@ -3,7 +3,6 @@ cz Česká cz+qwerty Česká (QWERTY) de Německá dk Dánská -emoji Emoji es Španělská fi Finská fr Francouzská @@ -15,7 +14,6 @@ no Norská pl Polská ru Ruská se Švédská -terminal Terminál th Thajská ua Ukrajinská us Anglická (USA) diff --git a/data/langs/de-DE.txt b/data/langs/de-DE.txt deleted file mode 100644 index e89678c3..00000000 --- a/data/langs/de-DE.txt +++ /dev/null @@ -1,2 +0,0 @@ -emoji Emoji -terminal Terminal diff --git a/data/langs/en-US.txt b/data/langs/en-US.txt deleted file mode 100644 index e89678c3..00000000 --- a/data/langs/en-US.txt +++ /dev/null @@ -1,2 +0,0 @@ -emoji Emoji -terminal Terminal diff --git a/data/langs/fa-IR.txt b/data/langs/fa-IR.txt deleted file mode 100644 index 17a65aec..00000000 --- a/data/langs/fa-IR.txt +++ /dev/null @@ -1,2 +0,0 @@ -emoji ایموجی -terminal ترمینال diff --git a/data/langs/fur-IT.txt b/data/langs/fur-IT.txt index 05b78268..956e68e5 100644 --- a/data/langs/fur-IT.txt +++ b/data/langs/fur-IT.txt @@ -13,6 +13,5 @@ no Norvegjês pl Polac ru Rus se Svedês -terminal Terminâl ua Ucrain us American (USA) diff --git a/data/langs/he-IL.txt b/data/langs/he-IL.txt index 221cdf77..c6db858e 100644 --- a/data/langs/he-IL.txt +++ b/data/langs/he-IL.txt @@ -4,7 +4,6 @@ cz צ'כית de גרמנית dk דנית es ספרדית -emoji אימוג'י fi פינית fr צרפתית gr יוונית @@ -14,6 +13,5 @@ no נורווגית pl פולנית ru רוסית se שוודית -terminal טרמינל ua אוקראינית us אנגלית (ארה"ב) diff --git a/data/langs/hy-AM.txt b/data/langs/hy-AM.txt index a181b1dd..1e8b1f76 100644 --- a/data/langs/hy-AM.txt +++ b/data/langs/hy-AM.txt @@ -7,5 +7,4 @@ no Նորուեգերէն pl Լեհերէն ru Ռուսերէն se Շուեդերէն -terminal Տերմինալ us Անգլերէն (ԱՄՆ) diff --git a/data/langs/ja-JP.txt b/data/langs/ja-JP.txt deleted file mode 100644 index e69de29b..00000000 diff --git a/data/langs/pl-PL.txt b/data/langs/pl-PL.txt deleted file mode 100644 index 0884b6fb..00000000 --- a/data/langs/pl-PL.txt +++ /dev/null @@ -1,2 +0,0 @@ -emoji emoji -terminal terminal diff --git a/data/langs/ru-RU.txt b/data/langs/ru-RU.txt index 23b131e0..72377337 100644 --- a/data/langs/ru-RU.txt +++ b/data/langs/ru-RU.txt @@ -7,5 +7,4 @@ no Норвежский pl Польский ru Русский se Шведский -terminal Терминал us Английский (США) diff --git a/src/resources.rs b/src/resources.rs index f512921d..39372755 100644 --- a/src/resources.rs +++ b/src/resources.rs @@ -127,13 +127,9 @@ pub fn get_overlays() -> Vec<&'static str> { /// Translations of the layout identifier strings static LAYOUT_NAMES: &[(&'static str, &'static str)] = &[ - ("de-DE", include_str!("../data/langs/de-DE.txt")), - ("en-US", include_str!("../data/langs/en-US.txt")), ("es-ES", include_str!("../data/langs/es-ES.txt")), ("fur-IT", include_str!("../data/langs/fur-IT.txt")), ("he-IL", include_str!("../data/langs/he-IL.txt")), - ("ja-JP", include_str!("../data/langs/ja-JP.txt")), - ("pl-PL", include_str!("../data/langs/pl-PL.txt")), ("ru-RU", include_str!("../data/langs/ru-RU.txt")), ]; From 72a7825c85aa107cda47b0d59d96802ec7a300d0 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Guido=20G=C3=BCnther?= Date: Wed, 8 Dec 2021 14:18:15 +0100 Subject: [PATCH 5/8] popover: Don't complain about missing translations It's fine to do all via po. --- src/popover.rs | 7 +------ 1 file changed, 1 insertion(+), 6 deletions(-) diff --git a/src/popover.rs b/src/popover.rs index 5ac6e1ab..8c43f30a 100644 --- a/src/popover.rs +++ b/src/popover.rs @@ -239,8 +239,7 @@ fn translate_layout_names(layouts: &Vec) -> Vec { LayoutId::Local(name) => Status::Remaining(name.clone()), }); - // Non-xkb layouts and weird xkb layouts - // still need to be looked up in the internal database. + // Weird xkb layouts still need to be looked up in the internal database. let builtin_translations = system_locale() .map(|locale| locale.tags_for("messages") @@ -251,10 +250,6 @@ fn translate_layout_names(layouts: &Vec) -> Vec { .or_print(logging::Problem::Surprise, "No locale detected") .and_then(|lang| { resources::get_layout_names(lang.as_str()) - .or_print( - logging::Problem::Surprise, - &format!("No translations for locale {}", lang), - ) }); match builtin_translations { From 10ea276052d93fdd70c0881116de4aef505a4df9 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Guido=20G=C3=BCnther?= Date: Wed, 8 Dec 2021 14:18:17 +0100 Subject: [PATCH 6/8] Drop custom translation handling xkb layouts are translated fine by libgnome-desktop. If not we should go fixing there since that makes keyboard layout strings consistent with e.g. gnome-control-center. There weren't any 'exotic' strings in the current translations so what we get from libgnome-desktop should be just fine. --- src/locale.rs | 9 ------- src/popover.rs | 58 ++++++++-------------------------------- src/resources.rs | 69 ------------------------------------------------ 3 files changed, 11 insertions(+), 125 deletions(-) diff --git a/src/locale.rs b/src/locale.rs index 63ff6fca..8bdae9ba 100644 --- a/src/locale.rs +++ b/src/locale.rs @@ -88,15 +88,6 @@ impl Drop for XkbInfo { } } -#[derive(Clone, Debug, PartialEq)] -pub struct Translation<'a>(pub &'a str); - -impl<'a> Translation<'a> { - pub fn to_owned(&'a self) -> OwnedTranslation { - OwnedTranslation(self.0.to_owned()) - } -} - #[derive(Clone, Debug, PartialEq)] pub struct OwnedTranslation(pub String); diff --git a/src/popover.rs b/src/popover.rs index 8c43f30a..feec80f1 100644 --- a/src/popover.rs +++ b/src/popover.rs @@ -5,9 +5,7 @@ use gtk; use std::ffi::CString; use std::cmp::Ordering; use ::layout::c::{ Bounds, EekGtkKeyboard }; -use ::locale; -use ::locale::{ OwnedTranslation, Translation, compare_current_locale }; -use ::locale_config::system_locale; +use ::locale::{ OwnedTranslation, compare_current_locale }; use ::logging; use ::manager; use ::resources; @@ -209,13 +207,8 @@ fn get_current_layout( /// Translates all provided layout names according to current locale, /// for the purpose of display (i.e. errors will be caught and reported) fn translate_layout_names(layouts: &Vec) -> Vec { - // This procedure is rather ugly... - // Xkb lookup *must not* be applied to non-system layouts, - // so both translators can't be merged into one lookup table, - // therefore must be done in two steps. - // `XkbInfo` being temporary also means - // that its return values must be copied, - // forcing the use of `OwnedTranslation`. + // `XkbInfo` being temporary means that its return values must be + // copied, forcing the use of `OwnedTranslation`. enum Status { /// xkb names should get all translated here Translated(OwnedTranslation), @@ -224,7 +217,7 @@ fn translate_layout_names(layouts: &Vec) -> Vec { } // Attempt to take all xkb names from gnome-desktop's xkb info. - let xkb_translator = locale::XkbInfo::new(); + let xkb_translator = ::locale::XkbInfo::new(); let translated_names = layouts.iter() .map(|id| match id { @@ -236,44 +229,15 @@ fn translate_layout_names(layouts: &Vec) -> Vec { &format!("No display name for xkb layout {}", name), ).unwrap_or_else(|| Status::Remaining(name.clone())) }, - LayoutId::Local(name) => Status::Remaining(name.clone()), + LayoutId::Local (_) => unreachable!(), }); - // Weird xkb layouts still need to be looked up in the internal database. - let builtin_translations = system_locale() - .map(|locale| - locale.tags_for("messages") - .next().unwrap() // guaranteed to exist - .as_ref() - .to_owned() - ) - .or_print(logging::Problem::Surprise, "No locale detected") - .and_then(|lang| { - resources::get_layout_names(lang.as_str()) - }); - - match builtin_translations { - Some(translations) => { - translated_names - .map(|status| match status { - Status::Remaining(name) => { - translations.get(name.as_str()) - .unwrap_or(&Translation(name.as_str())) - .to_owned() - }, - Status::Translated(t) => t, - }) - .collect() - }, - None => { - translated_names - .map(|status| match status { - Status::Remaining(name) => OwnedTranslation(name), - Status::Translated(t) => t, - }) - .collect() - }, - } + translated_names + .map(|status| match status { + Status::Remaining(name) => OwnedTranslation(name), + Status::Translated(t) => t, + }) + .collect() } pub fn show( diff --git a/src/resources.rs b/src/resources.rs index 39372755..1795dbe9 100644 --- a/src/resources.rs +++ b/src/resources.rs @@ -2,11 +2,6 @@ * This could be done using GResource, but that would need additional work. */ -use std::collections::HashMap; -use ::locale::Translation; - -use std::iter::FromIterator; - // TODO: keep a list of what is a language layout, // and what a convenience layout. "_wide" is not a layout, // neither is "number" @@ -125,42 +120,6 @@ pub fn get_overlays() -> Vec<&'static str> { OVERLAY_NAMES.to_vec() } -/// Translations of the layout identifier strings -static LAYOUT_NAMES: &[(&'static str, &'static str)] = &[ - ("es-ES", include_str!("../data/langs/es-ES.txt")), - ("fur-IT", include_str!("../data/langs/fur-IT.txt")), - ("he-IL", include_str!("../data/langs/he-IL.txt")), - ("ru-RU", include_str!("../data/langs/ru-RU.txt")), -]; - -pub fn get_layout_names(lang: &str) - -> Option>> -{ - let translations = LAYOUT_NAMES.iter() - .find(|(name, _data)| *name == lang) - .map(|(_name, data)| *data); - translations.map(make_mapping) -} - -fn parse_line(line: &str) -> Option<(&str, Translation)> { - let comment = line.trim().starts_with("#"); - if comment { - None - } else { - let mut iter = line.splitn(2, " "); - let name = iter.next().unwrap(); - // will skip empty and unfinished lines - iter.next().map(|tr| (name, Translation(tr.trim()))) - } -} - -fn make_mapping(data: &str) -> HashMap<&str, Translation> { - HashMap::from_iter( - data.split("\n") - .filter_map(parse_line) - ) -} - #[cfg(test)] mod test { use super::*; @@ -171,32 +130,4 @@ mod test { assert!(get_keyboard(&format!("{}/us", name)).is_some()); } } - - #[test] - fn mapping_line() { - assert_eq!( - Some(("name", Translation("translation"))), - parse_line("name translation") - ); - } - - #[test] - fn mapping_bad() { - assert_eq!(None, parse_line("bad")); - } - - #[test] - fn mapping_empty() { - assert_eq!(None, parse_line("")); - } - - #[test] - fn mapping_comment() { - assert_eq!(None, parse_line("# comment")); - } - - #[test] - fn mapping_comment_offset() { - assert_eq!(None, parse_line(" # comment")); - } } From 750814e050bf2d99a7a2c19ff81886ca927f8ecb Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Guido=20G=C3=BCnther?= Date: Wed, 8 Dec 2021 14:18:17 +0100 Subject: [PATCH 7/8] Drop locale_config Not used anymore. --- src/lib.rs | 1 - src/locale_config.rs | 535 ------------------------------------------- 2 files changed, 536 deletions(-) delete mode 100644 src/locale_config.rs diff --git a/src/lib.rs b/src/lib.rs index 287b5286..a133e9ee 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -26,7 +26,6 @@ pub mod imservice; mod keyboard; mod layout; mod locale; -mod locale_config; mod manager; mod outputs; mod popover; diff --git a/src/locale_config.rs b/src/locale_config.rs deleted file mode 100644 index 959dfde1..00000000 --- a/src/locale_config.rs +++ /dev/null @@ -1,535 +0,0 @@ -/*! Locale detection and management. - * Based on https://github.com/rust-locale/locale_config - * - * Ready for deletion/replacement once Debian starts packaging this, - * although this version doesn't need lazy_static. - * - * Copyright (c) 2016–2019 Jan Hudec - Copyright (c) 2016 A.J. Gardner - Copyright (c) 2019, Bastien Orivel - Copyright (c) 2019, Igor Gnatenko - Copyright (c) 2019, Sophie Tauchert <999eagle@999eagle.moe> - */ - -use regex::Regex; -use std::borrow::Cow; -use std::env; - -/// Errors that may be returned by `locale_config`. -#[derive(Copy,Clone,Debug,PartialEq,Eq)] -pub enum Error { - /// Provided definition was not well formed. - /// - /// This is returned when provided configuration string does not match even the rather loose - /// definition for language range from [RFC4647] or the composition format used by `Locale`. - /// - /// [RFC4647]: https://www.rfc-editor.org/rfc/rfc4647.txt - NotWellFormed, - /// Placeholder for adding more errors in future. **Do not match!**. - __NonExhaustive, -} - -impl ::std::fmt::Display for Error { - fn fmt(&self, out: &mut ::std::fmt::Formatter) -> ::std::fmt::Result { - out.write_str(match self { - &Error::NotWellFormed => "Language tag is not well-formed.", - // this is exception: here we do want exhaustive match so we don't publish version with - // missing descriptions by mistake. - &Error::__NonExhaustive => panic!("Placeholder error must not be instantiated!"), - }) - } -} - - -/// Convenience Result alias. -type Result = ::std::result::Result; - -/// Iterator over `LanguageRange`s for specific category in a `Locale` -/// -/// Returns `LanguageRange`s in the `Locale` that are applicable to provided category. The tags -/// are returned in order of preference, which means the category-specific ones first and then -/// the generic ones. -/// -/// The iterator is guaranteed to return at least one value. -pub struct TagsFor<'a, 'c> { - src: &'a str, - tags: std::str::Split<'a, &'static str>, - category: Option<&'c str>, -} - -impl<'a, 'c> Iterator for TagsFor<'a, 'c> { - type Item = LanguageRange<'a>; - fn next(&mut self) -> Option { - if let Some(cat) = self.category { - while let Some(s) = self.tags.next() { - if s.starts_with(cat) && s[cat.len()..].starts_with("=") { - return Some( - LanguageRange { language: Cow::Borrowed(&s[cat.len()+1..]) }); - } - } - self.category = None; - self.tags = self.src.split(","); - } - while let Some(s) = self.tags.next() { - if s.find('=').is_none() { - return Some( - LanguageRange{ language: Cow::Borrowed(s) }); - } - } - return None; - } -} - -/// Language and culture identifier. -/// -/// This object holds a [RFC4647] extended language range. -/// -/// The internal data may be owned or shared from object with lifetime `'a`. The lifetime can be -/// extended using the `into_static()` method, which internally clones the data as needed. -/// -/// # Syntax -/// -/// The range is composed of `-`-separated alphanumeric subtags, possibly replaced by `*`s. It -/// might be empty. -/// -/// In agreement with [RFC4647], this object only requires that the tag matches: -/// -/// ```ebnf -/// language_tag = (alpha{1,8} | "*") -/// ("-" (alphanum{1,8} | "*"))* -/// ``` -/// -/// The exact interpretation is up to the downstream localization provider, but it expected that -/// it will be matched against a normalized [RFC5646] language tag, which has the structure: -/// -/// ```ebnf -/// language_tag = language -/// ("-" script)? -/// ("-" region)? -/// ("-" variant)* -/// ("-" extension)* -/// ("-" private)? -/// -/// language = alpha{2,3} ("-" alpha{3}){0,3} -/// -/// script = aplha{4} -/// -/// region = alpha{2} -/// | digit{3} -/// -/// variant = alphanum{5,8} -/// | digit alphanum{3} -/// -/// extension = [0-9a-wyz] ("-" alphanum{2,8})+ -/// -/// private = "x" ("-" alphanum{1,8})+ -/// ``` -/// -/// * `language` is an [ISO639] 2-letter or, where not defined, 3-letter code. A code for -/// macro-language might be followed by code of specific dialect. -/// * `script` is an [ISO15924] 4-letter code. -/// * `region` is either an [ISO3166] 2-letter code or, for areas other than countries, [UN M.49] -/// 3-digit numeric code. -/// * `variant` is a string indicating variant of the language. -/// * `extension` and `private` define additional options. The private part has same structure as -/// the Unicode [`-u-` extension][u_ext]. Available options are documented for the facets that -/// use them. -/// -/// The values obtained by inspecting the system are normalized according to those rules. -/// -/// The content will be case-normalized as recommended in [RFC5646] §2.1.1, namely: -/// -/// * `language` is written in lowercase, -/// * `script` is written with first capital, -/// * `country` is written in uppercase and -/// * all other subtags are written in lowercase. -/// -/// When detecting system configuration, additional options that may be generated under the -/// [`-u-` extension][u_ext] currently are: -/// -/// * `cf` — Currency format (`account` for parenthesized negative values, `standard` for minus -/// sign). -/// * `fw` — First day of week (`mon` to `sun`). -/// * `hc` — Hour cycle (`h12` for 1–12, `h23` for 0–23). -/// * `ms` — Measurement system (`metric` or `ussystem`). -/// * `nu` — Numbering system—only decimal systems are currently used. -/// * `va` — Variant when locale is specified in Unix format and the tag after `@` does not -/// correspond to any variant defined in [Language subtag registry]. -/// -/// And under the `-x-` extension, following options are defined: -/// -/// * `df` — Date format: -/// -/// * `iso`: Short date should be in ISO format of `yyyy-MM-dd`. -/// -/// For example `-df-iso`. -/// -/// * `dm` — Decimal separator for monetary: -/// -/// Followed by one or more Unicode codepoints in hexadecimal. For example `-dm-002d` means to -/// use comma. -/// -/// * `ds` — Decimal separator for numbers: -/// -/// Followed by one or more Unicode codepoints in hexadecimal. For example `-ds-002d` means to -/// use comma. -/// -/// * `gm` — Group (thousand) separator for monetary: -/// -/// Followed by one or more Unicode codepoints in hexadecimal. For example `-dm-00a0` means to -/// use non-breaking space. -/// -/// * `gs` — Group (thousand) separator for numbers: -/// -/// Followed by one or more Unicode codepoints in hexadecimal. For example `-ds-00a0` means to -/// use non-breaking space. -/// -/// * `ls` — List separator: -/// -/// Followed by one or more Unicode codepoints in hexadecimal. For example, `-ds-003b` means to -/// use a semicolon. -/// -/// [RFC5646]: https://www.rfc-editor.org/rfc/rfc5646.txt -/// [RFC4647]: https://www.rfc-editor.org/rfc/rfc4647.txt -/// [ISO639]: https://en.wikipedia.org/wiki/ISO_639 -/// [ISO15924]: https://en.wikipedia.org/wiki/ISO_15924 -/// [ISO3166]: https://en.wikipedia.org/wiki/ISO_3166 -/// [UN M.49]: https://en.wikipedia.org/wiki/UN_M.49 -/// [u_ext]: http://www.unicode.org/reports/tr35/#u_Extension -/// [Language subtag registry]: https://www.iana.org/assignments/language-subtag-registry/language-subtag-registry -#[derive(Clone,Debug,Eq,Hash,PartialEq)] -pub struct LanguageRange<'a> { - language: Cow<'a, str> -} - -impl<'a> LanguageRange<'a> { - /// Return LanguageRange for the invariant locale. - /// - /// Invariant language is identified simply by empty string. - pub fn invariant() -> LanguageRange<'static> { - LanguageRange { language: Cow::Borrowed("") } - } - - /// Create language tag from Unix/Linux/GNU locale tag. - /// - /// Unix locale tags have the form - /// - /// > *language* [ `_` *region* ] [ `.` *encoding* ] [ `@` *variant* ] - /// - /// The *language* and *region* have the same format as RFC5646. *Encoding* is not relevant - /// here, since Rust always uses Utf-8. That leaves *variant*, which is unfortunately rather - /// free-form. So this function will translate known variants to corresponding RFC5646 subtags - /// and represent anything else with Unicode POSIX variant (`-u-va-`) extension. - /// - /// Note: This function is public here for benefit of applications that may come across this - /// kind of tags from other sources than system configuration. - pub fn from_unix(s: &str) -> Result> { - let unix_tag_regex = Regex::new(r"(?ix) ^ - (?P [[:alpha:]]{2,3} ) - (?: _ (?P [[:alpha:]]{2} | [[:digit:]]{3} ))? - (?: \. (?P [0-9a-zA-Z-]{1,20} ))? - (?: @ (?P [[:alnum:]]{1,20} ))? - $ ").unwrap(); - - let unix_invariant_regex = Regex::new(r"(?ix) ^ - (?: c | posix ) - (?: \. (?: [0-9a-zA-Z-]{1,20} ))? - $ ").unwrap(); - - if let Some(caps) = unix_tag_regex.captures(s) { - let src_variant = caps.name("variant").map(|m| m.as_str()).unwrap_or("").to_ascii_lowercase(); - let mut res = caps.name("language").map(|m| m.as_str()).unwrap().to_ascii_lowercase(); - let region = caps.name("region").map(|m| m.as_str()).unwrap_or(""); - let mut script = ""; - let mut variant = ""; - let mut uvariant = ""; - match src_variant.as_ref() { - // Variants seen in the wild in GNU LibC (via http://lh.2xlibre.net/) or in Debian - // GNU/Linux Stretch system. Treatment of things not found in RFC5646 subtag registry - // (http://www.iana.org/assignments/language-subtag-registry/language-subtag-registry) - // or CLDR according to notes at https://wiki.openoffice.org/wiki/LocaleMapping. - // Dialects: - // aa_ER@saaho - NOTE: Can't be found under that name in RFC5646 subtag registry, - // but there is language Saho with code ssy, which is likely that thing. - "saaho" if res == "aa" => res = String::from("ssy"), - // Scripts: - // @arabic - "arabic" => script = "Arab", - // @cyrillic - "cyrl" => script = "Cyrl", - "cyrillic" => script = "Cyrl", - // @devanagari - "devanagari" => script = "Deva", - // @hebrew - "hebrew" => script = "Hebr", - // tt@iqtelif - // Neither RFC5646 subtag registry nor CLDR knows anything about this, but as best - // as I can tell it is Tatar name for Latin (default is Cyrillic). - "iqtelif" => script = "Latn", - // @Latn - "latn" => script = "Latn", - // @latin - "latin" => script = "Latn", - // en@shaw - "shaw" => script = "Shaw", - // Variants: - // sr@ijekavianlatin - "ijekavianlatin" => { - script = "Latn"; - variant = "ijekavsk"; - }, - // sr@ije - "ije" => variant = "ijekavsk", - // sr@ijekavian - "ijekavian" => variant = "ijekavsk", - // ca@valencia - "valencia" => variant = "valencia", - // Currencies: - // @euro - NOTE: We follow suite of Java and Openoffice and ignore it, because it - // is default for all locales where it sometimes appears now, and because we use - // explicit currency in monetary formatting anyway. - "euro" => {}, - // Collation: - // gez@abegede - NOTE: This is collation, but CLDR does not have any code for it, - // so we for the moment leave it fall through as -u-va- instead of -u-co-. - // Anything else: - // en@boldquot, en@quot, en@piglatin - just randomish stuff - // @cjknarrow - beware, it's gonna end up as -u-va-cjknarro due to lenght limit - s if s.len() <= 8 => uvariant = &*s, - s => uvariant = &s[0..8], // the subtags are limited to 8 chars, but some are longer - }; - if script != "" { - res.push('-'); - res.push_str(script); - } - if region != "" { - res.push('-'); - res.push_str(&*region.to_ascii_uppercase()); - } - if variant != "" { - res.push('-'); - res.push_str(variant); - } - if uvariant != "" { - res.push_str("-u-va-"); - res.push_str(uvariant); - } - return Ok(LanguageRange { - language: Cow::Owned(res) - }); - } else if unix_invariant_regex.is_match(s) { - return Ok(LanguageRange::invariant()) - } else { - return Err(Error::NotWellFormed); - } - } -} - -impl<'a> AsRef for LanguageRange<'a> { - fn as_ref(&self) -> &str { - self.language.as_ref() - } -} - -/// Locale configuration. -/// -/// Users may accept several languages in some order of preference and may want to use rules from -/// different culture for some particular aspect of the program behaviour, and operating systems -/// allow them to specify this (to various extent). -/// -/// The `Locale` objects represent the user configuration. They contain: -/// -/// - The primary `LanguageRange`. -/// - Optional category-specific overrides. -/// - Optional fallbacks in case data (usually translations) for the primary language are not -/// available. -/// -/// The set of categories is open-ended. The `locale` crate uses five well-known categories -/// `messages`, `numeric`, `time`, `collate` and `monetary`, but some systems define additional -/// ones (GNU Linux has additionally `paper`, `name`, `address`, `telephone` and `measurement`) and -/// these are provided in the user default `Locale` and other libraries can use them. -/// -/// `Locale` is represented by a `,`-separated sequence of tags in `LanguageRange` syntax, where -/// all except the first one may be preceded by category name and `=` sign. -/// -/// The first tag indicates the default locale, the tags prefixed by category names indicate -/// _overrides_ for those categories and the remaining tags indicate fallbacks. -/// -/// Note that a syntactically valid value of HTTP `Accept-Language` header is a valid `Locale`. Not -/// the other way around though due to the presence of category selectors. -// TODO: Interning -#[derive(Clone,Debug,Eq,Hash,PartialEq)] -pub struct Locale { - // TODO: Intern the string for performance reasons - // XXX: Store pre-split to LanguageTags? - inner: String, -} - -impl Locale { - /// Construct invariant locale. - /// - /// Invariant locale is represented simply with empty string. - pub fn invariant() -> Locale { - Locale::from(LanguageRange::invariant()) - } - - /// Append fallback language tag. - /// - /// Adds fallback to the end of the list. - pub fn add(&mut self, tag: &LanguageRange) { - for i in self.inner.split(',') { - if i == tag.as_ref() { - return; // don't add duplicates - } - } - self.inner.push_str(","); - self.inner.push_str(tag.as_ref()); - } - - /// Append category override. - /// - /// Appending new override for a category that already has one will not replace the existing - /// override. This might change in future. - pub fn add_category(&mut self, category: &str, tag: &LanguageRange) { - if self.inner.split(',').next().unwrap() == tag.as_ref() { - return; // don't add useless override equal to the primary tag - } - for i in self.inner.split(',') { - if i.starts_with(category) && - i[category.len()..].starts_with("=") && - &i[category.len() + 1..] == tag.as_ref() { - return; // don't add duplicates - } - } - self.inner.push_str(","); - self.inner.push_str(category); - self.inner.push_str("="); - self.inner.push_str(tag.as_ref()); - } - - /// Iterate over `LanguageRange`s in this `Locale` applicable to given category. - /// - /// Returns `LanguageRange`s in the `Locale` that are applicable to provided category. The tags - /// are returned in order of preference, which means the category-specific ones first and then - /// the generic ones. - /// - /// The iterator is guaranteed to return at least one value. - pub fn tags_for<'a, 'c>(&'a self, category: &'c str) -> TagsFor<'a, 'c> { - let mut tags = self.inner.split(","); - while let Some(s) = tags.clone().next() { - if s.starts_with(category) && s[category.len()..].starts_with("=") { - return TagsFor { - src: self.inner.as_ref(), - tags: tags, - category: Some(category), - }; - } - tags.next(); - } - return TagsFor { - src: self.inner.as_ref(), - tags: self.inner.split(","), - category: None, - }; - } -} - -/// Locale is specified by a string tag. This is the way to access it. -// FIXME: Do we want to provide the full string representation? We would have it as single string -// then. -impl AsRef for Locale { - fn as_ref(&self) -> &str { - self.inner.as_ref() - } -} - -impl<'a> From> for Locale { - fn from(t: LanguageRange<'a>) -> Locale { - Locale { - inner: t.language.into_owned(), - } - } -} - -fn tag(s: &str) -> Result { - LanguageRange::from_unix(s) -} - -// TODO: Read /etc/locale.alias -fn tag_inv(s: &str) -> LanguageRange { - tag(s).unwrap_or(LanguageRange::invariant()) -} - -pub fn system_locale() -> Option { - // LC_ALL overrides everything - if let Ok(all) = env::var("LC_ALL") { - if let Ok(t) = tag(all.as_ref()) { - return Some(Locale::from(t)); - } - } - // LANG is default - let mut loc = - if let Ok(lang) = env::var("LANG") { - Locale::from(tag_inv(lang.as_ref())) - } else { - Locale::invariant() - }; - // category overrides - for &(cat, var) in [ - ("ctype", "LC_CTYPE"), - ("numeric", "LC_NUMERIC"), - ("time", "LC_TIME"), - ("collate", "LC_COLLATE"), - ("monetary", "LC_MONETARY"), - ("messages", "LC_MESSAGES"), - ("paper", "LC_PAPER"), - ("name", "LC_NAME"), - ("address", "LC_ADDRESS"), - ("telephone", "LC_TELEPHONE"), - ("measurement", "LC_MEASUREMENT"), - ].iter() { - if let Ok(val) = env::var(var) { - if let Ok(tag) = tag(val.as_ref()) - { - loc.add_category(cat, &tag); - } - } - } - // LANGUAGE defines fallbacks - if let Ok(langs) = env::var("LANGUAGE") { - for i in langs.split(':') { - if i != "" { - if let Ok(tag) = tag(i) { - loc.add(&tag); - } - } - } - } - if loc.as_ref() != "" { - return Some(loc); - } else { - return None; - } -} - -#[cfg(test)] -mod test { - use super::LanguageRange; - - #[test] - fn unix_tags() { - assert_eq!("cs-CZ", LanguageRange::from_unix("cs_CZ.UTF-8").unwrap().as_ref()); - assert_eq!("sr-RS-ijekavsk", LanguageRange::from_unix("sr_RS@ijekavian").unwrap().as_ref()); - assert_eq!("sr-Latn-ijekavsk", LanguageRange::from_unix("sr.UTF-8@ijekavianlatin").unwrap().as_ref()); - assert_eq!("en-Arab", LanguageRange::from_unix("en@arabic").unwrap().as_ref()); - assert_eq!("en-Arab", LanguageRange::from_unix("en.UTF-8@arabic").unwrap().as_ref()); - assert_eq!("de-DE", LanguageRange::from_unix("DE_de.UTF-8@euro").unwrap().as_ref()); - assert_eq!("ssy-ER", LanguageRange::from_unix("aa_ER@saaho").unwrap().as_ref()); - assert!(LanguageRange::from_unix("foo_BAR").is_err()); - assert!(LanguageRange::from_unix("en@arabic.UTF-8").is_err()); - assert_eq!("", LanguageRange::from_unix("C").unwrap().as_ref()); - assert_eq!("", LanguageRange::from_unix("C.UTF-8").unwrap().as_ref()); - assert_eq!("", LanguageRange::from_unix("C.ISO-8859-1").unwrap().as_ref()); - assert_eq!("", LanguageRange::from_unix("POSIX").unwrap().as_ref()); - } -} From 0a417fe4a277c81b94277208bda60b818a54876a Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Guido=20G=C3=BCnther?= Date: Wed, 8 Dec 2021 14:18:18 +0100 Subject: [PATCH 8/8] Remove custom translations Can be handled via po. Most of them should come via libgnome-desktop anyway and half of them weren't listed in LAYOUT_NAMES so were unused anyway. --- data/langs/bg-BG.txt | 20 -------------------- data/langs/cs-CZ.txt | 19 ------------------- data/langs/es-ES.txt | 7 ------- data/langs/fur-IT.txt | 17 ----------------- data/langs/he-IL.txt | 17 ----------------- data/langs/hy-AM.txt | 10 ---------- data/langs/ru-RU.txt | 10 ---------- 7 files changed, 100 deletions(-) delete mode 100644 data/langs/bg-BG.txt delete mode 100644 data/langs/cs-CZ.txt delete mode 100644 data/langs/es-ES.txt delete mode 100644 data/langs/fur-IT.txt delete mode 100644 data/langs/he-IL.txt delete mode 100644 data/langs/hy-AM.txt delete mode 100644 data/langs/ru-RU.txt diff --git a/data/langs/bg-BG.txt b/data/langs/bg-BG.txt deleted file mode 100644 index 7bafc131..00000000 --- a/data/langs/bg-BG.txt +++ /dev/null @@ -1,20 +0,0 @@ -ara Арабски -be Белгийски -bg Български -br Бразилски -cz Чешки -de Немски -dk Датски -es Испански -fi Фински -fr Френски -gr Гръцки -it Италиански -jp Японски -no Норвежки -pl Полски -ru Руски -se Шведски -th Тайски -ua Украински -us Английски (САЩ) diff --git a/data/langs/cs-CZ.txt b/data/langs/cs-CZ.txt deleted file mode 100644 index 1482fe86..00000000 --- a/data/langs/cs-CZ.txt +++ /dev/null @@ -1,19 +0,0 @@ -be Belgická -cz Česká -cz+qwerty Česká (QWERTY) -de Německá -dk Dánská -es Španělská -fi Finská -fr Francouzská -gr Řecká -it Italská -jp Japonská -jp+kana Japonská (Kana) -no Norská -pl Polská -ru Ruská -se Švédská -th Thajská -ua Ukrajinská -us Anglická (USA) diff --git a/data/langs/es-ES.txt b/data/langs/es-ES.txt deleted file mode 100644 index d377de21..00000000 --- a/data/langs/es-ES.txt +++ /dev/null @@ -1,7 +0,0 @@ -us Inglés (EE.UU.) -de Alemán -el Griego -es Español -it Italiano -jp+kana Japonés (Kana) -no Noruego diff --git a/data/langs/fur-IT.txt b/data/langs/fur-IT.txt deleted file mode 100644 index 956e68e5..00000000 --- a/data/langs/fur-IT.txt +++ /dev/null @@ -1,17 +0,0 @@ -be Belgjic -br Brasilian -de Todesc -dk Danês -es Spagnûl -fi Finlandês -fr Francês -it+fur Furlan -gr Grêc -it Talian -jp+kana Gjaponês (Kana) -no Norvegjês -pl Polac -ru Rus -se Svedês -ua Ucrain -us American (USA) diff --git a/data/langs/he-IL.txt b/data/langs/he-IL.txt deleted file mode 100644 index c6db858e..00000000 --- a/data/langs/he-IL.txt +++ /dev/null @@ -1,17 +0,0 @@ -be בלגית -br פורטוגזית (ברזיל) -cz צ'כית -de גרמנית -dk דנית -es ספרדית -fi פינית -fr צרפתית -gr יוונית -il עברית -it איטלקית -no נורווגית -pl פולנית -ru רוסית -se שוודית -ua אוקראינית -us אנגלית (ארה"ב) diff --git a/data/langs/hy-AM.txt b/data/langs/hy-AM.txt deleted file mode 100644 index 1e8b1f76..00000000 --- a/data/langs/hy-AM.txt +++ /dev/null @@ -1,10 +0,0 @@ -de Գերմաներէն -es Իսպաներէն -fi Ֆիններէն -gr Յունարէն -it Իտալերէն -no Նորուեգերէն -pl Լեհերէն -ru Ռուսերէն -se Շուեդերէն -us Անգլերէն (ԱՄՆ) diff --git a/data/langs/ru-RU.txt b/data/langs/ru-RU.txt deleted file mode 100644 index 72377337..00000000 --- a/data/langs/ru-RU.txt +++ /dev/null @@ -1,10 +0,0 @@ -de Немецкий -es Испанский -fi Финский -gr Греческий -it Итальянский -no Норвежский -pl Польский -ru Русский -se Шведский -us Английский (США)