mirror of
https://github.com/storytold/vectorcraft.git
synced 2026-10-24 09:46:59 -06:00
Preferences: list the languages alphabetically by their own name
- English (and Auto where offered) stays first as the source language and fallback; the rest sort by native name with accents folded, so Čeština is under C and Magyar under M; Cyrillic and CJK names follow the Latin ones - The list used the registry order, which is the order the languages were added
This commit is contained in:
committed by
bflatastic
parent
cb0a7d9b31
commit
48263b339d
@@ -312,6 +312,16 @@ impl Lang {
|
||||
LANGUAGES.iter().map(Lang)
|
||||
}
|
||||
|
||||
/// Every registered language in the order a picker lists them: English first (the source
|
||||
/// language and the fallback), then the rest by their own name with accents folded, so
|
||||
/// Čeština sits under C rather than after Z. Names in other scripts follow the Latin ones in
|
||||
/// code point order: Greek, Cyrillic, Arabic, then the CJK languages.
|
||||
pub fn for_picker() -> Vec<Lang> {
|
||||
let mut rest: Vec<Lang> = Lang::all().filter(|l| l.code() != Lang::EN.code()).collect();
|
||||
rest.sort_by_cached_key(|l| name_sort_key(l.name()));
|
||||
std::iter::once(Lang::EN).chain(rest).collect()
|
||||
}
|
||||
|
||||
pub fn name(self) -> &'static str {
|
||||
self.0.name
|
||||
}
|
||||
@@ -596,3 +606,40 @@ pub fn tn(n: u64, one: &str, other: &str) -> String {
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests;
|
||||
|
||||
/// `name` lowercased with the Latin accents that occur in language names folded away, so a
|
||||
/// byte-wise sort reads alphabetically. Other scripts are left as they are.
|
||||
fn name_sort_key(name: &str) -> String {
|
||||
name.chars()
|
||||
.flat_map(char::to_lowercase)
|
||||
.map(|c| match c {
|
||||
'à'..='å' | 'ā' | 'ă' | 'ą' => 'a',
|
||||
'ç' | 'ć' | 'č' => 'c',
|
||||
'è'..='ë' | 'ē' | 'ě' | 'ę' | 'ế' | 'ệ' => 'e',
|
||||
'ì'..='ï' | 'ī' => 'i',
|
||||
'ñ' | 'ń' | 'ň' => 'n',
|
||||
'ò'..='ö' | 'ø' | 'ő' => 'o',
|
||||
'ś' | 'š' => 's',
|
||||
'ù'..='ü' | 'ū' | 'ů' | 'ű' => 'u',
|
||||
'ý' | 'ÿ' => 'y',
|
||||
'ź' | 'ż' | 'ž' => 'z',
|
||||
c => c,
|
||||
})
|
||||
.collect()
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod picker_order_tests {
|
||||
use super::*;
|
||||
|
||||
#[test]
|
||||
fn picker_lists_english_first_then_names_alphabetically() {
|
||||
let order = Lang::for_picker();
|
||||
assert_eq!(order.first().map(|l| l.code()), Some(Lang::EN.code()));
|
||||
assert_eq!(order.len(), Lang::all().count());
|
||||
let keys: Vec<String> = order[1..].iter().map(|l| name_sort_key(l.name())).collect();
|
||||
assert!(keys.windows(2).all(|w| w[0] <= w[1]), "{keys:?}");
|
||||
assert_eq!(name_sort_key("Čeština"), "cestina");
|
||||
assert_eq!(name_sort_key("Türkçe"), "turkce");
|
||||
}
|
||||
}
|
||||
|
||||
@@ -365,7 +365,7 @@ pub fn mac_layout(app: &VectorcraftApp, bar: &MenuBar, lang: Lang) -> Layout {
|
||||
/// with the `interfaceLanguage` preference checked.
|
||||
fn language_node(app: &VectorcraftApp, lang: Lang) -> Node {
|
||||
let mut children = vec![Node::Item(native_item(app, "Automatic", "app.language", json!({"lang": "auto"}))), Node::Separator];
|
||||
children.extend(Lang::all().map(|l| Node::Item(native_item(app, l.name(), "app.language", json!({"lang": l.code()})))));
|
||||
children.extend(Lang::for_picker().into_iter().map(|l| Node::Item(native_item(app, l.name(), "app.language", json!({"lang": l.code()})))));
|
||||
Node::Submenu { label: tr(lang, "Language").into(), children }
|
||||
}
|
||||
|
||||
|
||||
@@ -348,8 +348,8 @@ fn category_fields(ui: &mut egui::Ui, d: &mut Dialog, cat: &str) {
|
||||
// `auto` plus every registered language, by its own name.
|
||||
labeled(ui, sp.label, |ui| {
|
||||
let cur = v.as_str().unwrap_or("auto");
|
||||
let codes: Vec<&str> = std::iter::once("auto").chain(crate::i18n::Lang::all().map(|l| l.code())).collect();
|
||||
let labels: Vec<&str> = std::iter::once(tl!("Auto")).chain(crate::i18n::Lang::all().map(|l| l.name())).collect();
|
||||
let codes: Vec<&str> = std::iter::once("auto").chain(crate::i18n::Lang::for_picker().into_iter().map(|l| l.code())).collect();
|
||||
let labels: Vec<&str> = std::iter::once(tl!("Auto")).chain(crate::i18n::Lang::for_picker().into_iter().map(|l| l.name())).collect();
|
||||
let cur_label = codes.iter().position(|c| c.eq_ignore_ascii_case(cur)).and_then(|i| labels.get(i).copied()).unwrap_or(cur);
|
||||
if let Some(i) = widgets::dropdown(ui, sp.key, cur_label, &labels, 260.0)
|
||||
&& let Some(code) = codes.get(i)
|
||||
|
||||
@@ -135,7 +135,7 @@ fn language_and_appearance_are_in_the_app_menu() {
|
||||
assert_eq!(ids(submenu(app_menu, "Language"))[..2], ["app.language", "---"], "Automatic, then the languages");
|
||||
let langs: Vec<(String, Option<bool>)> = items(submenu(app_menu, "Language")).iter().map(|it| (it.label.clone(), it.checked)).collect();
|
||||
let mut want = vec![("Automatic".to_string(), Some(false))];
|
||||
want.extend(Lang::all().map(|l| (l.name().to_string(), Some(l.code() == "ja"))));
|
||||
want.extend(Lang::for_picker().into_iter().map(|l| (l.name().to_string(), Some(l.code() == "ja"))));
|
||||
assert_eq!(langs, want);
|
||||
// In every UI language the languages keep their own names.
|
||||
for ui in Lang::all() {
|
||||
|
||||
Reference in New Issue
Block a user