diff --git a/Cargo.lock b/Cargo.lock index 683ec1b2..581f78b9 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -3793,6 +3793,7 @@ dependencies = [ "serde", "serde_json", "skrifa 0.44.0", + "sys-locale", "wasm-bindgen", "wasm-bindgen-futures", "web-sys", @@ -5281,6 +5282,15 @@ dependencies = [ "syn 3.0.6", ] +[[package]] +name = "sys-locale" +version = "0.3.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8eab9a99a024a169fe8a903cf9d4a3b3601109bcc13bd9e3c6fff259138626c4" +dependencies = [ + "libc", +] + [[package]] name = "tag_ptr" version = "0.1.0" diff --git a/ROADMAP.md b/ROADMAP.md index 11e5d260..8da005d5 100644 --- a/ROADMAP.md +++ b/ROADMAP.md @@ -127,6 +127,8 @@ M0 → M1 → M2 → M3 → M4 must happen in order. After M4, M5–M12 can run Newest first. One line per session: the date, what moved, and the new overall percentage. +- **2026-10-08 (M14, Windows Chinese Auto):** Auto now reads the Windows display language after locale environment overrides, selecting Simplified or Traditional Chinese through the existing locale mapping. Added Chinese region/script and Windows query regressions. Chinese font delivery remains incomplete. Localization remains partial; overall ≈ 30–35% (unchanged). + - **2026-10-08 (M14, Simplified Chinese):** Added 208 translations, bringing the catalog from 1,625 to 1,833 entries. PhotoCraft-style coverage checks protect registered commands, All tools labels and UI `tl!` literals; generated history and diagnostic regressions preserve user values. Chinese font delivery remains incomplete: the release-pinned craft-fonts input has no Hans face and the web build only embeds the Japanese UI face (see #112 and #140). Localization remains partial; overall ≈ 30–35% (unchanged). - **2026-10-07 (community, #208):** Measure: distance, perimeter and area annotations (standard LineDimension/PolyLineDimension/PolygonDimension with rectilinear /Measure), persistent viewport scales with two-point calibration, vector snapping (endpoints, midpoints, intersections, paths) through nested forms, live readings, CSV export, and eight `measure_*` agent tools. Hardened on landing: snap extraction caps segments and snap targets page-wide and skips undecodable streams and over-deep `q` instead of failing; unsupported imported measurements (compound ft-in, fractional, invalid geometry) are listed as unsupported rather than failing the list; non-ASCII units ("m²") are allowed and rendered in WinAnsi; a closing duplicate polygon vertex counts as closed. Distance and scale are partial (single-unit decimal formats only; Acrobat interop untested). 51.0% shipped; ≈ 30–35% effort. diff --git a/crates/ui-egui/Cargo.toml b/crates/ui-egui/Cargo.toml index bc24e779..28257d54 100644 --- a/crates/ui-egui/Cargo.toml +++ b/crates/ui-egui/Cargo.toml @@ -31,6 +31,10 @@ arboard = { version = "3.6", default-features = false, features = ["image-data"] # Waits for native file pickers off the UI thread (already in the tree through wgpu). pollster = "1.0" +[target.'cfg(windows)'.dependencies] +# Auto interface language: the Windows display language, without starting a process. +sys-locale = "0.3" + [target.'cfg(target_arch = "wasm32")'.dependencies] wasm-bindgen-futures = "0.4" wasm-bindgen = "0.2" diff --git a/crates/ui-egui/src/i18n/mod.rs b/crates/ui-egui/src/i18n/mod.rs index 5e462066..371521e4 100644 --- a/crates/ui-egui/src/i18n/mod.rs +++ b/crates/ui-egui/src/i18n/mod.rs @@ -217,11 +217,24 @@ fn detect_system_lang() -> Lang { { return l; } + #[cfg(target_os = "windows")] + if let Some(l) = windows_ui_language().as_deref().and_then(first_supported) { + return l; + } Lang::EN } -/// The first supported language in a `defaults read` list like `(\n "ja-JP",\n "en-US"\n)`. -#[cfg_attr(not(target_os = "macos"), allow(dead_code))] +/// The Windows display languages in preference order, one tag per line, queried once when Auto +/// first resolves. `sys-locale` asks Windows directly (`GetUserPreferredUILanguages`), so no +/// process is started. +#[cfg(target_os = "windows")] +fn windows_ui_language() -> Option { + let tags: Vec = sys_locale::get_locales().collect(); + (!tags.is_empty()).then(|| tags.join("\n")) +} + +/// The first supported language in a macOS `defaults read` list or Windows UI-culture output. +#[cfg_attr(not(any(target_os = "macos", target_os = "windows")), allow(dead_code))] fn first_supported(list: &str) -> Option { list.split(['(', ')', ',', '"', '\n']).map(str::trim).filter(|s| !s.is_empty()).find_map(lang_from_tag) } @@ -388,6 +401,28 @@ mod tests { assert_eq!(first_supported("("), None); } + #[test] + fn windows_display_language_resolves_chinese_scripts() { + for tag in ["zh-CN", "zh-SG", "zh-Hans", "zh-Hans-CN"] { + assert_eq!(first_supported(&format!("{tag}\r\n")), Lang::from_code("zh-hans")); + } + for tag in ["zh-TW", "zh-HK", "zh-MO", "zh-Hant", "zh-Hant-HK"] { + assert_eq!(first_supported(&format!("{tag}\r\n")), Lang::from_code("zh-hant")); + } + assert_eq!(first_supported("en-US\r\n"), Some(Lang::EN)); + assert_eq!(first_supported("fr-FR\r\n"), None); + assert_eq!(first_supported("\r\n"), None); + } + + #[cfg(target_os = "windows")] + #[test] + fn windows_display_language_query_returns_a_locale_tag() { + let tag = windows_ui_language().expect("Windows should report a display language"); + let tag = tag.trim(); + assert!(!tag.is_empty()); + assert!(tag.split('-').all(|part| !part.is_empty() && part.bytes().all(|b| b.is_ascii_alphanumeric()))); + } + #[test] fn preferences_resolve_with_fallback() { assert_eq!(Lang::from_pref("ja"), JA()); diff --git a/docs/localization.md b/docs/localization.md index 8c5bed86..d81862f4 100644 --- a/docs/localization.md +++ b/docs/localization.md @@ -2,7 +2,7 @@ Choose **Menu > Edit > Preferences…** (Command-comma on macOS, Ctrl-comma elsewhere) **> Interface language** and select **Auto**, **English**, **日本語**, **简体中文**, **繁體中文**, **Čeština** or **Português (Brasil)**. The change applies immediately and persists between launches. Command ids, document contents and file names are unchanged. -**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, 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. A system language without a catalog, such as French, shows English. Windows has no system-language detection yet, so **Auto** shows English there unless `LANG` is set. +**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, 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. A system language without a catalog, such as French, shows English. On Windows, **Auto** uses the Windows display languages, in preference order, when no supported locale is selected by those environment variables. 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`: diff --git a/parity/acrobat-features.toml b/parity/acrobat-features.toml index a83f6f8a..835d0b10 100644 --- a/parity/acrobat-features.toml +++ b/parity/acrobat-features.toml @@ -9806,8 +9806,8 @@ milestone = "M14" status = "partial" commands = ["app.preferences"] tools = [] -evidence = ["crates/ui-egui/src/i18n/mod.rs::language_persists_and_invalid_input_keeps_current_language", "crates/ui-egui/src/i18n/mod.rs::bundled_catalogs_are_consistent", "crates/ui-egui/src/i18n/mod.rs::japanese_covers_commands_and_catalogue", "crates/ui-egui/src/i18n/mod.rs::simplified_chinese_covers_commands_and_catalogue", "crates/ui-egui/src/i18n/mod.rs::simplified_chinese_covers_ui_literals", "crates/ui-egui/src/i18n/mod.rs::simplified_chinese_history_and_diagnostics_preserve_user_values", "crates/ui-egui/tests/control.rs::simplified_chinese_signature_prompts_and_errors_keep_document_state", "crates/ui-egui/tests/control.rs::language_switch_preserves_document_and_command_ids", "crates/ui-egui/tests/control.rs::japanese_controls_and_search_keep_command_ids", "crates/ui-egui/tests/control.rs::japanese_dialogs_errors_and_custom_action_names"] -notes = "Preferences switches Auto (system language)/English/Japanese/Simplified Chinese/Traditional Chinese/Czech/Brazilian Portuguese and persists; ui.set language auto|en|ja|zh-hans|zh-hant|cs|pt-br and ui.state expose it. TSV catalogs per language (same system as PhotoCraft); every dialog, panel and notice goes through tl!. Japanese, Traditional Chinese and Simplified Chinese cover commands, dialogs, panels, shortcuts and history labels; Czech and Brazilian Portuguese the menus. Engine/OS error details stay untranslated. Missing: the other catalogs filled in, Chinese font delivery, Windows system-language detection, a full localization audit." +evidence = ["crates/ui-egui/src/i18n/mod.rs::windows_display_language_resolves_chinese_scripts", "crates/ui-egui/src/i18n/mod.rs::windows_display_language_query_returns_a_locale_tag", "crates/ui-egui/src/i18n/mod.rs::language_persists_and_invalid_input_keeps_current_language", "crates/ui-egui/src/i18n/mod.rs::bundled_catalogs_are_consistent", "crates/ui-egui/src/i18n/mod.rs::japanese_covers_commands_and_catalogue", "crates/ui-egui/src/i18n/mod.rs::simplified_chinese_covers_commands_and_catalogue", "crates/ui-egui/src/i18n/mod.rs::simplified_chinese_covers_ui_literals", "crates/ui-egui/src/i18n/mod.rs::simplified_chinese_history_and_diagnostics_preserve_user_values", "crates/ui-egui/tests/control.rs::simplified_chinese_signature_prompts_and_errors_keep_document_state", "crates/ui-egui/tests/control.rs::language_switch_preserves_document_and_command_ids", "crates/ui-egui/tests/control.rs::japanese_controls_and_search_keep_command_ids", "crates/ui-egui/tests/control.rs::japanese_dialogs_errors_and_custom_action_names"] +notes = "Preferences switches Auto (system language)/English/Japanese/Simplified Chinese/Traditional Chinese/Czech/Brazilian Portuguese and persists; ui.set language auto|en|ja|zh-hans|zh-hant|cs|pt-br and ui.state expose it. TSV catalogs per language (same system as PhotoCraft); every dialog, panel and notice goes through tl!. Japanese, Traditional Chinese and Simplified Chinese cover commands, dialogs, panels, shortcuts and history labels; Czech and Brazilian Portuguese the menus. Engine/OS error details stay untranslated. Auto detects the Windows display language after locale environment overrides, with Chinese script/region mapping and English fallback. Missing: the other catalogs filled in, Chinese font delivery, a full localization audit." [[feature]] id = "misc.prefs-reading"