Merge PR #2227: M14: add Catalan interface and date localization

crates/engine/src/dates.rs, crates/ui-egui/src/i18n/mod.rs: both sides' language rows, arrays sized 27.
crates/ui-egui/tests/fonts.rs: both catalog lists; Catalan added to main's comment.
ATTRIBUTION.toml/.md: both sides' assets; hashes and counts regenerated after all merges.
ROADMAP.md: both log lines.
docs/localization-parity.md: main's rows plus the ca row; 27 interface languages; both revision rows.
docs/localization.md: main's text with Català, the ca locales, code and catalog paragraph added.
This commit is contained in:
aiongg
2026-10-22 01:52:24 +00:00
10 changed files with 2827 additions and 8 deletions
+1
View File
@@ -286,6 +286,7 @@ Every asset PdfCraft includes, bundles or uses to build its published material,
| `assets/icons/pilcrow.svg` | Lucide icon "pilcrow" | Lucide Contributors (portions Cole Bemis, Feather) | ISC | https://github.com/lucide-icons/lucide (npm lucide-static 1.49.0, icons/pilcrow.svg) | UI icon, embedded via crates/ui-egui/src/icon_data.rs | | `assets/icons/pilcrow.svg` | Lucide icon "pilcrow" | Lucide Contributors (portions Cole Bemis, Feather) | ISC | https://github.com/lucide-icons/lucide (npm lucide-static 1.49.0, icons/pilcrow.svg) | UI icon, embedded via crates/ui-egui/src/icon_data.rs |
| `assets/icons/move-horizontal.svg` | Lucide icon "move-horizontal" | Lucide Contributors (portions Cole Bemis, Feather) | ISC | https://github.com/lucide-icons/lucide (npm lucide-static 1.49.0, icons/move-horizontal.svg) | UI icon, embedded via crates/ui-egui/src/icon_data.rs | | `assets/icons/move-horizontal.svg` | Lucide icon "move-horizontal" | Lucide Contributors (portions Cole Bemis, Feather) | ISC | https://github.com/lucide-icons/lucide (npm lucide-static 1.49.0, icons/move-horizontal.svg) | UI icon, embedded via crates/ui-egui/src/icon_data.rs |
| `crates/ui-egui/src/i18n/sq.tsv` | Albanian interface translation catalog | PdfCraft contributors | MIT OR Apache-2.0 | Contributor-original: clean-room translations of PdfCraft's English UI labels, no proprietary localisation resources | Albanian UI strings, compiled in by crates/ui-egui/src/i18n/mod.rs | | `crates/ui-egui/src/i18n/sq.tsv` | Albanian interface translation catalog | PdfCraft contributors | MIT OR Apache-2.0 | Contributor-original: clean-room translations of PdfCraft's English UI labels, no proprietary localisation resources | Albanian UI strings, compiled in by crates/ui-egui/src/i18n/mod.rs |
| `crates/ui-egui/src/i18n/ca.tsv` | Catalan interface translation catalog | PdfCraft contributors | MIT OR Apache-2.0 | Contributor-original: clean-room translations of PdfCraft's English UI labels, written from their meaning; no proprietary localisation resources | Catalan UI strings, compiled in by crates/ui-egui/src/i18n/mod.rs |
## Compiled in through dependencies (6) ## Compiled in through dependencies (6)
+11
View File
@@ -3453,3 +3453,14 @@ licence_file = "LICENSE-MIT"
kind = "translation" kind = "translation"
usage = "Thai UI strings, compiled in by crates/ui-egui/src/i18n/mod.rs" usage = "Thai UI strings, compiled in by crates/ui-egui/src/i18n/mod.rs"
sha256 = "a9e20f819001357ea548b3d05ea0a6484a228c3079effdf7d33f437f6207f11f" sha256 = "a9e20f819001357ea548b3d05ea0a6484a228c3079effdf7d33f437f6207f11f"
[[asset]]
path = "crates/ui-egui/src/i18n/ca.tsv"
title = "Catalan interface translation catalog"
author = "PdfCraft contributors"
source = "Contributor-original: clean-room translations of PdfCraft's English UI labels, written from their meaning; no proprietary localisation resources"
licence = "MIT OR Apache-2.0"
licence_file = "LICENSE-MIT"
kind = "translation"
usage = "Catalan UI strings, compiled in by crates/ui-egui/src/i18n/mod.rs"
sha256 = "0988fff524d2b76e21a977d078e92da67ea927c9422ac5b67a9ac476c0f441d5"
+1
View File
@@ -272,6 +272,7 @@ Ranked; detail and the full milestone table in [docs/roadmap.md](docs/roadmap.md
- **2026-10-14 (M9, #1787):** Changes after signing are judged by the role an object held when signed for a page's resources too: a rewritten font, XObject, graphics state or resources dictionary is page content however its new version is labelled, so a `/FT` or `/Subtype /Widget` added by the attacker no longer reads as a permitted form fill. A category dictionary that only gained a name is judged as before. - **2026-10-14 (M9, #1787):** Changes after signing are judged by the role an object held when signed for a page's resources too: a rewritten font, XObject, graphics state or resources dictionary is page content however its new version is labelled, so a `/FT` or `/Subtype /Widget` added by the attacker no longer reads as a permitted form fill. A category dictionary that only gained a name is judged as before.
- **2026-10-15 (community, M14, Polish signature panel):** A Polish signature panel listed its details in English: the catalog lacked every signature wording, the changes after signing they name and 34 UI literals. All 101 are translated; `polish_covers_ui_literals` and `polish_says_what_a_signature_says` keep them so. Estimates unchanged. - **2026-10-15 (community, M14, Polish signature panel):** A Polish signature panel listed its details in English: the catalog lacked every signature wording, the changes after signing they name and 34 UI literals. All 101 are translated; `polish_covers_ui_literals` and `polish_says_what_a_signature_says` keep them so. Estimates unchanged.
- **2026-10-14 (M14, Finnish localization):** Added Finnish as another interface and date language, with a 2,674-entry catalog covering every key of the other bundled catalogs and two-form plurals (partitive after numbers other than 1). Calendar dates use partitive month names (`14. lokakuuta 2026`). Coverage tests check commands/tools, scanned UI literals and bundled catalog keys. AI-assisted; native proofreading pending. - **2026-10-14 (M14, Finnish localization):** Added Finnish as another interface and date language, with a 2,674-entry catalog covering every key of the other bundled catalogs and two-form plurals (partitive after numbers other than 1). Calendar dates use partitive month names (`14. lokakuuta 2026`). Coverage tests check commands/tools, scanned UI literals and bundled catalog keys. AI-assisted; native proofreading pending.
- **2026-10-14 (M14, Catalan localization):** Added Catalan as another interface and date language, with a 2,592-entry clean-room catalog, one/other plurals and coverage tests for commands, tools, UI literals and every key in the other catalogs. A native Catalan speaker (the contributor) has reviewed the translation. Estimates unchanged.
- **2026-10-13 (M3, native-opening candidate):** Chosen tools survive preparation, conversion and password retries; startup intake waits for pending preparation. Regression tests cover completion, cancellation and document identity. Estimates unchanged. - **2026-10-13 (M3, native-opening candidate):** Chosen tools survive preparation, conversion and password retries; startup intake waits for pending preparation. Regression tests cover completion, cancellation and document identity. Estimates unchanged.
- **2026-10-13 (community, M3, #531 touchpad momentum):** On Wayland a two-finger swipe keeps gliding after the fingers lift and slows to a stop, measured from the gesture's last 0.1 s and decaying with a 0.35 s time constant through the #759 1:1 path. Any wheel input, click, key, zoom or edge ends it (fingers resting on the touchpad don't yet: the vendored winit binds no Wayland hold gestures); single-page paging, macOS (which sends its own momentum), the web, and Animation effects Off never glide. Unit tests for the physics and two headless view tests. Estimates unchanged. - **2026-10-13 (community, M3, #531 touchpad momentum):** On Wayland a two-finger swipe keeps gliding after the fingers lift and slows to a stop, measured from the gesture's last 0.1 s and decaying with a 0.35 s time constant through the #759 1:1 path. Any wheel input, click, key, zoom or edge ends it (fingers resting on the touchpad don't yet: the vendored winit binds no Wayland hold gestures); single-page paging, macOS (which sends its own momentum), the web, and Animation effects Off never glide. Unit tests for the physics and two headless view tests. Estimates unchanged.
- **2026-10-13 (M3, native-open admission):** Later prepared replies stay queued while a modal or native picker owns the foreground. Picker completion, cancellation and abandonment wake retained opens. Regressions cover recipient ownership, exactly-once admission, source/history preservation and one-shot repaint requests. Estimates unchanged. - **2026-10-13 (M3, native-open admission):** Later prepared replies stay queued while a modal or native picker owns the foreground. Picker completion, cancellation and abandonment wake retained opens. Regressions cover recipient ownership, exactly-once admission, source/history preservation and one-shot repaint requests. Estimates unchanged.
+35 -1
View File
@@ -59,7 +59,7 @@ const AR_DAYS: [&str; 7] = ["الأحد", "الاثنين", "الثلاثاء",
/// The languages month and weekday names can be written in (Preferences ▸ Date format ▸ Language): /// The languages month and weekday names can be written in (Preferences ▸ Date format ▸ Language):
/// the interface languages, in the same order and with the same names. /// the interface languages, in the same order and with the same names.
pub static DATE_LANGUAGES: [DateLanguage; 26] = [ pub static DATE_LANGUAGES: [DateLanguage; 27] = [
DateLanguage { DateLanguage {
code: "en", code: "en",
name: "English", name: "English",
@@ -455,6 +455,29 @@ pub static DATE_LANGUAGES: [DateLanguage; 26] = [
days: ["วันอาทิตย์", "วันจันทร์", "วันอังคาร", "วันพุธ", "วันพฤหัสบดี", "วันศุกร์", "วันเสาร์"], days: ["วันอาทิตย์", "วันจันทร์", "วันอังคาร", "วันพุธ", "วันพฤหัสบดี", "วันศุกร์", "วันเสาร์"],
days_short: ["อา.", "จ.", "อ.", "พ.", "พฤ.", "ศ.", "ส."], days_short: ["อา.", "จ.", "อ.", "พ.", "พฤ.", "ศ.", "ส."],
}, },
DateLanguage {
code: "ca",
name: "Català",
months: ["gener", "febrer", "març", "abril", "maig", "juny", "juliol", "agost", "setembre", "octubre", "novembre", "desembre"],
// After a day the month carries its own preposition, elided before a vowel: `7 d'abril`.
months_with_day: Some([
"de gener",
"de febrer",
"de març",
"d'abril",
"de maig",
"de juny",
"de juliol",
"d'agost",
"de setembre",
"d'octubre",
"de novembre",
"de desembre",
]),
months_short: ["gen.", "febr.", "març", "abr.", "maig", "juny", "jul.", "ag.", "set.", "oct.", "nov.", "des."],
days: ["diumenge", "dilluns", "dimarts", "dimecres", "dijous", "divendres", "dissabte"],
days_short: ["dg.", "dl.", "dt.", "dx.", "dj.", "dv.", "ds."],
},
]; ];
/// The date language for `code` (any case), or `None` when there's none. /// The date language for `code` (any case), or `None` when there's none.
@@ -779,6 +802,17 @@ mod tests {
assert_eq!(in_lang("dddd, mmmm", "cs"), "sobota, březen"); assert_eq!(in_lang("dddd, mmmm", "cs"), "sobota, březen");
} }
#[test]
fn catalan_months_elide_the_preposition_after_a_day() {
assert_eq!(in_lang("dddd d mmmm \\de yyyy", "ca"), "dissabte 7 de març de 2026");
assert_eq!(in_lang("mmmm yyyy", "ca"), "març 2026");
let ca = date_language("ca").unwrap();
assert_eq!(format_day("d mmmm \\de yyyy", (2026, 4, 23), ca), "23 d'abril de 2026");
assert_eq!(format_day("d mmmm", (2026, 8, 1), ca), "1 d'agost");
assert_eq!(format_day("d mmmm", (2026, 10, 8), ca), "8 d'octubre");
assert_eq!(format_day("mmmm", (2026, 10, 8), ca), "octubre", "no preposition without a day");
}
#[test] #[test]
fn the_session_writes_names_in_its_date_language() { fn the_session_writes_names_in_its_date_language() {
let mut s = Session::new().with_clock(|| 1_700_000_000); let mut s = Session::new().with_clock(|| 1_700_000_000);
File diff suppressed because it is too large Load Diff
+86 -1
View File
@@ -147,7 +147,7 @@ fn plural_sl(n: u64) -> usize {
} }
/// The registry. English first: it is the fallback and the source language. /// The registry. English first: it is the fallback and the source language.
pub static LANGUAGES: [LangInfo; 26] = [ pub static LANGUAGES: [LangInfo; 27] = [
LangInfo { code: "en", name: "English", source: "", plural: plural_one_other, rtl: false, catalog: OnceLock::new() }, LangInfo { code: "en", name: "English", source: "", plural: plural_one_other, rtl: false, catalog: OnceLock::new() },
LangInfo { code: "ja", name: "日本語", source: include_str!("ja.tsv"), plural: plural_none, rtl: false, catalog: OnceLock::new() }, LangInfo { code: "ja", name: "日本語", source: include_str!("ja.tsv"), plural: plural_none, rtl: false, catalog: OnceLock::new() },
// Simplified Chinese; `zh`, `zh-CN`, `zh-SG` and `zh-Hans-*` locales resolve here (see `candidates`). // Simplified Chinese; `zh`, `zh-CN`, `zh-SG` and `zh-Hans-*` locales resolve here (see `candidates`).
@@ -217,6 +217,8 @@ pub static LANGUAGES: [LangInfo; 26] = [
LangInfo { code: "sq", name: "Shqip", source: include_str!("sq.tsv"), plural: plural_one_other, rtl: false, catalog: OnceLock::new() }, LangInfo { code: "sq", name: "Shqip", source: include_str!("sq.tsv"), plural: plural_one_other, rtl: false, catalog: OnceLock::new() },
// Thai; every `th-*` locale (`th-TH`, `th_TH.UTF-8`) resolves here. No grammatical plural. // Thai; every `th-*` locale (`th-TH`, `th_TH.UTF-8`) resolves here. No grammatical plural.
LangInfo { code: "th", name: "ไทย", source: include_str!("th.tsv"), plural: plural_none, rtl: false, catalog: OnceLock::new() }, LangInfo { code: "th", name: "ไทย", source: include_str!("th.tsv"), plural: plural_none, rtl: false, catalog: OnceLock::new() },
// Catalan; every `ca-*` locale (`ca-ES`, `ca_ES.UTF-8`, `ca_ES@valencia`) resolves here.
LangInfo { code: "ca", name: "Català", source: include_str!("ca.tsv"), plural: plural_one_other, rtl: false, catalog: OnceLock::new() },
]; ];
impl LangInfo { impl LangInfo {
@@ -2853,6 +2855,89 @@ mod tests {
assert!(unchanged.is_empty(), "unapproved English Vietnamese catalog entries: {unchanged:#?}"); assert!(unchanged.is_empty(), "unapproved English Vietnamese catalog entries: {unchanged:#?}");
} }
#[test]
fn catalan_is_registered_and_persists() {
let ca = Lang::from_code("ca").expect("ca registered");
assert_eq!(ca.name(), "Català");
assert_eq!(normalize_pref("CA"), Some("ca"));
for tag in ["ca", "ca-ES", "ca_ES.UTF-8", "CA-es", "ca_ES@valencia"] {
assert_eq!(lang_from_tag(tag), Some(ca), "{tag}");
}
assert_eq!(first_supported("ca-ES\r\nen-US"), Some(ca));
assert_eq!(tr(ca, "File"), "Fitxer");
assert_eq!(tr(ca, "Save as…"), "Anomena i desa…");
assert_eq!(tr(ca, "Cancel"), "Cancel·la");
assert_eq!(tr_ctx(ca, "comment menu", "Edit"), "Edita");
assert_eq!(tr_ctx(ca, "signature pad", "Type"), "Escriu");
// one (1), other (0, 2+), as in English
assert_eq!((0..=3).map(|n| (ca.0.plural)(n)).collect::<Vec<_>>(), [1, 0, 1, 1]);
assert_eq!(trn(ca, 1, "{n} page", "{n} pages"), "1 pàgina");
assert_eq!(trn(ca, 3, "{n} page", "{n} pages"), "3 pàgines");
let mut app = crate::PdfCraftApp::default();
app.set_option("language", "CA").unwrap();
assert_eq!(app.language, "ca");
let mut restored = crate::PdfCraftApp::default();
restored.restore(&app.persist());
assert_eq!(restored.language, "ca");
assert!(restored.set_option("language", "xx").is_err());
assert_eq!(restored.language, "ca");
}
/// Catalan translates every registered command, menu, All tools group, section and item.
#[test]
fn catalan_covers_commands_and_catalogue() {
let ca = Lang::from_code("ca").expect("ca registered");
for command in pdfcraft_engine::commands::COMMANDS {
assert!(has(ca, command.label), "missing command: {}", command.label);
if let Some(menu) = command.menu {
assert!(has(ca, menu), "missing menu: {menu}");
}
}
for group in pdfcraft_engine::catalog::TOOL_GROUPS {
assert!(has(ca, group.label), "missing group: {}", group.label);
for section in group.sections {
assert!(has(ca, section.title), "missing section: {}", section.title);
for item in section.items {
assert!(has(ca, item.label), "missing item: {}", item.label);
}
}
}
}
/// Catalan translates every `tl!("…")` literal in the UI source, so a new string can't ship in
/// English by accident.
#[test]
fn catalan_covers_ui_literals() {
let ca = Lang::from_code("ca").expect("ca registered");
let literals = ui_literals();
assert!(literals.len() > 900, "source scan found only {} literals", literals.len());
let missing: Vec<_> = literals.iter().filter(|label| !has(ca, label)).collect();
assert!(missing.is_empty(), "untranslated Catalan UI literals: {missing:#?}");
}
/// Catalan has every key the other bundled catalogs have (English keys, not their translations).
#[test]
fn catalan_covers_bundled_catalog_keys() {
let ca = Lang::from_code("ca").expect("ca registered");
let mut missing = std::collections::BTreeSet::new();
for reference in LANGUAGES.iter().filter(|language| language.code != "ca") {
let (entries, errors) = catalog::parse_entries(reference.source, reference.plural_forms());
assert!(errors.is_empty(), "{}: invalid reference catalog: {errors:?}", reference.code);
for entry in entries {
let translated = match entry.context.as_str() {
"" => ca.catalog().plain(&entry.source),
"@id" => ca.catalog().id(&entry.source),
"@plural" => entry.source.split_once('|').and_then(|(one, other)| ca.catalog().plural(one, other, 0)),
context => ca.catalog().contextual(context, &entry.source),
};
if translated.is_none() {
missing.insert((entry.context, entry.source));
}
}
}
assert!(missing.is_empty(), "Catalan misses bundled catalog keys: {missing:#?}");
}
#[test] #[test]
fn slovene_is_registered_and_persists() { fn slovene_is_registered_and_persists() {
let sl = Lang::from_code("sl").expect("sl registered"); let sl = Lang::from_code("sl").expect("sl registered");
+2 -1
View File
@@ -290,7 +290,7 @@ fn ui_fonts_work_without_craft_fonts() {
theme::install_fonts(&ctx); theme::install_fonts(&ctx);
} }
/// Latin- and Cyrillic-script catalogs (Czech, Brazilian Portuguese, German, Spanish, French, Russian, Bulgarian, Hungarian, Ukrainian, Italian, Slovene, Albanian) are drawn entirely by the app's own faces /// Latin- and Cyrillic-script catalogs (Czech, Brazilian Portuguese, German, Spanish, French, Russian, Bulgarian, Hungarian, Ukrainian, Italian, Slovene, Albanian, Catalan) are drawn entirely by the app's own faces
/// (Inter, JetBrains Mono with Inter fallback): no letter falls through to egui's defaults or a CJK fallback. (egui's /// (Inter, JetBrains Mono with Inter fallback): no letter falls through to egui's defaults or a CJK fallback. (egui's
/// `has_glyphs` can't answer this: with only the primary face it is also the replacement face.) /// `has_glyphs` can't answer this: with only the primary face it is also the replacement face.)
#[test] #[test]
@@ -320,6 +320,7 @@ fn primary_ui_fonts_cover_latin_and_cyrillic_catalogs() {
("vi", include_str!("../src/i18n/vi.tsv")), ("vi", include_str!("../src/i18n/vi.tsv")),
("fi", include_str!("../src/i18n/fi.tsv")), ("fi", include_str!("../src/i18n/fi.tsv")),
("sq", include_str!("../src/i18n/sq.tsv")), ("sq", include_str!("../src/i18n/sq.tsv")),
("ca", include_str!("../src/i18n/ca.tsv")),
] { ] {
let mut text = String::from("áčďéěíňóřšťúůýžÁČĎÉĚÍŇÓŘŠŤÚŮÝŽãõçâêôàÃÕÇÂÊÔÀñÑüÜäÄöÖőűß„“¿¡«»…ґҐєЄіІїЇәғқңөұүһӘҒҚҢӨҰҮҺ"); let mut text = String::from("áčďéěíňóřšťúůýžÁČĎÉĚÍŇÓŘŠŤÚŮÝŽãõçâêôàÃÕÇÂÊÔÀñÑüÜäÄöÖőűß„“¿¡«»…ґҐєЄіІїЇәғқңөұүһӘҒҚҢӨҰҮҺ");
for line in catalog.lines().filter(|l| !l.starts_with('#')) { for line in catalog.lines().filter(|l| !l.starts_with('#')) {
+4 -2
View File
@@ -77,9 +77,10 @@ help/docs polish (catalog and script work are already done), not missing UI stri
| Lithuanian | `lt` | 2,674 (100% of current key union) | partial; community native-speaker review ([#1401](https://github.com/storytold/pdfcraft/pull/1401)) | no | | Lithuanian | `lt` | 2,674 (100% of current key union) | partial; community native-speaker review ([#1401](https://github.com/storytold/pdfcraft/pull/1401)) | no |
| Finnish | `fi` | 2,674 entries | partial; AI-assisted, native proofreading pending | yes | | Finnish | `fi` | 2,674 entries | partial; AI-assisted, native proofreading pending | yes |
| Albanian | `sq` | 2,344 entries | partial; not yet reviewed by a native speaker | no | | Albanian | `sq` | 2,344 entries | partial; not yet reviewed by a native speaker | no |
| Catalan | `ca` | 2,592 entries | partial: no translated help, engine error details in English; native-speaker review by the contributor | no |
**15 other languages** ship (24 catalogs plus English = **25** interface languages in Preferences). **15 other languages** ship (26 catalogs plus English = **27** interface languages in Preferences).
The entry counts without percentages were measured on 2026-10-12–13; they are not percentages of the older 2,143-key snapshot above. Their presence does not imply a detailed linguistic or runtime review. The entry counts without percentages were measured on 2026-10-12–14; they are not percentages of the older 2,143-key snapshot above. Their presence does not imply a detailed linguistic or runtime review.
Acrobat languages PdfCraft lacks: Danish, Dutch, Norwegian, Swedish, Hebrew. Vietnamese is shipped here but not by Acrobat. Acrobat languages PdfCraft lacks: Danish, Dutch, Norwegian, Swedish, Hebrew. Vietnamese is shipped here but not by Acrobat.
## Revision history ## Revision history
@@ -93,5 +94,6 @@ Acrobat languages PdfCraft lacks: Danish, Dutch, Norwegian, Swedish, Hebrew. Vie
| 2026-10-14 | minor | Italian: 2,528 entries after adding the 201 keys the other catalogs had and the code looks up | | 2026-10-14 | minor | Italian: 2,528 entries after adding the 201 keys the other catalogs had and the code looks up |
| 2026-10-14 | minor | Added Finnish (2,674 entries, AI-assisted) | | 2026-10-14 | minor | Added Finnish (2,674 entries, AI-assisted) |
| 2026-10-12 | minor | Added Albanian (`sq`): catalog, date names, Preferences entry | | 2026-10-12 | minor | Added Albanian (`sq`): catalog, date names, Preferences entry |
| 2026-10-14 | minor | Added Catalan (`ca`, 2,592 entries, measured 2026-10-14); retained existing language assessments |
| 2026-10-12 | minor | Added current counts for Kazakh, Polish, Slovenian, Greek, Turkish and Lithuanian; retained existing language assessments and the original percentage snapshot | | 2026-10-12 | minor | Added current counts for Kazakh, Polish, Slovenian, Greek, Turkish and Lithuanian; retained existing language assessments and the original percentage snapshot |
| 2026-10-10 | major | Created. Catalog counts measured from `crates/ui-egui/src/i18n/*.tsv` against the 2,143-key union; Acrobat's localizations from its bundle's `.lproj` and Sequences folder names | | 2026-10-10 | major | Created. Catalog counts measured from `crates/ui-egui/src/i18n/*.tsv` against the 2,143-key union; Acrobat's localizations from its bundle's `.lproj` and Sequences folder names |
+5 -3
View File
@@ -1,10 +1,10 @@
# Interface language # Interface language
Choose **Menu > Edit > Preferences…** (Command-comma on macOS, Ctrl-comma elsewhere) **> Interface language** and select **Auto**, **English**, **日本語**, **简体中文**, **繁體中文**, **Čeština**, **Português (Brasil)**, **Deutsch**, **Español**, **Français**, **Русский**, **Қазақша**, **Български**, **తెలుగు**, **Magyar**, **Українська**, **Italiano**, **العربية**, **Polski**, **Slovenščina**, **Ελληνικά**, **Türkçe**, **Lietuvių**, **Tiếng Việt**, **Suomi** or **Shqip**. The change applies immediately and persists between launches. Command ids, document contents and file names are unchanged. Choose **Menu > Edit > Preferences…** (Command-comma on macOS, Ctrl-comma elsewhere) **> Interface language** and select **Auto**, **English**, **日本語**, **简体中文**, **繁體中文**, **Čeština**, **Português (Brasil)**, **Deutsch**, **Español**, **Français**, **Русский**, **Қазақша**, **Български**, **తెలుగు**, **Magyar**, **Українська**, **Italiano**, **العربية**, **Polski**, **Slovenščina**, **Ελληνικά**, **Türkçe**, **Lietuvių**, **Tiếng Việt**, **Suomi**, **Shqip** or **Català**. The change applies immediately and persists between launches. Command ids, document contents and file names are unchanged.
Displayed keyboard shortcuts use the same active interface language. For example, German shows `Strg+K` and `Strg+Umschalt+S` in the toolbar, menus, command palette, tooltips and Keyboard shortcuts dialog; macOS keeps its modifier symbols (`⌘`, `⇧`, `⌃`). Key names have their own `key` catalog context, so the Delete key can read `Entf` while the command remains `Löschen`. A missing key-name entry falls back to the English key name. Actual bindings and machine-facing command ids remain stable. Displayed keyboard shortcuts use the same active interface language. For example, German shows `Strg+K` and `Strg+Umschalt+S` in the toolbar, menus, command palette, tooltips and Keyboard shortcuts dialog; macOS keeps its modifier symbols (`⌘`, `⇧`, `⌃`). Key names have their own `key` catalog context, so the Delete key can read `Entf` while the command remains `Löschen`. A missing key-name entry falls back to the English key name. Actual bindings and machine-facing command ids remain stable.
**Auto** (the default) follows the system language: `LC_ALL`, `LC_MESSAGES` or `LANG`, then the preferred-languages list on macOS. Any Portuguese locale (`pt_BR`, `pt_PT`) uses the Brazilian catalog, any French locale (`fr`, `fr_FR`, `fr_CA`, `fr_BE`) the French one, any German locale (`de`, `de_DE`, `de_AT`, `de_CH`) the German one, and Chinese locales for Taiwan, Hong Kong and Macau (`zh_TW`, `zh_HK`, `zh_MO`, `zh-Hant`) the Traditional Chinese one; other Chinese locales (`zh`, `zh_CN`, `zh_SG`, `zh-Hans`) the Simplified Chinese one. Telugu locales (`te`, `te_IN`) use the Telugu catalog, Bulgarian locales (`bg`, `bg_BG`) the Bulgarian one, and Hungarian locales (`hu`, `hu_HU`) the Hungarian one, and Ukrainian locales (`uk`, `uk_UA`) the Ukrainian one, and Italian locales (`it`, `it_IT`, `it_CH`) the Italian one, and Arabic locales (`ar`, `ar_MA`, `ar_EG`, `ar_SA`, …) the Arabic one, and Turkish locales (`tr`, `tr_TR`) the Turkish one, and Slovenian locales (`sl`, `sl_SI`) the Slovenian one, Vietnamese locales (`vi`, `vi_VN`) the Vietnamese one, and Albanian locales (`sq`, `sq_AL`, `sq_XK`, …) the Albanian one. A system language without a catalog shows English. On Windows, **Auto** uses the Windows display languages, in preference order, when no supported locale is selected by those environment variables. Neutral `C`/`POSIX` locale values (including `C.UTF-8`) inherited from a launcher do not override the Windows display language; explicitly named supported locales such as `fr_FR.UTF-8` still do. The query asks Windows directly (`GetUserPreferredUILanguages`, through the `sys-locale` crate) without starting a process, and is cached for the process. If detection fails or the display language has no catalog, PdfCraft shows English. **Auto** (the default) follows the system language: `LC_ALL`, `LC_MESSAGES` or `LANG`, then the preferred-languages list on macOS. Any Portuguese locale (`pt_BR`, `pt_PT`) uses the Brazilian catalog, any French locale (`fr`, `fr_FR`, `fr_CA`, `fr_BE`) the French one, any German locale (`de`, `de_DE`, `de_AT`, `de_CH`) the German one, and Chinese locales for Taiwan, Hong Kong and Macau (`zh_TW`, `zh_HK`, `zh_MO`, `zh-Hant`) the Traditional Chinese one; other Chinese locales (`zh`, `zh_CN`, `zh_SG`, `zh-Hans`) the Simplified Chinese one. Telugu locales (`te`, `te_IN`) use the Telugu catalog, Bulgarian locales (`bg`, `bg_BG`) the Bulgarian one, and Hungarian locales (`hu`, `hu_HU`) the Hungarian one, and Ukrainian locales (`uk`, `uk_UA`) the Ukrainian one, and Italian locales (`it`, `it_IT`, `it_CH`) the Italian one, and Arabic locales (`ar`, `ar_MA`, `ar_EG`, `ar_SA`, …) the Arabic one, and Turkish locales (`tr`, `tr_TR`) the Turkish one, and Slovenian locales (`sl`, `sl_SI`) the Slovenian one, Vietnamese locales (`vi`, `vi_VN`) the Vietnamese one, Albanian locales (`sq`, `sq_AL`, `sq_XK`, …) the Albanian one, and Catalan locales (`ca`, `ca_ES`, `ca_AD`, `ca_ES@valencia`, …) the Catalan one. A system language without a catalog shows English. On Windows, **Auto** uses the Windows display languages, in preference order, when no supported locale is selected by those environment variables. Neutral `C`/`POSIX` locale values (including `C.UTF-8`) inherited from a launcher do not override the Windows display language; explicitly named supported locales such as `fr_FR.UTF-8` still do. The query asks Windows directly (`GetUserPreferredUILanguages`, through the `sys-locale` crate) without starting a process, and is cached for the process. If detection fails or the display language has no catalog, PdfCraft shows English.
The control channel exposes the setting through `ui.set`: The control channel exposes the setting through `ui.set`:
@@ -12,7 +12,7 @@ The control channel exposes the setting through `ui.set`:
{"method":"ui.set","params":{"key":"language","value":"ja"}} {"method":"ui.set","params":{"key":"language","value":"ja"}}
``` ```
The value is `auto` or a language code (`en`, `ja`, `zh-hans`, `zh-hant`, `cs`, `pt-br`, `de`, `es`, `fr`, `ru`, `kk`, `bg`, `te`, `hu`, `uk`, `it`, `ar`, `pl`, `sl`, `el`, `tr`, `lt`, `vi`, `fi` or `sq`), in any case. `ui.state` reports `language` as written in the preferences (`auto`, `en`, `ja`, `zh-hans`, `zh-hant`, `cs`, `pt-br`, `de`, `es`, `fr`, `ru`, `kk`, `bg`, `te`, `hu`, `uk`, `it`, `ar`, `pl`, `sl`, `el`, `tr`, `lt`, `vi`, `fi` or `sq`). Unknown values return an error without changing the current setting. Preferences saved before this setting existed follow the system language. The value is `auto` or a language code (`en`, `ja`, `zh-hans`, `zh-hant`, `cs`, `pt-br`, `de`, `es`, `fr`, `ru`, `kk`, `bg`, `te`, `hu`, `uk`, `it`, `ar`, `pl`, `sl`, `el`, `tr`, `lt`, `vi`, `fi`, `sq` or `ca`), in any case. `ui.state` reports `language` as written in the preferences (`auto`, `en`, `ja`, `zh-hans`, `zh-hant`, `cs`, `pt-br`, `de`, `es`, `fr`, `ru`, `kk`, `bg`, `te`, `hu`, `uk`, `it`, `ar`, `pl`, `sl`, `el`, `tr`, `lt`, `vi`, `fi`, `sq` or `ca`). Unknown values return an error without changing the current setting. Preferences saved before this setting existed follow the system language.
Dialogs, panels, menus, notices and history labels go through the catalog (`tl!`). Most shipped catalogs (Japanese, Chinese, Russian, Bulgarian, German, Spanish, French, Telugu, Hungarian, Ukrainian, Italian, Brazilian Portuguese, Arabic, Slovenian, Kazakh, Polish, Greek, Turkish, Lithuanian, Vietnamese and others) cover the current localizable surface (about 2,000–2,600 entries each), and Czech (2,674 entries) covers the same surface. Untranslated labels use English. Error details that come from the engine or the operating system are shown as they are, inside a translated frame ("操作「…」に失敗しました: …"). The command palette matches the translated label, the English label and the command id. Vertical Japanese PDF rendering is an existing viewer feature; this change does not add vertical text editing. Document contents, filenames, CLI/MCP/control protocol strings and external help stay outside the catalog. Per-language coverage, script support and what each language still needs are in [localization-parity.md](localization-parity.md). Dialogs, panels, menus, notices and history labels go through the catalog (`tl!`). Most shipped catalogs (Japanese, Chinese, Russian, Bulgarian, German, Spanish, French, Telugu, Hungarian, Ukrainian, Italian, Brazilian Portuguese, Arabic, Slovenian, Kazakh, Polish, Greek, Turkish, Lithuanian, Vietnamese and others) cover the current localizable surface (about 2,000–2,600 entries each), and Czech (2,674 entries) covers the same surface. Untranslated labels use English. Error details that come from the engine or the operating system are shown as they are, inside a translated frame ("操作「…」に失敗しました: …"). The command palette matches the translated label, the English label and the command id. Vertical Japanese PDF rendering is an existing viewer feature; this change does not add vertical text editing. Document contents, filenames, CLI/MCP/control protocol strings and external help stay outside the catalog. Per-language coverage, script support and what each language still needs are in [localization-parity.md](localization-parity.md).
@@ -26,6 +26,8 @@ The Vietnamese catalog has 2,674 entries and one plural form (`plural_none`), co
The Finnish catalog has 2,674 entries: every key of the other bundled catalogs, written from the English labels in ordinary Finnish computing vocabulary and sentence case. Finnish has the English one/other plural rule, with the partitive singular after any number but 1 (`1 sivu`, `2 sivua`, `0 sivua`). Calendar headings use the nominative month (`Lokakuu 2026`); dates use the partitive (`14. lokakuuta 2026`), and Finnish is also a date language for month and weekday names. Any `fi-*` locale selects it; `sv-FI` keeps English until PdfCraft has a Swedish catalog. The bundled Latin faces cover `ä ö å š ž`. The translation is AI-assisted (Claude Code) and has not yet been proofread line by line by a native speaker. The Finnish catalog has 2,674 entries: every key of the other bundled catalogs, written from the English labels in ordinary Finnish computing vocabulary and sentence case. Finnish has the English one/other plural rule, with the partitive singular after any number but 1 (`1 sivu`, `2 sivua`, `0 sivua`). Calendar headings use the nominative month (`Lokakuu 2026`); dates use the partitive (`14. lokakuuta 2026`), and Finnish is also a date language for month and weekday names. Any `fi-*` locale selects it; `sv-FI` keeps English until PdfCraft has a Swedish catalog. The bundled Latin faces cover `ä ö å š ž`. The translation is AI-assisted (Claude Code) and has not yet been proofread line by line by a native speaker.
The Catalan catalog has 2,592 entries, written from the meaning of the English labels in standard Catalan computing vocabulary. Commands use the second-person imperative (`Desa`, `Obre`, `Cancel·la`) and notices use impersonal or informal wording. Catalan has two plural forms like English (1 is singular; 0 and every other count take the plural: `1 pàgina`, `3 pàgines`). Every `ca-*` locale, including Valencian (`ca_ES@valencia`), uses this catalog. The interpunct of `l·l` is U+00B7, which the bundled Latin faces cover (tested in `tests/fonts.rs`). The date picker has Catalan month and weekday names; dates are day-first like French (`dddd d mmmm yyyy`), but after a day the month carries its own preposition, elided before a vowel (`7 de març`, `7 d'abril`, `8 d'octubre`), as Czech and Russian change theirs through `months_with_day`; the year takes `\de` from the pattern (`d mmmm \de yyyy`). A native Catalan speaker (the contributor) has reviewed the translation.
Edit text reads Hebrew lines in typing order and writes them back in drawing order (`crates/edit/src/bidi.rs`); letters the page's own font lacks are drawn with the craft-fonts `Hebr` face (Noto Sans Hebrew) as Type 3 glyphs, and a build without one says so. Hebrew points (niqqud) can't be written yet in Edit text. Add text (#128) draws Hebrew with the same face through the shaped path Arabic and Thai use: right to left with numbers and Latin in place, points kept on their letter, and typing order for copy and search. The craft-fonts revision pinned for CI and releases has no `Hebr` face yet, so until one is added releases report that error instead of drawing the missing letters, and refuse Hebrew in Add text. Edit text reads Hebrew lines in typing order and writes them back in drawing order (`crates/edit/src/bidi.rs`); letters the page's own font lacks are drawn with the craft-fonts `Hebr` face (Noto Sans Hebrew) as Type 3 glyphs, and a build without one says so. Hebrew points (niqqud) can't be written yet in Edit text. Add text (#128) draws Hebrew with the same face through the shaped path Arabic and Thai use: right to left with numbers and Latin in place, points kept on their letter, and typing order for copy and search. The craft-fonts revision pinned for CI and releases has no `Hebr` face yet, so until one is added releases report that error instead of drawing the missing letters, and refuse Hebrew in Add text.
Japanese interface text uses BIZ UDPGothic from [craft-fonts](https://github.com/storytold/craft-fonts), an optional build input that every release includes (`CRAFT_FONTS_DIR`; see the README). A build made without it has no Japanese face, so Japanese labels show replacement boxes. Chinese is drawn with the same craft-fonts CJK faces (in Simplified Chinese the `Hans` faces come first, so a line never mixes faces with different baselines), so a character none of them has shows a replacement box (and every Chinese character does in a build without craft-fonts). Russian, Bulgarian, Czech, Brazilian Portuguese, German, Spanish, French, Hungarian, Italian, Turkish, Lithuanian and Albanian need only the bundled Latin faces (tested in `tests/fonts.rs`). Hungarian uses the same one/other plural rule as English: 1 is singular, and every other count — including 0 — is the other form. After a numeral the noun stays singular (`1 oldal`, `2 oldal`). Telugu is drawn with a craft-fonts `Telu` face (Noto Sans Telugu, kept in the web build too), whose conjuncts egui shapes; a build without one shows Telugu labels as replacement boxes, and the release-pinned craft-fonts input has no Telugu face yet. Japanese interface text uses BIZ UDPGothic from [craft-fonts](https://github.com/storytold/craft-fonts), an optional build input that every release includes (`CRAFT_FONTS_DIR`; see the README). A build made without it has no Japanese face, so Japanese labels show replacement boxes. Chinese is drawn with the same craft-fonts CJK faces (in Simplified Chinese the `Hans` faces come first, so a line never mixes faces with different baselines), so a character none of them has shows a replacement box (and every Chinese character does in a build without craft-fonts). Russian, Bulgarian, Czech, Brazilian Portuguese, German, Spanish, French, Hungarian, Italian, Turkish, Lithuanian and Albanian need only the bundled Latin faces (tested in `tests/fonts.rs`). Hungarian uses the same one/other plural rule as English: 1 is singular, and every other count — including 0 — is the other form. After a numeral the noun stays singular (`1 oldal`, `2 oldal`). Telugu is drawn with a craft-fonts `Telu` face (Noto Sans Telugu, kept in the web build too), whose conjuncts egui shapes; a build without one shows Telugu labels as replacement boxes, and the release-pinned craft-fonts input has no Telugu face yet.
+1
View File
@@ -235,6 +235,7 @@ M2 (renderer), M7 (content editing), M10 (OCR and Office), M11 (Preflight and st
| 2026-10-15 | minor | M2: three vendored hayro-interpret patches (31–33): sampled functions index a flat table (`bug1721218` 1.34 → 0.49 s), one JPEG or Flate decode serves every size an image is drawn at (35–40% less render work zooming a scan), patch meshes copy their vertices less (10–16% on mesh-gradient pages); pixel for pixel the same over the pdf.js corpus. Estimates unchanged. | | 2026-10-15 | minor | M2: three vendored hayro-interpret patches (31–33): sampled functions index a flat table (`bug1721218` 1.34 → 0.49 s), one JPEG or Flate decode serves every size an image is drawn at (35–40% less render work zooming a scan), patch meshes copy their vertices less (10–16% on mesh-gradient pages); pixel for pixel the same over the pdf.js corpus. Estimates unchanged. |
| 2026-10-13 | minor | M2/M6: images with at most 16 bits of samples a pixel (8-bit Separation/DeviceN, 1-4-bit gray or indexed) convert once per pixel value (hayro-interpret patch 28), so spot-colour form backgrounds draw in milliseconds instead of seconds; widgets without `/P` are placed in one pass; the Fields panel no longer searches the form per row and draws only the rows in view. Estimates unchanged. | | 2026-10-13 | minor | M2/M6: images with at most 16 bits of samples a pixel (8-bit Separation/DeviceN, 1-4-bit gray or indexed) convert once per pixel value (hayro-interpret patch 28), so spot-colour form backgrounds draw in milliseconds instead of seconds; widgets without `/P` are placed in one pass; the Fields panel no longer searches the form per row and draws only the rows in view. Estimates unchanged. |
| 2026-10-13 | minor | M3: native-open admission preserves modal/picker ownership; regressions cover queued replies and one-shot picker wakes. Estimates unchanged | | 2026-10-13 | minor | M3: native-open admission preserves modal/picker ownership; regressions cover queued replies and one-shot picker wakes. Estimates unchanged |
| 2026-10-14 | minor | M14: Catalan interface and date language (2,592-entry catalog, coverage tests). Estimates unchanged |
| 2026-10-12 | minor | M13: CLI check directory identity, with overlap and symlink regressions; estimates unchanged | | 2026-10-12 | minor | M13: CLI check directory identity, with overlap and symlink regressions; estimates unchanged |
| 2026-10-12 | minor | M4: shared Type 3 glyph resources survive extraction, with save/reopen and independent raster regressions; estimates unchanged | | 2026-10-12 | minor | M4: shared Type 3 glyph resources survive extraction, with save/reopen and independent raster regressions; estimates unchanged |
| 2026-10-12 | minor | M2: packed TIFF-predictor RGB row boundaries, with decoding and page-render regressions; estimates unchanged | | 2026-10-12 | minor | M2: packed TIFF-predictor RGB row boundaries, with decoding and page-render regressions; estimates unchanged |