mirror of
https://github.com/storytold/lightcraft.git
synced 2026-10-29 04:27:30 -06:00
Merge PR #2449: Settings and Edit ▸ Language: list the languages alphabetically by their own name
Conflicts: - crates/ui-egui/src/menus.rs: LANGUAGE_COMMANDS lists every language, including sv, fi, id and vi, in Locale::for_picker order
This commit is contained in:
@@ -136,6 +136,39 @@ language_table! {
|
||||
Vi, "vi", "Tiếng Việt", "Latn", include_str!("../locales/vi.json");
|
||||
}
|
||||
|
||||
impl Locale {
|
||||
/// Every language in the order the Settings row and Edit ▸ Language list them: English first
|
||||
/// (the source language), then the rest by their own name with accents folded, so Čeština
|
||||
/// sits under C. Names in other scripts follow the Latin ones in code point order: Cyrillic,
|
||||
/// Georgian, then the CJK languages.
|
||||
pub fn for_picker() -> Vec<Self> {
|
||||
let mut rest: Vec<Self> = Self::ALL.iter().copied().filter(|l| *l != Self::En).collect();
|
||||
rest.sort_by_cached_key(|l| name_sort_key(l.name()));
|
||||
std::iter::once(Self::En).chain(rest).collect()
|
||||
}
|
||||
}
|
||||
|
||||
/// `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()
|
||||
}
|
||||
|
||||
// The settings file stores the BCP-47 code (`"zh-hans"`), never the Rust variant name, so a
|
||||
// language can be renamed in code without invalidating anyone's settings.
|
||||
impl Serialize for Locale {
|
||||
@@ -2015,3 +2048,25 @@ mod tests {
|
||||
set_language(Locale::En);
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod picker_order_tests {
|
||||
use super::*;
|
||||
|
||||
#[test]
|
||||
fn picker_lists_english_first_then_names_alphabetically() {
|
||||
let order = Locale::for_picker();
|
||||
assert_eq!(order.first(), Some(&Locale::En));
|
||||
assert_eq!(order.len(), Locale::ALL.len());
|
||||
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");
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn language_menu_lists_the_picker_order() {
|
||||
let menu: Vec<&str> = crate::menus::LANGUAGE_COMMANDS.iter().map(|c| c.1).collect();
|
||||
let picker: Vec<&str> = Locale::for_picker().into_iter().map(Locale::name).collect();
|
||||
assert_eq!(menu, picker, "Edit ▸ Language and the Settings row list the languages the same way");
|
||||
}
|
||||
}
|
||||
|
||||
+15
-13
@@ -15,23 +15,25 @@ pub type UiCommand = (&'static str, &'static str, Option<&'static str>, &'static
|
||||
/// has a matching menu command (also listed through the control channel). Tests check coverage.
|
||||
pub const LANGUAGE_COMMANDS: &[UiCommand] = &[
|
||||
("app.language.english", crate::i18n::Locale::En.name(), None, "Edit>Language"),
|
||||
("app.language.simplifiedChinese", crate::i18n::Locale::ZhHans.name(), None, "Edit>Language"),
|
||||
("app.language.traditionalChinese", crate::i18n::Locale::ZhHant.name(), None, "Edit>Language"),
|
||||
("app.language.japanese", crate::i18n::Locale::Ja.name(), None, "Edit>Language"),
|
||||
("app.language.portuguese", crate::i18n::Locale::PtBr.name(), None, "Edit>Language"),
|
||||
("app.language.french", crate::i18n::Locale::Fr.name(), None, "Edit>Language"),
|
||||
("app.language.spanish", crate::i18n::Locale::Es.name(), None, "Edit>Language"),
|
||||
// The picker order (`Locale::for_picker`): alphabetical by the language's own name after
|
||||
// English; Cyrillic, Georgian, then CJK names follow the Latin ones. A test keeps them equal.
|
||||
("app.language.indonesian", crate::i18n::Locale::Id.name(), None, "Edit>Language"),
|
||||
("app.language.czech", crate::i18n::Locale::Cs.name(), None, "Edit>Language"),
|
||||
("app.language.german", crate::i18n::Locale::De.name(), None, "Edit>Language"),
|
||||
("app.language.spanish", crate::i18n::Locale::Es.name(), None, "Edit>Language"),
|
||||
("app.language.french", crate::i18n::Locale::Fr.name(), None, "Edit>Language"),
|
||||
("app.language.italian", crate::i18n::Locale::It.name(), None, "Edit>Language"),
|
||||
("app.language.polish", crate::i18n::Locale::Pl.name(), None, "Edit>Language"),
|
||||
("app.language.portuguese", crate::i18n::Locale::PtBr.name(), None, "Edit>Language"),
|
||||
("app.language.finnish", crate::i18n::Locale::Fi.name(), None, "Edit>Language"),
|
||||
("app.language.swedish", crate::i18n::Locale::Sv.name(), None, "Edit>Language"),
|
||||
("app.language.vietnamese", crate::i18n::Locale::Vi.name(), None, "Edit>Language"),
|
||||
("app.language.russian", crate::i18n::Locale::Ru.name(), None, "Edit>Language"),
|
||||
("app.language.ukrainian", crate::i18n::Locale::Uk.name(), None, "Edit>Language"),
|
||||
("app.language.polish", crate::i18n::Locale::Pl.name(), None, "Edit>Language"),
|
||||
("app.language.czech", crate::i18n::Locale::Cs.name(), None, "Edit>Language"),
|
||||
("app.language.italian", crate::i18n::Locale::It.name(), None, "Edit>Language"),
|
||||
("app.language.georgian", crate::i18n::Locale::Ka.name(), None, "Edit>Language"),
|
||||
("app.language.swedish", crate::i18n::Locale::Sv.name(), None, "Edit>Language"),
|
||||
("app.language.finnish", crate::i18n::Locale::Fi.name(), None, "Edit>Language"),
|
||||
("app.language.indonesian", crate::i18n::Locale::Id.name(), None, "Edit>Language"),
|
||||
("app.language.vietnamese", crate::i18n::Locale::Vi.name(), None, "Edit>Language"),
|
||||
("app.language.japanese", crate::i18n::Locale::Ja.name(), None, "Edit>Language"),
|
||||
("app.language.simplifiedChinese", crate::i18n::Locale::ZhHans.name(), None, "Edit>Language"),
|
||||
("app.language.traditionalChinese", crate::i18n::Locale::ZhHant.name(), None, "Edit>Language"),
|
||||
];
|
||||
|
||||
/// Every UI command: the languages, then everything else. `xtask parity` reads both tables from
|
||||
|
||||
@@ -106,7 +106,7 @@ pub(super) fn choices<V: PartialEq + Copy>(ui: &mut egui::Ui, id: &str, options:
|
||||
|
||||
fn general_tab(app: &mut LightcraftApp, ui: &mut egui::Ui, t: &Tokens) {
|
||||
row(ui, t, crate::i18n::tr("Language"), |ui| {
|
||||
let languages: Vec<_> = crate::i18n::Locale::ALL.iter().map(|language| (*language, language.name())).collect();
|
||||
let languages: Vec<_> = crate::i18n::Locale::for_picker().into_iter().map(|language| (language, language.name())).collect();
|
||||
// Thirteen language names do not fit one line in a narrow window: wrap them.
|
||||
ui.horizontal_wrapped(|ui| {
|
||||
choices(ui, "settingsLanguage", &languages, &mut app.ui.language);
|
||||
|
||||
Reference in New Issue
Block a user