French (fr) UI localization (#1224)

Adds a French catalog as a partial locale (missing strings fall back to English, like Russian), registered in the language list, with tests and attribution.
This commit is contained in:
rlartot authored and GitHub committed 2026-10-14 19:05:17 -04:00
1 parent 1cc65cbc91
commit e4ef6cfe29
7 files changed
+1083 -14

No files matched your search

+43 -8
View File
@@ -34,10 +34,11 @@ impl Catalog {
static ENGLISH: Catalog = Catalog::new("en", "English", include_str!(concat!(env!("CARGO_MANIFEST_DIR"), "/../../assets/locales/en.json")));
static JAPANESE: Catalog = Catalog::new("ja", "日本語", include_str!(concat!(env!("CARGO_MANIFEST_DIR"), "/../../assets/locales/ja.json")));
static GERMAN: Catalog = Catalog::new("de", "Deutsch", include_str!(concat!(env!("CARGO_MANIFEST_DIR"), "/../../assets/locales/de.json")));
static FRENCH: Catalog = Catalog::new("fr", "Français", include_str!(concat!(env!("CARGO_MANIFEST_DIR"), "/../../assets/locales/fr.json")));
static KOREAN: Catalog = Catalog::new("ko", "한국어", include_str!(concat!(env!("CARGO_MANIFEST_DIR"), "/../../assets/locales/ko.json")));
static RUSSIAN: Catalog = Catalog::new("ru", "Русский", include_str!(concat!(env!("CARGO_MANIFEST_DIR"), "/../../assets/locales/ru.json")));
static CHINESE: Catalog = Catalog::new("zh", "中文", include_str!(concat!(env!("CARGO_MANIFEST_DIR"), "/../../assets/locales/zh.json")));
pub const LOCALES: &[&Catalog] = &[&ENGLISH, &JAPANESE, &GERMAN, &KOREAN, &RUSSIAN, &CHINESE];
pub const LOCALES: &[&Catalog] = &[&ENGLISH, &JAPANESE, &GERMAN, &FRENCH, &KOREAN, &RUSSIAN, &CHINESE];
pub fn is_supported(id: &str) -> bool {
LOCALES.iter().any(|catalog| catalog.id == id)
@@ -188,9 +189,9 @@ mod tests {
}
/// Catalogs delivered complete. English is the source catalog; Japanese was completed
/// upstream and Chinese lands complete here. German, Korean and Russian are partial
/// (German and Korean predate the coverage guards; Russian is kept partial so a new label
/// doesn't need a sixth translation), so their gaps fall back to English and are reported by
/// upstream and Chinese lands complete here. German, Korean, Russian and French are partial
/// (German and Korean predate the coverage guards; Russian and French are kept partial so a
/// new label doesn't need more translations), so their gaps fall back to English and are reported by
/// `cargo xtask i18n` without failing the build, while nothing in the complete catalogs may
/// regress.
const COMPLETE: &[&str] = &["ja", "zh"];
@@ -260,6 +261,7 @@ mod tests {
("de", "Flansch", "Zebra-Analyse", "Bewegungsuntersuchung"),
("ko", "플랜지", "얼룩말줄무늬 분석", "운동 연구"),
("ru", "Фланец", "Зебра-анализ", "Исследование движения"),
("fr", "Bord tombé", "Analyse zébrée", "Étude de mouvement"),
];
for (lang, flange, zebra, motion) in cases {
let locale = Locale::for_language(lang);
@@ -335,9 +337,13 @@ mod tests {
/// non-empty string" would not catch that.
#[test]
fn id_labels_resolve_to_their_own_translation_not_an_english_fallback() {
for (lang, profile, points) in
[("de", "Profil", "Punkte"), ("ja", "プロファイル", "点"), ("ko", "프로파일", "점"), ("ru", "Профиль", "Точки")]
{
for (lang, profile, points) in [
("de", "Profil", "Punkte"),
("ja", "プロファイル", "点"),
("ko", "프로파일", "점"),
("ru", "Профиль", "Точки"),
("fr", "Profil", "Points"),
] {
let locale = Locale::for_language(lang);
assert_eq!(locale.label("ui.palette_profile"), profile, "{lang}");
assert_eq!(locale.label("ui.palette_points"), points, "{lang}");
@@ -355,7 +361,13 @@ mod tests {
/// the row's two-word text must reach its own entry rather than that one.
#[test]
fn the_construction_geometries_row_reaches_its_own_entry_not_the_line_type() {
let rows = [("de", "Konstruktionsgeometrien"), ("ja", "構築ジオメトリ"), ("ko", "구성 형상"), ("ru", "Вспомогательная геометрия")];
let rows = [
("de", "Konstruktionsgeometrien"),
("ja", "構築ジオメトリ"),
("ko", "구성 형상"),
("ru", "Вспомогательная геометрия"),
("fr", "Géométries de construction"),
];
for (lang, want) in rows {
let locale = Locale::for_language(lang);
assert_eq!(locale.label("Construction Geometries"), want, "{lang}");
@@ -428,6 +440,29 @@ mod tests {
assert_eq!(ru.dynamic("Sketch of bracket"), "Sketch of bracket");
}
#[test]
fn french_catalog_translates_keys_labels_and_browser_patterns() {
let fr = Locale::for_language("fr");
assert_eq!(fr.name(), "Français");
assert!(is_supported("fr"));
assert_eq!(fr.text("ui.preferences"), "Préférences");
assert_eq!(fr.label("Extrude"), "Extrusion");
assert_eq!(fr.label("Fillet"), "Congé");
assert_eq!(fr.label("CREATE SKETCH"), "CRÉER UNE ESQUISSE");
assert_eq!(fr.label("NonExistentKey"), "NonExistentKey");
// French puts a no-break space before the two-part punctuation marks.
assert_eq!(fr.format("document.save_changes", &[("name", "My {design}")]), "Enregistrer les modifications de My {design}\u{a0}?");
assert_eq!(fr.format("dialog.selected_count", &[("count", "2")]), "↖ 2 sélectionné(s)");
assert_eq!(fr.dynamic("Sketch1"), "Esquisse1");
assert_eq!(fr.dynamic("Body2"), "Corps2");
assert_eq!(fr.dynamic("Component12"), "Composant12");
assert_eq!(fr.dynamic("Units: mm"), "Unités\u{a0}: mm");
assert_eq!(fr.dynamic("Precision: 3"), "Précision\u{a0}: 3");
assert_eq!(fr.dynamic("Parameters (5)"), "Paramètres (5)");
assert_eq!(fr.dynamic("4 degrees of freedom"), "4 degrés de liberté");
assert_eq!(fr.dynamic("Sketch of bracket"), "Sketch of bracket");
}
#[test]
fn render_scopes_restore_the_previous_language() {
let _english = scope("en");
+1
View File
@@ -65,6 +65,7 @@ fn window_chrome_renders_in_the_active_language() {
("de", "BROWSER", "Auswahlfilter", "Flächen"),
("ko", "브라우저", "선택 필터", "면"),
("ru", "БРАУЗЕР", "Фильтр выделения", "Грани"),
("fr", "NAVIGATEUR", "Filtre de sélection", "Faces"),
] {
let mut h = Harness::new();
h.app.preferences.language = lang.into();
+1 -1
View File
@@ -1584,7 +1584,7 @@ mod tests {
assert!(control(&mut app, &json!({"open": "Plugins"})).is_err());
assert!(set(&mut app, &json!({"up_axis": "x"})).is_err());
assert!(set(&mut app, &json!({"theme": "sepia"})).is_err());
assert!(set(&mut app, &json!({"language": "fr"})).is_err());
assert!(set(&mut app, &json!({"language": "xx"})).is_err());
}
#[test]