Merge pull request #934 from dwetscher/feat/i18n-german
German interface language (de)
This commit is contained in:
commit
2b000d828e
|
|
@ -213,7 +213,7 @@ doesn't write Affinity files. Current `.af` validation includes 33 pinned files,
|
|||
and every-board SVG/PDF/PSD export, with fixes for Affinity 3 artboards, source-backed JPEGs and text runs.
|
||||
[Scope and limits](crates/affinity/README.md); [source audit and remaining gaps](docs/affinity-validation.md).
|
||||
The interface
|
||||
speaks English, Japanese, Traditional and Simplified Chinese, Spanish, French, Italian, Russian and Ukrainian (and Czech and Brazilian Portuguese in the menus).
|
||||
speaks English, German, Japanese, Traditional and Simplified Chinese, Spanish, French, Italian, Russian and Ukrainian (and Czech and Brazilian Portuguese in the menus).
|
||||
The scores are
|
||||
self-assessed, so the [honest assessment](ROADMAP.md#honest-assessment-2026-10-05) explains how far to trust them.
|
||||
|
||||
|
|
|
|||
File diff suppressed because one or more lines are too long
|
|
@ -733,7 +733,7 @@ mod tests {
|
|||
let fr = crate::i18n::Lang::from_code("fr").unwrap();
|
||||
let shown = crate::i18n::message(fr, "2 font file(s) are now in /Fonts; 1 couldn't be added: a.otf: larger than 256 MB");
|
||||
assert!(shown.contains("se trouvent maintenant dans /Fonts") && shown.contains("a.otf") && shown.contains("plus de 256 Mo"), "{shown}");
|
||||
for code in ["es", "fr", "it", "ru"] {
|
||||
for code in ["de", "es", "fr", "it", "ru"] {
|
||||
let lang = crate::i18n::Lang::from_code(code).unwrap();
|
||||
for s in [
|
||||
"every search thread stopped on an internal error (please report this bug)",
|
||||
|
|
|
|||
File diff suppressed because it is too large
Load Diff
|
|
@ -82,13 +82,23 @@ fn plural_east_slavic(n: u64) -> usize {
|
|||
}
|
||||
|
||||
/// The registry. English first: it is the fallback and the source language.
|
||||
pub static LANGUAGES: [LangInfo; 11] = [
|
||||
pub static LANGUAGES: [LangInfo; 12] = [
|
||||
LangInfo { code: "en", name: "English", source: "", plural: plural_one_other, complete_menus: false, catalog: OnceLock::new() },
|
||||
// Japanese: the whole interface (every menu string and `tl!` literal) and the status and error
|
||||
// messages, keeping the product, workspace and perspective preset names in English (`MENU_KEEP_AS_IS`).
|
||||
LangInfo { code: "ja", name: "日本語", source: include_str!("ja.tsv"), plural: plural_none, complete_menus: true, catalog: OnceLock::new() },
|
||||
// Czech: every menu label (`menu_catalogs_translate_every_menu_label`); panels and dialogs not yet.
|
||||
LangInfo { code: "cs", name: "Čeština", source: include_str!("cs.tsv"), plural: plural_czech, complete_menus: false, catalog: OnceLock::new() },
|
||||
// German: the whole interface and the status and error messages, keeping the same names in
|
||||
// English as Spanish; every `de-*` locale (`de-DE`, `de-AT`, `de-CH`, `de-LU` …) resolves here.
|
||||
LangInfo {
|
||||
code: "de",
|
||||
name: "Deutsch",
|
||||
source: include_str!("de.tsv"),
|
||||
plural: plural_one_other,
|
||||
complete_menus: true,
|
||||
catalog: OnceLock::new(),
|
||||
},
|
||||
// Spanish: the whole interface in neutral, international Spanish, keeping the same names in
|
||||
// English as Japanese; every `es-*` locale (`es-ES`, `es-MX`, `es-AR`, `es-419` …) resolves here.
|
||||
LangInfo {
|
||||
|
|
|
|||
|
|
@ -8,7 +8,11 @@ fn tags_map_to_languages() {
|
|||
assert_eq!(lang_from_tag("en_US.UTF-8"), Some(Lang::EN));
|
||||
assert_eq!(lang_from_tag("C"), Some(Lang::EN));
|
||||
assert_eq!(lang_from_tag("POSIX"), Some(Lang::EN));
|
||||
assert_eq!(lang_from_tag("de_DE"), None);
|
||||
assert_eq!(lang_from_tag("nl_NL"), None);
|
||||
// German: Germany, Austria, Switzerland and the rest share the one catalog.
|
||||
for tag in ["de", "de_DE.UTF-8", "de-AT", "de_CH", "de-LU", "de_DE.UTF-8@euro"] {
|
||||
assert_eq!(lang_from_tag(tag), Some(de()), "{tag}");
|
||||
}
|
||||
assert_eq!(lang_from_tag("ja_JP.UTF-8"), Lang::from_code("ja"));
|
||||
assert_eq!(lang_from_tag("ja"), Lang::from_code("ja"));
|
||||
// Spanish: every region (and Latin America as a whole) resolves to the one catalog.
|
||||
|
|
@ -51,7 +55,8 @@ fn candidates_walk_from_specific_to_general() {
|
|||
#[test]
|
||||
fn os_language_lists_are_parsed() {
|
||||
assert_eq!(first_supported("(\n \"zh-Hant-TW\",\n \"en-US\"\n)\n"), Some(ZH()));
|
||||
assert_eq!(first_supported("(\n \"de-DE\",\n \"en-US\"\n)\n"), Some(Lang::EN));
|
||||
assert_eq!(first_supported("(\n \"nl-NL\",\n \"en-US\"\n)\n"), Some(Lang::EN));
|
||||
assert_eq!(first_supported("(\n \"de-DE\",\n \"en-US\"\n)\n"), Some(de()));
|
||||
assert_eq!(first_supported("(\n \"fr-FR\",\n \"en-US\"\n)\n"), Lang::from_code("fr"));
|
||||
assert_eq!(first_supported("("), None);
|
||||
// Windows: `reg query HKCU\Control Panel\International /v LocaleName`.
|
||||
|
|
@ -435,9 +440,9 @@ fn cs() -> Lang {
|
|||
}
|
||||
|
||||
/// Languages whose catalogs leave [`MENU_KEEP_AS_IS`] in English.
|
||||
const KEEPS_MENU_NAMES: [&str; 8] = ["cs", "es", "fr", "it", "ja", "pt-br", "ru", "uk"];
|
||||
const KEEPS_MENU_NAMES: [&str; 9] = ["cs", "de", "es", "fr", "it", "ja", "pt-br", "ru", "uk"];
|
||||
|
||||
/// Menu labels the menu-complete catalogs (Czech, Spanish, Italian, Japanese, Brazilian Portuguese) show as they are: the product name, a format name, the built-in workspace
|
||||
/// Menu labels the menu-complete catalogs (Czech, German, Spanish, Italian, Japanese, Brazilian Portuguese) show as they are: the product name, a format name, the built-in workspace
|
||||
/// names and the perspective grid presets (names, shown untranslated wherever else they appear).
|
||||
/// Each language's own name in the Language menu is left alone too.
|
||||
const MENU_KEEP_AS_IS: &[&str] = &[
|
||||
|
|
@ -527,7 +532,7 @@ fn toggled_labels() -> Vec<String> {
|
|||
labels
|
||||
}
|
||||
|
||||
/// Czech, Spanish, Italian, Japanese and Brazilian Portuguese cover every menu label, the Show/Hide pairs
|
||||
/// Czech, German, Spanish, Italian, Japanese and Brazilian Portuguese cover every menu label, the Show/Hide pairs
|
||||
/// and the canvas context menu included (panels and dialogs not yet).
|
||||
#[test]
|
||||
fn menu_catalogs_translate_every_menu_label() {
|
||||
|
|
@ -565,9 +570,14 @@ fn menu_catalogs_translate_every_menu_label() {
|
|||
}
|
||||
assert_eq!(tr(Lang::from_code("pt-br").expect("pt-br"), "File"), "Arquivo");
|
||||
assert_eq!(tr(es(), "File"), "Archivo");
|
||||
assert_eq!(tr(de(), "File"), "Datei");
|
||||
assert_eq!(tr(it(), "Edit"), "Modifica");
|
||||
}
|
||||
|
||||
fn de() -> Lang {
|
||||
Lang::from_code("de").expect("de registered")
|
||||
}
|
||||
|
||||
fn es() -> Lang {
|
||||
Lang::from_code("es").expect("es registered")
|
||||
}
|
||||
|
|
@ -609,6 +619,36 @@ fn spanish_reads_as_spanish() {
|
|||
assert_eq!(trn(es(), 3, "{n} Layer", "{n} Layers"), "3 capas");
|
||||
}
|
||||
|
||||
/// German uses the vector-illustration vocabulary its users know, has two plural forms like
|
||||
/// English (zero takes the plural), translates the reason inside a message too, and tells the
|
||||
/// interface theme ("Erscheinungsbild") from the Appearance panel ("Aussehen").
|
||||
#[test]
|
||||
fn german_reads_as_german() {
|
||||
for (en, want) in [
|
||||
("Artboard Tool", "Zeichenflächen-Werkzeug"),
|
||||
("Swatches", "Farbfelder"),
|
||||
("Pathfinder", "Pathfinder"),
|
||||
("Stroke", "Kontur"),
|
||||
("Fill", "Fläche"),
|
||||
("Direct Selection Tool", "Direktauswahl-Werkzeug"),
|
||||
("Save As…", "Speichern unter…"),
|
||||
("Undo", "Rückgängig"),
|
||||
] {
|
||||
assert_eq!(tr(de(), en), want);
|
||||
}
|
||||
assert_eq!(trn(de(), 1, "{n} Layer", "{n} Layers"), "1 Ebene");
|
||||
assert_eq!(trn(de(), 0, "{n} Layer", "{n} Layers"), "0 Ebenen");
|
||||
assert_eq!(trn(de(), 3, "{n} Layer", "{n} Layers"), "3 Ebenen");
|
||||
assert_eq!(tr_ctx(de(), "axis", "Both"), "Beide");
|
||||
assert_eq!(tr(de(), "Appearance"), "Aussehen");
|
||||
assert_eq!(tr_ctx(de(), "theme", "Appearance"), "Erscheinungsbild");
|
||||
let nested = "command `object.group` is not available right now: nothing selected";
|
||||
assert_eq!(message(de(), nested), "der Befehl `object.group` ist derzeit nicht verfügbar: nichts ausgewählt");
|
||||
// Text styles are "…format" in German: the kind is joined to it ("Neues Zeichenformat erstellen").
|
||||
assert_eq!(fmt(tr(de(), "Create New {kind} Style"), &[("kind", tr(de(), "Character"))]), "Neues Zeichenformat erstellen");
|
||||
assert_eq!(fmt(tr(de(), "Show {kind} Brushes"), &[("kind", tr(de(), "Calligraphic"))]), "Kalligrafiepinsel einblenden");
|
||||
}
|
||||
|
||||
/// French uses the vector-illustration vocabulary its users know, puts zero in the singular (« 0
|
||||
/// calque »), and reads the same in the menus and in the panels.
|
||||
#[test]
|
||||
|
|
@ -769,7 +809,7 @@ fn russian_plurals_have_three_forms() {
|
|||
assert_eq!(forms, [2, 0, 1, 1, 2, 2, 0, 1, 2, 2, 0]);
|
||||
}
|
||||
|
||||
/// Czech, Spanish, French and Italian letters (and the punctuation their text uses) come from each family's own first font, not
|
||||
/// Czech, German, Spanish, French and Italian letters (and the punctuation their text uses) come from each family's own first font, not
|
||||
/// from a fallback further down the stack. (`has_glyph` can't tell: it counts characters of the
|
||||
/// face that draws missing glyphs, the first one, as missing.)
|
||||
#[test]
|
||||
|
|
@ -784,7 +824,7 @@ fn latin_script_glyphs_are_available_without_system_fonts() {
|
|||
let first = first.unwrap();
|
||||
let mut font = fonts.fonts.font(&family);
|
||||
let chars = font.characters();
|
||||
for ch in "áčďéěíňóřšťúůýžÁČĎÉĚÍŇÓŘŠŤÚŮÝŽ„“‚‘…–ñÑüÜ¿¡”àèìòùÀÈÌÒÙ«»’âêîôûçëïÿœæÂÊÎÔÛÇËÏŒÆ\u{a0}".chars()
|
||||
for ch in "áčďéěíňóřšťúůýžÁČĎÉĚÍŇÓŘŠŤÚŮÝŽ„“‚‘…–ñÑüÜ¿¡”àèìòùÀÈÌÒÙ«»’âêîôûçëïÿœæÂÊÎÔÛÇËÏŒÆäöÄÖß\u{a0}".chars()
|
||||
{
|
||||
assert!(chars.get(&ch).is_some_and(|fonts| fonts.contains(&first)), "{first} ({family:?}) has no {ch}");
|
||||
}
|
||||
|
|
@ -884,7 +924,7 @@ fn complete_languages_translate_every_message() {
|
|||
}
|
||||
|
||||
/// Languages whose catalogs cover every status and error message.
|
||||
const COMPLETE_MESSAGES: &[&str] = &["es", "fr", "it", "ja", "ru", "uk"];
|
||||
const COMPLETE_MESSAGES: &[&str] = &["de", "es", "fr", "it", "ja", "ru", "uk"];
|
||||
|
||||
/// Crates whose error and status messages reach the status bar.
|
||||
const MESSAGE_CRATES: &[&str] = &["ui-egui", "engine", "doc", "format", "svg", "pdf", "eps", "text", "plugins", "metafile", "cad", "trace"];
|
||||
|
|
|
|||
|
|
@ -166,7 +166,8 @@ always use the English ids and labels, so agents and scripts never see translate
|
|||
Languages shipped: English (`en`, the source), Traditional Chinese (`zh-hant`, complete, in the vocabulary used
|
||||
in Taiwan; `zh-TW`, `zh-HK`, `zh-MO` and `zh-Hant-*` locales all resolve to it), Simplified Chinese (`zh-hans`,
|
||||
complete, in the vocabulary used in mainland China; `zh-CN`, `zh-SG`, `zh-Hans-*` and a bare `zh` resolve to it,
|
||||
so the two scripts never mix), Japanese (`ja`, complete), Spanish (`es`, complete, in neutral
|
||||
so the two scripts never mix), Japanese (`ja`, complete), German (`de`, complete; `de-DE`, `de-AT`, `de-CH` and every
|
||||
other `de-*` locale resolve to it), Spanish (`es`, complete, in neutral
|
||||
international Spanish; every `es-*` locale such as `es-ES`, `es-MX`, `es-AR` or `es-419` resolves to it), French (`fr`,
|
||||
complete; `fr-FR`, `fr-BE`, `fr-CA`, `fr-CH` and every other `fr-*` locale resolve to it), Italian (`it`,
|
||||
complete; `it-IT`, `it-CH` and every other `it-*` locale resolve to it), Russian (`ru`, complete; every `ru-*`
|
||||
|
|
|
|||
Loading…
Reference in New Issue