Options: 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:
Nikolai Nyegaard
2026-10-21 12:01:39 +02:00
parent e05b7fe86f
commit ea61d1fcb8
2 changed files with 48 additions and 1 deletions
+47
View File
@@ -256,6 +256,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()
}
fn catalog(self) -> &'static Catalog {
self.0.catalog()
}
@@ -474,3 +484,40 @@ mod audit;
pub(crate) mod pseudo;
#[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");
}
}
+1 -1
View File
@@ -634,7 +634,7 @@ fn language_picker(app: &mut WordApp, ui: &mut Ui) {
if ui.selectable_label(app.ui.language == AUTO, auto_label.as_str()).clicked() {
let _ = app.run("ui.language", json!({"value": AUTO}));
}
for lang in Lang::all() {
for lang in Lang::for_picker() {
if ui.selectable_label(app.ui.language == lang.code(), lang.name()).clicked() {
let _ = app.run("ui.language", json!({"value": lang.code()}));
}