i18n: add Italian (it) UI translation

Adds an Italian interface catalog (crates/ui-egui/src/i18n/it.rs) covering every
key of the shared table and of the Arabic table (1,430 strings), written from the
English source using ordinary Italian desktop-publishing vocabulary.

Registers "Italiano" in Edit > Interface Language, wires it into tr(), and extends
the i18n and menu tests (lookup, no duplicates, full key coverage, translated menu
titles, language switch frames).

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
insanityxoff
2026-10-09 18:36:55 +02:00
co-authored by Claude Opus 5.5
parent 66c7ce7e96
commit 4e333c652f
4 changed files with 1497 additions and 4 deletions
+1 -1
View File
@@ -103,7 +103,7 @@ DesignCraft aims at full Adobe InDesign parity — and to be better: faster, ope
- Transparency flattener (`file.exportPdf {flatten: high|medium|low|ppi}`, Edit ▸ Transparency Flattener Presets): each object involving transparency becomes an opaque image of its area over what's beneath it, so everything else stays vector (spreads with transparent parent items are rasterised whole).
- Video and sound (place .mp4/.mov/.webm/.mp3/.wav…, `media.options/get`, Media panel): media frames show a poster or placeholder on screen, in print and in PDF, and export as HTML5 `<video>`/`<audio>` in EPUB. Interactive PDF export (`file.exportPdf {media: true}`, File ▸ Export PDF (Interactive)) embeds each file and plays it in a Screen annotation through a rendition action (controls, loop, play on page load). No in-app playback.
- Right-to-left text (World-Ready basics): right-to-left runs are shaped right to left, lines are reordered by the Unicode Bidirectional Algorithm (matched against the reference implementation), Paragraph Direction (`type.para {direction}`, Paragraph panel, IDML ParagraphDirection, EPUB `dir`). Digits (`digits`: Default, Arabic, Hindi, Farsi, Native by language; Character panel for right-to-left text; IDML DigitsType) draw 0–9 in another script. Kashidas (`kashidas` paragraph attribute, on by default): justified Arabic lines stretch at one join per word (after seen/sad when possible, never inside lam–alef) with a drawn tatweel before word spaces widen. Carets follow the drawing: the caret before a right-to-left glyph sits at its right edge, arrow keys move as drawn (by words in the line's direction), clicks find the nearest drawn caret position and selections highlight the selected glyphs. Binding: Right to Left (Document Setup, `layout.documentSetup {binding}`, IDML PageBinding) makes page 1 a left page and lays spreads out right to left.
- Interface language (Edit ▸ Interface Language, `app.language`): German, French, Spanish, Japanese, Simplified Chinese and Arabic menu titles, common menu items and panel names (our own translations). Common dialog, panel and tooltip labels are also localized, with English fallback for untranslated strings. Arabic translations cover every key in the shared interface translation table, including menu commands and utility labels. The Type menu's Story Direction, Horizontal/Vertical, Tate-Chu-Yoko, Ruby… and Kenten are translated too. Arabic labels use Unicode bidi run ordering and contextual shaping; welcome and new-document form layouts follow RTL direction independently of document binding. CJK and Arabic glyphs come from [craft-fonts](https://github.com/storytold/craft-fonts) when built with `CRAFT_FONTS_DIR` (all releases): BIZ UDPGothic and Noto Sans CJK SC for the UI (Chinese forms first in the Chinese interface), Noto Sans Arabic, and Mincho for Japanese documents (the web build embeds BIZ UDPGothic only); otherwise from system fonts.
- Interface language (Edit ▸ Interface Language, `app.language`): German, French, Spanish, Italian, Japanese, Simplified Chinese and Arabic menu titles, common menu items and panel names (our own translations). Common dialog, panel and tooltip labels are also localized, with English fallback for untranslated strings. Arabic translations cover every key in the shared interface translation table, including menu commands and utility labels. The Type menu's Story Direction, Horizontal/Vertical, Tate-Chu-Yoko, Ruby… and Kenten are translated too. Arabic labels use Unicode bidi run ordering and contextual shaping; welcome and new-document form layouts follow RTL direction independently of document binding. CJK and Arabic glyphs come from [craft-fonts](https://github.com/storytold/craft-fonts) when built with `CRAFT_FONTS_DIR` (all releases): BIZ UDPGothic and Noto Sans CJK SC for the UI (Chinese forms first in the Chinese interface), Noto Sans Arabic, and Mincho for Japanese documents (the web build embeds BIZ UDPGothic only); otherwise from system fonts.
- Page transitions (`page.transition/transitions`, Page Transitions panel): twelve transition types with speed and direction per spread, written into exported PDFs as `/Trans` entries through an incremental update.
- PDF/X-4 export (`file.exportPdf {standard: "x4"}`): an output intent with our own Generic CMYK ICC profile (lut16 tables generated from the parametric press model), GTS_PDFXVersion in Info and XMP, and built-in PDF/X-4 checks reported as warnings. qpdf finds no errors in the result.
- EPS place: placed at its (high-resolution) bounding box and shown, printed and exported through its TIFF preview, or a placeholder when it has none. PostScript itself isn't interpreted.
+21 -1
View File
@@ -4,6 +4,7 @@
use std::{collections::HashMap, sync::OnceLock};
mod ar;
mod it;
mod pt_br;
/// Supported interface languages: (code, name in that language).
@@ -16,6 +17,7 @@ pub const LANGUAGES: &[(&str, &str)] = &[
("zh", "简体中文"),
("ar", "العربية"),
("pt-br", "Português (Brasil)"),
("it", "Italiano"),
];
/// English → [German, French, Spanish, Japanese, Simplified Chinese].
@@ -2733,12 +2735,16 @@ pub fn tr<'a>(lang: &str, s: &'a str) -> &'a str {
static TRANSLATIONS: OnceLock<HashMap<&'static str, [&'static str; 5]>> = OnceLock::new();
static ARABIC: OnceLock<HashMap<&'static str, &'static str>> = OnceLock::new();
static PORTUGUESE_BR: OnceLock<HashMap<&'static str, &'static str>> = OnceLock::new();
static ITALIAN: OnceLock<HashMap<&'static str, &'static str>> = OnceLock::new();
if lang == "ar" {
return ARABIC.get_or_init(|| ar::TABLE.iter().copied().collect()).get(s).copied().unwrap_or(s);
}
if lang == "pt-br" {
return PORTUGUESE_BR.get_or_init(|| pt_br::TABLE.iter().copied().collect()).get(s).copied().unwrap_or(s);
}
if lang == "it" {
return ITALIAN.get_or_init(|| it::TABLE.iter().copied().collect()).get(s).copied().unwrap_or(s);
}
let Some(c) = column(lang) else { return s };
TRANSLATIONS.get_or_init(|| TABLE.iter().copied().collect()).get(s).and_then(|row| row.get(c)).copied().unwrap_or(s)
}
@@ -2777,8 +2783,11 @@ mod tests {
for (key, expected) in pt_br::TABLE {
assert_eq!(tr("pt-br", key), *expected, "pt-br: {key}");
}
for (key, expected) in it::TABLE {
assert_eq!(tr("it", key), *expected, "it: {key}");
}
let unknown = String::from("A user-defined untranslated label");
for lang in ["ar", "pt-br", "zh", "ja", "de", "fr", "es", "", "unknown"] {
for lang in ["ar", "pt-br", "it", "zh", "ja", "de", "fr", "es", "", "unknown"] {
assert!(std::ptr::eq(tr(lang, &unknown), unknown.as_str()));
}
}
@@ -2794,6 +2803,9 @@ mod tests {
assert_eq!(tr("pt-br", "File"), "Arquivo");
assert_eq!(tr("pt-br", "New Document…"), "Novo documento…");
assert_eq!(tr("pt-br", "Unknown label"), "Unknown label");
assert_eq!(tr("it", "Edit"), "Modifica");
assert_eq!(tr("it", "New Document…"), "Nuovo documento…");
assert_eq!(tr("it", "Unknown label"), "Unknown label");
assert!(is_rtl("ar"));
for (i, (en, translation)) in ar::TABLE.iter().enumerate() {
assert!(ar::TABLE[..i].iter().all(|(key, _)| key != en), "duplicate {en}");
@@ -2803,6 +2815,14 @@ mod tests {
assert!(pt_br::TABLE[..i].iter().all(|(key, _)| key != en), "duplicate {en}");
assert!(!translation.is_empty(), "{en}");
}
for (i, (en, translation)) in it::TABLE.iter().enumerate() {
assert!(it::TABLE[..i].iter().all(|(key, _)| key != en), "duplicate {en}");
assert!(!translation.is_empty(), "{en}");
}
// Italian covers every key of the shared table and of the Arabic one.
for en in TABLE.iter().map(|(en, _)| *en).chain(ar::TABLE.iter().map(|(en, _)| *en)) {
assert!(it::TABLE.iter().any(|(key, _)| *key == en), "it: missing {en}");
}
// Every row is unique and complete.
for (i, (en, t)) in TABLE.iter().enumerate() {
assert!(TABLE[..i].iter().all(|(e, _)| e != en), "duplicate {en}");
File diff suppressed because it is too large Load Diff
+14 -2
View File
@@ -38,7 +38,7 @@ pub const UI_COMMANDS: &[(&str, &str, Option<&str>, &str)] = &[
"app.language",
"Interface Language",
None,
"{lang: \"\"|de|fr|es|ja|zh|ar|pt-br} — menus and panel names (the macOS menu bar follows on the next launch)",
"{lang: \"\"|de|fr|es|ja|zh|ar|pt-br|it} — menus and panel names (the macOS menu bar follows on the next launch)",
),
(
"app.flattener",
@@ -234,6 +234,7 @@ pub const MENUS: &[(&str, &[&str])] = &[
"ui:app.language|简体中文|{\"lang\": \"zh\"}",
"ui:app.language|العربية|{\"lang\": \"ar\"}",
"ui:app.language|Português (Brasil)|{\"lang\": \"pt-br\"}",
"ui:app.language|Italiano|{\"lang\": \"it\"}",
"<",
">Transparency Flattener Presets",
"ui:app.flattener|None (keep transparency)|{\"preset\": \"\"}",
@@ -2139,6 +2140,17 @@ mod tests {
assert_ne!(crate::i18n::tr("pt-br", title), title, "{title}");
}
}
run_ui(&mut app, "app.language", &json!({"lang": "it"})).unwrap().unwrap();
assert_eq!(app.ui.language, "it");
assert_eq!(checked(&app, "app.language", &json!({"lang": "it"})), Some(true));
for (title, _) in menu_tree() {
// "File" and "Layout" are the usual Italian menu names, so they stay identical
// to English; every other title must be translated.
if title != "File" && title != "Layout" {
assert_ne!(crate::i18n::tr("it", title), title, "{title}");
}
}
}
#[test]
@@ -2168,7 +2180,7 @@ mod tests {
crate::dialogs::confirm(&mut app).unwrap();
frame(&mut app);
assert_eq!(app.session.documents().len(), 1);
for lang in ["zh", "", "ar", "pt-br"] {
for lang in ["zh", "", "ar", "pt-br", "it"] {
app.run("app.language", json!({"lang": lang})).unwrap();
frame(&mut app);
}