Add full set of locale translations

Generated using Claude Code / Codex so there may be errors
This commit is contained in:
2026-03-17 05:05:32 +00:00
parent 6d5de33f55
commit 84f4aabdff
20 changed files with 8748 additions and 47 deletions

View File

@@ -1,7 +1,10 @@
pub use rust_i18n::t;
/// Available UI locale codes. Separate from dictionary language support.
pub const SUPPORTED_UI_LOCALES: &[&str] = &["en", "de"];
pub const SUPPORTED_UI_LOCALES: &[&str] = &[
"en", "de", "es", "fr", "it", "pt", "nl", "sv", "da", "nb", "fi", "pl", "cs", "ro", "hr",
"hu", "lt", "lv", "sl", "et", "tr",
];
pub fn set_ui_locale(locale: &str) {
let effective = if SUPPORTED_UI_LOCALES.contains(&locale) {
@@ -74,70 +77,85 @@ mod tests {
}
#[test]
fn catalog_parity_en_de() {
fn catalog_parity_all_locales() {
let en = locale_keys("en");
let de = locale_keys("de");
let mut errors = Vec::new();
let missing_in_de: Vec<_> = en.difference(&de).collect();
let extra_in_de: Vec<_> = de.difference(&en).collect();
for locale in SUPPORTED_UI_LOCALES {
if *locale == "en" {
continue;
}
let other = locale_keys(locale);
let missing: Vec<_> = en.difference(&other).collect();
let extra: Vec<_> = other.difference(&en).collect();
assert!(
missing_in_de.is_empty(),
"Keys in en.yml missing from de.yml:\n {}",
missing_in_de
.iter()
.map(|k| k.as_str())
.collect::<Vec<_>>()
.join("\n ")
);
assert!(
extra_in_de.is_empty(),
"Keys in de.yml not present in en.yml:\n {}",
extra_in_de
.iter()
.map(|k| k.as_str())
.collect::<Vec<_>>()
.join("\n ")
);
if !missing.is_empty() {
errors.push(format!(
"Keys in en.yml missing from {locale}.yml:\n {}",
missing
.iter()
.map(|k| k.as_str())
.collect::<Vec<_>>()
.join("\n ")
));
}
if !extra.is_empty() {
errors.push(format!(
"Keys in {locale}.yml not present in en.yml:\n {}",
extra
.iter()
.map(|k| k.as_str())
.collect::<Vec<_>>()
.join("\n ")
));
}
}
assert!(errors.is_empty(), "Catalog parity errors:\n{}", errors.join("\n"));
}
#[test]
fn placeholder_parity_en_de() {
fn placeholder_parity_all_locales() {
let en_content = std::fs::read_to_string("locales/en.yml").unwrap();
let de_content = std::fs::read_to_string("locales/de.yml").unwrap();
let en_root: serde_yaml::Value = serde_yaml::from_str(&en_content).unwrap();
let de_root: serde_yaml::Value = serde_yaml::from_str(&de_content).unwrap();
let mut en_map = std::collections::BTreeMap::new();
let mut de_map = std::collections::BTreeMap::new();
collect_leaf_values(&en_root, "", &mut en_map);
collect_leaf_values(&de_root, "", &mut de_map);
let placeholder_re = regex::Regex::new(r"%\{(\w+)\}").unwrap();
let mut mismatches = Vec::new();
let mut all_mismatches = Vec::new();
for (key, en_val) in &en_map {
if let Some(de_val) = de_map.get(key) {
let en_placeholders: BTreeSet<_> = placeholder_re
.captures_iter(en_val)
.map(|c| c[1].to_string())
.collect();
let de_placeholders: BTreeSet<_> = placeholder_re
.captures_iter(de_val)
.map(|c| c[1].to_string())
.collect();
if en_placeholders != de_placeholders {
mismatches.push(format!(
" {key}: en={en_placeholders:?} de={de_placeholders:?}"
));
for locale in SUPPORTED_UI_LOCALES {
if *locale == "en" {
continue;
}
let other_content = std::fs::read_to_string(format!("locales/{locale}.yml")).unwrap();
let other_root: serde_yaml::Value = serde_yaml::from_str(&other_content).unwrap();
let mut other_map = std::collections::BTreeMap::new();
collect_leaf_values(&other_root, "", &mut other_map);
for (key, en_val) in &en_map {
if let Some(other_val) = other_map.get(key) {
let en_placeholders: BTreeSet<_> = placeholder_re
.captures_iter(en_val)
.map(|c| c[1].to_string())
.collect();
let other_placeholders: BTreeSet<_> = placeholder_re
.captures_iter(other_val)
.map(|c| c[1].to_string())
.collect();
if en_placeholders != other_placeholders {
all_mismatches.push(format!(
" {locale}/{key}: en={en_placeholders:?} {locale}={other_placeholders:?}"
));
}
}
}
}
assert!(
mismatches.is_empty(),
"Placeholder mismatches between en.yml and de.yml:\n{}",
mismatches.join("\n")
all_mismatches.is_empty(),
"Placeholder mismatches:\n{}",
all_mismatches.join("\n")
);
}