mirror of
https://github.com/storytold/pdfcraft.git
synced 2026-10-24 10:23:02 +00:00
M14: add Vietnamese interface and date localization (#1712)
* G14: Vietnamese catalog — menus, file and shell chrome Add the Vietnamese (vi) TSV catalog header and translations for menus, File/Edit/Help chrome, clipboard and common shell labels. PhotoCraft and VectorCraft catalogs used only as translation memory when English source and context matched. ATTRIBUTION records the asset hash. Co-authored-by: Cursor <cursoragent@cursor.com> * G14: Vietnamese catalog — forms, signatures and pages Translate form/Fill & Sign/certificate wording and page organize/rotate/ extract/split labels. Keep product and format names unchanged. Co-authored-by: Cursor <cursoragent@cursor.com> * G14: Vietnamese catalog — comments, view, a11y and contexts Translate comments/markup, view/zoom/preferences tools, accessibility and preflight sentences, plus contextual rows (certificate, calendar, keys, scroll speed, signature pad, action wizard, @plural one-form). Co-authored-by: Cursor <cursoragent@cursor.com> * G14: Vietnamese catalog — remaining dialogs and notices Complete the Vietnamese catalog to every (context, English) key in the bundled union (2,580 entries). No unapproved English UI fallbacks remain outside the documented VI_KEEP_AS_IS list. Co-authored-by: Cursor <cursoragent@cursor.com> * G14: restore complete Vietnamese catalog key order Replace the domain-appended catalog with the verified 2,580-entry union in stable key order so coverage matches every bundled reference key. Co-authored-by: Cursor <cursoragent@cursor.com> * G14: register Vietnamese interface and date language Add `vi` / Tiếng Việt to LANGUAGES and DATE_LANGUAGES with plural_none and rtl false. Locale tags vi, vi-VN and vi_VN.UTF-8 resolve. Calendar uses Tháng Một / tháng Một; Fill & Sign refuses non-WinAnsi month names. Coverage tests require full command, literal, key and credits coverage. Co-authored-by: Cursor <cursoragent@cursor.com> * G14: document Vietnamese localization Record Vietnamese at 100% catalog coverage in localization-parity and ROADMAP, list Tiếng Việt in Preferences/docs, and note the G14 catalog in the progress log. Engine/OS details, document contents, filenames, CLI/MCP and external help remain out of scope by design. Co-authored-by: Cursor <cursoragent@cursor.com> * G14: use ASCII > in Vietnamese preference paths Inter lacks U+25B8 (▸). Match other Latin catalogs and write preference paths as "Tùy chọn > …" so the glyph coverage test stays green. Co-authored-by: Cursor <cursoragent@cursor.com> * G14: finalize Vietnamese terminology and current coverage Disambiguate theme Appearance (Giao diện) from object look (Diện mạo), normalize form Field to Trường, make redaction apply wording permanent, use numeric calendar months, close the GoToR/digital-ID key gap, and pin regression assertions. Refresh the vi.tsv attribution hash. Co-authored-by: Cursor <cursoragent@cursor.com> * G14: align localization docs with Vietnamese coverage Recompute the twelve-language mean (≈59% → ≈68%), document 23 interface languages, and remove stale claims that Vietnamese is missing. Co-authored-by: Cursor <cursoragent@cursor.com> * G14: cover Wayland title-bar strings in Vietnamese Translate Minimize/Maximize/Restore and the system title-bar preference added on upstream main, and refresh the catalog attribution hash and counts. Co-authored-by: Cursor <cursoragent@cursor.com> * G14: correct current union key count in localization docs Co-authored-by: Cursor <cursoragent@cursor.com> * G14: prepare Vietnamese localization for review Trim the vi.tsv header to maintainer-oriented notes, record native-speaker community review in attribution and localization docs, and refresh the weighted readiness arithmetic (localization ≈68% → ≈50%). Co-authored-by: Cursor <cursoragent@cursor.com> --------- Co-authored-by: DUY Tran Nhat (SAE-KIDS) <duytn@sanancorp.com> Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
+2
-1
@@ -4,7 +4,7 @@
|
||||
|
||||
Every asset PdfCraft includes, bundles or uses to build its published material, with its author, source and licence. The policy is in [AGENTS.md](AGENTS.md) §1. The machine-readable list, with SHA-256 hashes, is [ATTRIBUTION.toml](ATTRIBUTION.toml). Licence texts are kept beside the assets and summarised in [NOTICE](NOTICE).
|
||||
|
||||
## In this repository (270)
|
||||
## In this repository (271)
|
||||
|
||||
| Asset | Title | Author | Licence | Source | Used for |
|
||||
|---|---|---|---|---|---|
|
||||
@@ -28,6 +28,7 @@ Every asset PdfCraft includes, bundles or uses to build its published material,
|
||||
| `crates/ui-egui/src/i18n/ar.tsv` | Arabic 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 | Arabic UI strings, compiled in by crates/ui-egui/src/i18n/mod.rs |
|
||||
| `crates/ui-egui/src/i18n/tr.tsv` | Turkish 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 | Turkish UI strings, compiled in by crates/ui-egui/src/i18n/mod.rs |
|
||||
| `crates/ui-egui/src/i18n/lt.tsv` | Lithuanian interface translation catalog | PdfCraft contributors | MIT OR Apache-2.0 | Contributor-original: clean-room translations of PdfCraft's English UI labels and user-facing messages; reviewed by a native Lithuanian-speaking contributor | Lithuanian UI strings, compiled in by crates/ui-egui/src/i18n/mod.rs |
|
||||
| `crates/ui-egui/src/i18n/vi.tsv` | Vietnamese interface translation catalog | PdfCraft contributors | MIT OR Apache-2.0 | Contributor-original: clean-room translations of PdfCraft's English UI labels; PhotoCraft and VectorCraft Vietnamese catalogs used only as translation memory when English source and semantic context matched; reviewed by a native Vietnamese-speaking contributor | Vietnamese UI strings, compiled in by crates/ui-egui/src/i18n/mod.rs |
|
||||
| `assets/icons/accessibility.svg` | Lucide icon "accessibility" | Lucide Contributors (portions Cole Bemis, Feather) | ISC | https://github.com/lucide-icons/lucide (npm lucide-static 1.49.0, icons/accessibility.svg) | UI icon, embedded via crates/ui-egui/src/icon_data.rs |
|
||||
| `assets/icons/align-left.svg` | Lucide icon "align-left" | Lucide Contributors (portions Cole Bemis, Feather) | ISC | https://github.com/lucide-icons/lucide (npm lucide-static 1.49.0, icons/align-left.svg) | UI icon, embedded via crates/ui-egui/src/icon_data.rs |
|
||||
| `assets/icons/align-center.svg` | Lucide icon "align-center" | Lucide Contributors (portions Cole Bemis, Feather) | ISC | https://github.com/lucide-icons/lucide (npm lucide-static 1.49.0, icons/align-center.svg) | UI icon, embedded via crates/ui-egui/src/icon_data.rs |
|
||||
|
||||
@@ -230,6 +230,18 @@ kind = "translation"
|
||||
usage = "Lithuanian UI strings, compiled in by crates/ui-egui/src/i18n/mod.rs"
|
||||
sha256 = "2daa34aacddccdbe9862932c5c6f0a0d8bdb86b07cc385a82f0ceaf9f1c4f333"
|
||||
|
||||
|
||||
[[asset]]
|
||||
path = "crates/ui-egui/src/i18n/vi.tsv"
|
||||
title = "Vietnamese interface translation catalog"
|
||||
author = "PdfCraft contributors"
|
||||
source = "Contributor-original: clean-room translations of PdfCraft's English UI labels; PhotoCraft and VectorCraft Vietnamese catalogs used only as translation memory when English source and semantic context matched; reviewed by a native Vietnamese-speaking contributor"
|
||||
licence = "MIT OR Apache-2.0"
|
||||
licence_file = "LICENSE-MIT"
|
||||
kind = "translation"
|
||||
usage = "Vietnamese UI strings, compiled in by crates/ui-egui/src/i18n/mod.rs"
|
||||
sha256 = "f9b0694f684bea95e20bd33bef33fedfd92e28c62f2c140193c89e0ed7a18b97"
|
||||
|
||||
[[asset]]
|
||||
path = "assets/icons/accessibility.svg"
|
||||
title = "Lucide icon \"accessibility\""
|
||||
|
||||
+12
-10
@@ -1,6 +1,6 @@
|
||||
# PdfCraft roadmap
|
||||
|
||||
**Stage: alpha** · next: beta, ~26% (ready for real work 49% → 75%) and ~400–700 h away
|
||||
**Stage: alpha** · next: beta, ~25% (ready for real work 50% → 75%) and ~400–700 h away
|
||||
|
||||
> **Last reviewed:** 2026-10-13 · **Last updated:** 2026-10-13 · **Change:** minor (M3 candidate: native opens retain chosen tools through password retries and defer startup intake while preparation is pending; estimates unchanged) · **Target:** Adobe Acrobat Pro (Acrobat DC, continuous track 26.002.21931, macOS)
|
||||
> **Last reviewed:** 2026-10-12 · **Last updated:** 2026-10-12 · **Change:** minor (M4 #1310 #1311 #1312: undo, revert and the next save keep the last saved revision; estimates unchanged) · **Target:** Adobe Acrobat Pro (Acrobat DC, continuous track 26.002.21931, macOS)
|
||||
@@ -49,7 +49,7 @@ and add a line to the [progress log](#progress-log); when a gap closes, remove i
|
||||
|---|---|---|
|
||||
| **Feature breadth** | **≈ 67%** tier-weighted (56.9% unweighted: 423 shipped + 74 partial of 808; P0 94.2%) | measured, `parity/acrobat-features.toml` |
|
||||
| Feature breadth, mainstream practitioner | ≈ 70% tier-weighted (63.2% unweighted; P0 95.3%) | measured ([split](docs/target-app-parity.md#mainstream-practitioner-vs-full-acrobat-pro)) |
|
||||
| **Ready for real work**, full Acrobat Pro | **≈ 49%** (45–53%) | estimated, weighted sum over [dimensions](#by-dimension) |
|
||||
| **Ready for real work**, full Acrobat Pro | **≈ 50%** (45–53%) | estimated, weighted sum over [dimensions](#by-dimension) |
|
||||
| **Ready for real work**, mainstream practitioner | **≈ 55%** (50–60%) | estimated ([split](docs/target-app-parity.md#mainstream-practitioner-vs-full-acrobat-pro)) |
|
||||
| **Ready for real work**, essentials user | **≈ 66%** (60–70%) | estimated ([method](docs/target-app-parity.md#essentials-user)) |
|
||||
| Remaining to **beta** | ≈ 400–700 Opus 5.5 agent-hours | estimated |
|
||||
@@ -66,7 +66,7 @@ and 245 open issues keep it far from beta.
|
||||
|
||||
| Audience | Ready | Opus 5.5 agent-hours to ≈ 95% | Work that dominates |
|
||||
|---|---|---|---|
|
||||
| Full Acrobat Pro (ready for real work) | ≈ 49% | ≈ 780–1,400 h (≈ 300–550 h wall clock with 3–5 agents; 60–70% parallelizes) | Own renderer and fidelity harness, editing existing content, OCR and Office conversion, Preflight and PDF/A/X/UA, accessibility tagging, signatures for institutions |
|
||||
| Full Acrobat Pro (ready for real work) | ≈ 50% | ≈ 780–1,400 h (≈ 300–550 h wall clock with 3–5 agents; 60–70% parallelizes) | Own renderer and fidelity harness, editing existing content, OCR and Office conversion, Preflight and PDF/A/X/UA, accessibility tagging, signatures for institutions |
|
||||
| Mainstream practitioner | ≈ 55% | ≈ 500–900 h (≈ 200–400 h wall clock; ≈ 60% parallelizes) | Rendering fidelity, editing existing text (CJK, RTL, subsets), conversion (Word layout, Excel, PowerPoint, Office→PDF), OCR languages, the bug backlog |
|
||||
| Essentials user | ≈ 66% | ≈ 180–330 h (≈ 90–180 h wall clock; ≈ 50% parallelizes) | Rendering fidelity on everyday files, launch and GPU stability, simple text fixes, Word export layout, Chinese UI glyphs |
|
||||
|
||||
@@ -81,13 +81,13 @@ Hours are calibrated as in [Effort and calibration](docs/target-app-parity.md#ef
|
||||
| File formats and conversion | ≈ 40% | 12% | 120–220 (within features) | [file-format-parity.md](docs/file-format-parity.md) |
|
||||
| PDF specification and standards | ≈ 60% | 8% | 150–260 (within features) | [pdf-spec-parity.md](docs/pdf-spec-parity.md) |
|
||||
| Hardware | ≈ 35% | 4% | 25–50 | [hardware-parity.md](docs/hardware-parity.md) |
|
||||
| Localization | ≈ 59% | 5% | 35–60 | [localization-parity.md](docs/localization-parity.md) |
|
||||
| Localization | ≈ 68% | 5% | 35–60 | [localization-parity.md](docs/localization-parity.md) |
|
||||
| Performance | ≈ 40% | 6% | 25–45 | [gaps.md](docs/gaps.md) gap 13 |
|
||||
| Stability | ≈ 50% | 8% | 45–80 | [gaps.md](docs/gaps.md) gap 6 |
|
||||
| Platforms | ≈ 70% | 5% | 15–30 | [hardware-parity.md](docs/hardware-parity.md#platforms) |
|
||||
| Ecosystem and plug-ins | ≈ 30% | 1% | 10–20 (within features) | [gaps.md](docs/gaps.md) |
|
||||
| AI features | ≈ 0% (in scope: local providers) | 1% | 20–40 (within features) | [gaps.md](docs/gaps.md) gap 23 |
|
||||
| **Total (weighted sum)** | **≈ 49%** | 100% | **≈ 780–1,400** | |
|
||||
| **Total (weighted sum)** | **≈ 50%** | 100% | **≈ 780–1,400** | |
|
||||
|
||||
## Features
|
||||
|
||||
@@ -112,8 +112,8 @@ What's strong and missing in each: [target-app-parity.md](docs/target-app-parity
|
||||
|
||||
## Languages
|
||||
|
||||
Measured from `crates/ui-egui/src/i18n/*.tsv` (2,143 keys). Detail and script notes:
|
||||
[localization-parity.md](docs/localization-parity.md).
|
||||
Twelve key languages (historical % snapshot vs 2,143 keys, except Vietnamese vs the current
|
||||
union). Detail and script notes: [localization-parity.md](docs/localization-parity.md).
|
||||
|
||||
| Language | Code | Strings | Status |
|
||||
|---|---|---|---|
|
||||
@@ -128,10 +128,11 @@ Measured from `crates/ui-egui/src/i18n/*.tsv` (2,143 keys). Detail and script no
|
||||
| Japanese | `ja` | 99.1% | partial |
|
||||
| German | `de` | 99.3% | partial |
|
||||
| Korean | `ko` | 0% | none |
|
||||
| Vietnamese | `vi` | 0% | none |
|
||||
| Vietnamese | `vi` | 100% of current union | partial (dialogs / tooltips; no help) |
|
||||
|
||||
Also shipped: Traditional Chinese, Russian, Bulgarian, Telugu, Hungarian, Ukrainian, Italian
|
||||
(94–100%) and Czech (menus only): **8 more languages**.
|
||||
Also shipped: Traditional Chinese, Russian, Bulgarian, Telugu, Hungarian, Ukrainian, Italian,
|
||||
Kazakh, Polish, Slovenian, Greek, Turkish, Lithuanian and Czech (menus only): **14 more catalogs**
|
||||
(23 interface languages including English).
|
||||
|
||||
## Upcoming
|
||||
|
||||
@@ -150,6 +151,7 @@ Ranked; detail and the full milestone table in [docs/roadmap.md](docs/roadmap.md
|
||||
| 8 | 1.0 polish: performance, right-to-left, missing languages, keyboard-only (M14) | 55–95 |
|
||||
|
||||
## Progress log
|
||||
- **2026-10-13 (G14, Vietnamese localization):** Added Vietnamese (`vi`, Tiếng Việt) as an interface and date language with a 2,591-entry catalog (`plural_none`) covering 100% of the current localizable key union (plus `theme`/`Appearance`). Locale tags `vi` / `vi-VN` / `vi_VN.UTF-8` resolve; calendar months use numeric `Tháng 1`…`12` / `tháng 1`…`12`. Native-speaker community review recorded. The twelve-language localization mean moves from ≈59% to ≈68%; weighted readiness ≈49% → ≈50%. Status stays `partial`: engine/OS details, document contents, filenames, CLI/MCP and external help stay outside the catalog by design.
|
||||
|
||||
- **2026-10-13 (M2, page-unit transform precision):** Preserve small transform coefficients in page marks, added content and print output. Regression cases cover accepted page units of 4096 and 75000; existing page-unit interpretation and estimates are unchanged.
|
||||
- **2026-10-14 (M5, PR #1457 branch refresh):** Merged upstream main and reconciled roadmap entries while preserving comment submission shortcuts. Estimates unchanged.
|
||||
|
||||
@@ -56,7 +56,7 @@ const AR_DAYS: [&str; 7] = ["الأحد", "الاثنين", "الثلاثاء",
|
||||
|
||||
/// 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.
|
||||
pub static DATE_LANGUAGES: [DateLanguage; 22] = [
|
||||
pub static DATE_LANGUAGES: [DateLanguage; 23] = [
|
||||
DateLanguage {
|
||||
code: "en",
|
||||
name: "English",
|
||||
@@ -360,6 +360,44 @@ pub static DATE_LANGUAGES: [DateLanguage; 22] = [
|
||||
days: ["sekmadienis", "pirmadienis", "antradienis", "trečiadienis", "ketvirtadienis", "penktadienis", "šeštadienis"],
|
||||
days_short: ["sk", "pr", "an", "tr", "kt", "pn", "št"],
|
||||
},
|
||||
// Vietnamese: numeric month forms (`tháng 1` with a day, `Tháng 1` standing alone).
|
||||
// Fill & Sign still refuses any format whose final text fails the existing WinAnsi check
|
||||
// (for example weekday names); `dd.mm.yyyy` remains writable.
|
||||
DateLanguage {
|
||||
code: "vi",
|
||||
name: "Tiếng Việt",
|
||||
months: [
|
||||
"Tháng 1",
|
||||
"Tháng 2",
|
||||
"Tháng 3",
|
||||
"Tháng 4",
|
||||
"Tháng 5",
|
||||
"Tháng 6",
|
||||
"Tháng 7",
|
||||
"Tháng 8",
|
||||
"Tháng 9",
|
||||
"Tháng 10",
|
||||
"Tháng 11",
|
||||
"Tháng 12",
|
||||
],
|
||||
months_with_day: Some([
|
||||
"tháng 1",
|
||||
"tháng 2",
|
||||
"tháng 3",
|
||||
"tháng 4",
|
||||
"tháng 5",
|
||||
"tháng 6",
|
||||
"tháng 7",
|
||||
"tháng 8",
|
||||
"tháng 9",
|
||||
"tháng 10",
|
||||
"tháng 11",
|
||||
"tháng 12",
|
||||
]),
|
||||
months_short: ["Thg 1", "Thg 2", "Thg 3", "Thg 4", "Thg 5", "Thg 6", "Thg 7", "Thg 8", "Thg 9", "Thg 10", "Thg 11", "Thg 12"],
|
||||
days: ["Chủ Nhật", "Thứ Hai", "Thứ Ba", "Thứ Tư", "Thứ Năm", "Thứ Sáu", "Thứ Bảy"],
|
||||
days_short: ["CN", "T2", "T3", "T4", "T5", "T6", "T7"],
|
||||
},
|
||||
];
|
||||
|
||||
/// The date language for `code` (any case), or `None` when there's none.
|
||||
@@ -683,6 +721,25 @@ mod tests {
|
||||
assert_eq!(s.date_language(), None);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn vietnamese_month_and_weekday_names() {
|
||||
let mut s = Session::new().with_clock(|| 1_700_000_000);
|
||||
s.set_date_language(Some("vi")).unwrap();
|
||||
assert_eq!(s.date_language(), Some("vi"));
|
||||
s.set_date_format("d mmmm yyyy").unwrap();
|
||||
assert_eq!(s.today_text(None, None).as_deref(), Ok("14 tháng 11 2023"));
|
||||
s.set_date_format("mmmm yyyy").unwrap();
|
||||
assert_eq!(s.today_text(None, None).as_deref(), Ok("Tháng 11 2023"));
|
||||
s.set_date_format("ddd, d mmm").unwrap();
|
||||
assert_eq!(s.today_text(None, None).as_deref(), Ok("T3, 14 Thg 11"));
|
||||
// Encoding follows the spelled text: `14 tháng 11 2023` is WinAnsi-representable;
|
||||
// a weekday name such as `Thứ Ba` is not, so Fill & Sign refuses that format.
|
||||
assert_eq!(s.today_text_for_pdf(Some("d mmmm yyyy"), Some("vi")).as_deref(), Ok("14 tháng 11 2023"));
|
||||
let e = s.today_text_for_pdf(Some("dddd d mmmm yyyy"), Some("vi")).unwrap_err();
|
||||
assert!(e.contains("can't be written into the PDF yet") && e.contains("dd.mm.yyyy"), "{e}");
|
||||
assert!(s.today_text_for_pdf(Some("dd.mm.yyyy"), Some("vi")).is_ok());
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn dates_the_pdf_cannot_hold_are_refused_not_damaged() {
|
||||
assert_eq!(unwritable("8. října 2026"), "ř");
|
||||
|
||||
@@ -111,7 +111,7 @@ fn card(ui: &mut egui::Ui, t: &Tokens, title: &str, active: bool, themes: &[(The
|
||||
|
||||
/// The Appearance section of Preferences.
|
||||
pub fn preferences_section(ui: &mut egui::Ui, app: &mut PdfCraftApp, t: &Tokens) {
|
||||
ui.label(RichText::new(tl!("Appearance")).font(theme::semibold(13.0)));
|
||||
ui.label(RichText::new(tl_ctx!("theme", "Appearance")).font(theme::semibold(13.0)));
|
||||
ui.horizontal(|ui| {
|
||||
ui.label(tl!("Appearance mode"));
|
||||
egui::ComboBox::from_id_salt("appearance-mode").selected_text(tl!(mode_label(app.theme_preference))).show_ui(ui, |ui| {
|
||||
|
||||
@@ -147,7 +147,7 @@ fn plural_sl(n: u64) -> usize {
|
||||
}
|
||||
|
||||
/// The registry. English first: it is the fallback and the source language.
|
||||
pub static LANGUAGES: [LangInfo; 22] = [
|
||||
pub static LANGUAGES: [LangInfo; 23] = [
|
||||
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() },
|
||||
// Simplified Chinese; `zh`, `zh-CN`, `zh-SG` and `zh-Hans-*` locales resolve here (see `candidates`).
|
||||
@@ -207,6 +207,9 @@ pub static LANGUAGES: [LangInfo; 22] = [
|
||||
LangInfo { code: "tr", name: "Türkçe", source: include_str!("tr.tsv"), plural: plural_none, rtl: false, catalog: OnceLock::new() },
|
||||
// Lithuanian; every `lt-*` locale (`lt-LT`, `lt_LT.UTF-8`) resolves here. Three plural forms (one, few, other).
|
||||
LangInfo { code: "lt", name: "Lietuvių", source: include_str!("lt.tsv"), plural: plural_lithuanian, rtl: false, catalog: OnceLock::new() },
|
||||
// Vietnamese; every `vi-*` locale (`vi-VN`, `vi_VN.UTF-8`) resolves here. CLDR `vi` counts have
|
||||
// one form ("other"), so `@plural` entries list a single form.
|
||||
LangInfo { code: "vi", name: "Tiếng Việt", source: include_str!("vi.tsv"), plural: plural_none, rtl: false, catalog: OnceLock::new() },
|
||||
];
|
||||
|
||||
impl LangInfo {
|
||||
@@ -2259,6 +2262,243 @@ mod tests {
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn vietnamese_is_registered_and_persists() {
|
||||
let vi = Lang::from_code("vi").expect("vi registered");
|
||||
assert_eq!(vi.name(), "Tiếng Việt");
|
||||
assert_eq!(normalize_pref("Vi"), Some("vi"));
|
||||
for tag in ["vi", "vi-VN", "vi_VN.UTF-8", "VI-vn"] {
|
||||
assert_eq!(lang_from_tag(tag), Some(vi), "{tag}");
|
||||
}
|
||||
assert_eq!(first_supported("vi-VN\r\nen-US"), Some(vi));
|
||||
assert_eq!(tr(vi, "File"), "Tệp");
|
||||
assert_eq!(tr(vi, "Edit"), "Chỉnh sửa");
|
||||
assert_eq!(tr(vi, "Save"), "Lưu");
|
||||
assert_eq!(tr(vi, "Save as…"), "Lưu thành…");
|
||||
assert_eq!(tr(vi, "Bookmarks"), "Dấu trang");
|
||||
assert_eq!(tr(vi, "Fill & Sign"), "Điền & Ký");
|
||||
assert_eq!(tr(vi, "Interface language"), "Ngôn ngữ giao diện");
|
||||
assert_eq!((0..=3).map(|n| (vi.0.plural)(n)).collect::<Vec<_>>(), [0, 0, 0, 0]);
|
||||
for n in [0, 1, 2, 5] {
|
||||
assert_eq!(trn(vi, n, "{n} page", "{n} pages"), format!("{n} trang"));
|
||||
assert_eq!(trn(vi, n, "{n} field", "{n} fields"), format!("{n} trường"));
|
||||
}
|
||||
let mut app = crate::PdfCraftApp::default();
|
||||
app.set_option("language", "vi").unwrap();
|
||||
assert_eq!(app.language, "vi");
|
||||
let mut restored = crate::PdfCraftApp::default();
|
||||
restored.restore(&app.persist());
|
||||
assert_eq!(restored.language, "vi");
|
||||
assert!(restored.set_option("language", "xx").is_err());
|
||||
assert_eq!(restored.language, "vi");
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn vietnamese_covers_commands_and_catalogue() {
|
||||
let vi = Lang::from_code("vi").expect("vi registered");
|
||||
let mut missing = std::collections::BTreeSet::new();
|
||||
for command in pdfcraft_engine::commands::COMMANDS {
|
||||
if !has(vi, command.label) {
|
||||
missing.insert(command.label);
|
||||
}
|
||||
if let Some(menu) = command.menu
|
||||
&& !has(vi, menu)
|
||||
{
|
||||
missing.insert(menu);
|
||||
}
|
||||
}
|
||||
for group in pdfcraft_engine::catalog::TOOL_GROUPS {
|
||||
if !has(vi, group.label) {
|
||||
missing.insert(group.label);
|
||||
}
|
||||
for section in group.sections {
|
||||
if !has(vi, section.title) {
|
||||
missing.insert(section.title);
|
||||
}
|
||||
for item in section.items {
|
||||
if !has(vi, item.label) {
|
||||
missing.insert(item.label);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
assert!(missing.is_empty(), "Vietnamese misses commands/tools: {missing:#?}");
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn vietnamese_covers_ui_literals() {
|
||||
let vi = Lang::from_code("vi").expect("vi registered");
|
||||
let literals = scan_ui_literals(true);
|
||||
let missing: Vec<_> = literals.iter().filter(|label| !has(vi, label)).collect();
|
||||
assert!(missing.is_empty(), "Untranslated Vietnamese UI labels: {missing:#?}");
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn vietnamese_covers_contextual_calendar_certificate_and_preference_labels() {
|
||||
let vi = Lang::from_code("vi").expect("vi registered");
|
||||
assert_eq!(tr_ctx(vi, "comment menu", "Edit"), "Sửa");
|
||||
assert_eq!(tr_ctx(vi, "signature pad", "Type"), "Nhập chữ");
|
||||
assert_eq!(tr_ctx(vi, "action wizard", "Start"), "Bắt đầu");
|
||||
assert_eq!(["Slow", "Normal", "Fast"].map(|label| tr_ctx(vi, "scroll speed", label)), ["Chậm", "Bình thường", "Nhanh"]);
|
||||
let months = [
|
||||
("January", "Tháng 1", "tháng 1"),
|
||||
("February", "Tháng 2", "tháng 2"),
|
||||
("March", "Tháng 3", "tháng 3"),
|
||||
("April", "Tháng 4", "tháng 4"),
|
||||
("May", "Tháng 5", "tháng 5"),
|
||||
("June", "Tháng 6", "tháng 6"),
|
||||
("July", "Tháng 7", "tháng 7"),
|
||||
("August", "Tháng 8", "tháng 8"),
|
||||
("September", "Tháng 9", "tháng 9"),
|
||||
("October", "Tháng 10", "tháng 10"),
|
||||
("November", "Tháng 11", "tháng 11"),
|
||||
("December", "Tháng 12", "tháng 12"),
|
||||
];
|
||||
for (source, standalone, in_date) in months {
|
||||
assert_eq!(tr(vi, source), standalone);
|
||||
assert_eq!(tr_ctx(vi, "calendar date", source), in_date);
|
||||
}
|
||||
assert_eq!(
|
||||
["Su", "Mo", "Tu", "We", "Th", "Fr", "Sa"].map(|day| tr_ctx(vi, "calendar weekday", day)),
|
||||
["CN", "T2", "T3", "T4", "T5", "T6", "T7"]
|
||||
);
|
||||
assert_eq!(tr_ctx(vi, "certificate", "Subject"), "Chủ thể chứng chỉ");
|
||||
assert_eq!(tr(vi, "Subject"), "Chủ đề");
|
||||
// Appearance: object/comment/signature look vs Preferences theme section.
|
||||
assert_eq!(tr(vi, "Appearance"), "Diện mạo");
|
||||
assert_eq!(tr_ctx(vi, "theme", "Appearance"), "Giao diện");
|
||||
// PDF form Field — never the visual-box sense "ô".
|
||||
assert_eq!(tr(vi, "Field"), "Trường");
|
||||
assert_eq!(tr(vi, "Fields"), "Các trường");
|
||||
assert_eq!(tr(vi, "Form fields"), "Các trường biểu mẫu");
|
||||
assert_eq!(tr(vi, "Text field"), "Trường văn bản");
|
||||
assert_eq!(tr(vi, "Date field"), "Trường ngày");
|
||||
assert_eq!(tr(vi, "Image field"), "Trường hình ảnh");
|
||||
assert_eq!(tr(vi, "Field properties"), "Thuộc tính trường");
|
||||
assert_eq!(tr(vi, "Field list"), "Danh sách trường");
|
||||
assert_eq!(tr(vi, "{kind} field"), "Trường {kind}");
|
||||
// Redaction: permanent removal, not temporary cover.
|
||||
assert_eq!(tr(vi, "Mark for redaction"), "Đánh dấu để xóa");
|
||||
assert_eq!(tr(vi, "Apply redactions"), "Xóa vĩnh viễn nội dung đã đánh dấu");
|
||||
assert_eq!(tr(vi, "Redact text and images"), "Xóa nội dung nhạy cảm trong văn bản và hình ảnh");
|
||||
assert_eq!(tr(vi, "Clear form"), "Xóa dữ liệu biểu mẫu");
|
||||
// Fill & Sign tool section title — fill-in, not paint fill.
|
||||
assert_eq!(tr(vi, "Fill"), "Điền");
|
||||
assert_eq!(tr(vi, "Fill & Sign"), "Điền & Ký");
|
||||
assert_eq!(
|
||||
tr(vi, "Remove \"{name}\" from this list? The file itself is not deleted."),
|
||||
"Gỡ \"{name}\" khỏi danh sách này? Bản thân tệp không bị xóa."
|
||||
);
|
||||
for field in [
|
||||
"Issued to",
|
||||
"Issued by",
|
||||
"Valid from",
|
||||
"Valid to",
|
||||
"Intended usage",
|
||||
"Serial number",
|
||||
"Issuer",
|
||||
"Validity starts",
|
||||
"Validity ends",
|
||||
"Public key",
|
||||
"Basic constraints",
|
||||
"Key usage",
|
||||
"Self-signed",
|
||||
"SHA-1 digest",
|
||||
"SHA-256 digest",
|
||||
] {
|
||||
assert_ne!(tr_ctx(vi, "certificate", field), field, "certificate field remains untranslated: {field}");
|
||||
}
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn vietnamese_covers_bundled_catalog_keys() {
|
||||
let vi = Lang::from_code("vi").expect("vi registered");
|
||||
let mut missing = std::collections::BTreeSet::new();
|
||||
for reference in LANGUAGES.iter().filter(|language| language.code != "vi") {
|
||||
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() {
|
||||
"" => vi.catalog().plain(&entry.source),
|
||||
"@id" => vi.catalog().id(&entry.source),
|
||||
"@plural" => entry.source.split_once('|').and_then(|(one, other)| vi.catalog().plural(one, other, 0)),
|
||||
context => vi.catalog().contextual(context, &entry.source),
|
||||
};
|
||||
if translated.is_none() {
|
||||
missing.insert((entry.context, entry.source));
|
||||
}
|
||||
}
|
||||
}
|
||||
assert!(missing.is_empty(), "Vietnamese misses bundled catalog keys: {missing:#?}");
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn vietnamese_covers_credits_labels() {
|
||||
let vi = Lang::from_code("vi").expect("vi registered");
|
||||
let labels = crate::credits::NameMode::ALL
|
||||
.iter()
|
||||
.map(|mode| mode.label())
|
||||
.chain(crate::credits::SortKey::ALL.iter().flat_map(|key| {
|
||||
let (menu, heading) = key.label();
|
||||
[menu, heading]
|
||||
}))
|
||||
.chain(crate::credits::MODEL_COLUMNS.iter().copied());
|
||||
let missing: Vec<_> = labels.filter(|label| !has(vi, label)).collect();
|
||||
assert!(missing.is_empty(), "Vietnamese misses credits labels: {missing:#?}");
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn vietnamese_leaves_no_unapproved_english_fallbacks() {
|
||||
let vi = Lang::from_code("vi").expect("vi registered");
|
||||
// Authoritative allowlist for intentional English-identical Vietnamese rows.
|
||||
const KEEP_AS_IS: &[&str] = &[
|
||||
"OK",
|
||||
"Digital ID",
|
||||
"ZIP",
|
||||
"JavaScript",
|
||||
"Microsoft Word (.docx)",
|
||||
"PDF/A…",
|
||||
"PostScript / EPS",
|
||||
"Rich Text Format (.rtf)",
|
||||
"CheckBox",
|
||||
"Combo",
|
||||
"Home / End",
|
||||
"PushButton",
|
||||
"Radio",
|
||||
"Standard Business",
|
||||
"Keychain · ",
|
||||
"{0}: {1}",
|
||||
"{field}: {e}",
|
||||
"{month} {y}",
|
||||
"{n} {kind}",
|
||||
"{rule} - {status}",
|
||||
"+LOC",
|
||||
"−LOC",
|
||||
"ΔLOC",
|
||||
"+Bin",
|
||||
"−Bin",
|
||||
"`{command}` {when}",
|
||||
"English",
|
||||
];
|
||||
const KEY_CAPS: &[&str] = &["Ctrl", "Shift", "Delete", "Esc", "Home", "End", "Space"];
|
||||
let (entries, errors) = catalog::parse_entries(vi.0.source, vi.0.plural_forms());
|
||||
assert!(errors.is_empty(), "{errors:?}");
|
||||
let edges = |s: &str| (s.len() - s.trim_start().len(), s.len() - s.trim_end().len());
|
||||
let mut unchanged = Vec::new();
|
||||
for e in &entries {
|
||||
let key_cap = e.context == "key" && KEY_CAPS.contains(&e.source.as_str());
|
||||
if e.source == e.translation && !KEEP_AS_IS.contains(&e.source.as_str()) && !key_cap {
|
||||
unchanged.push((e.context.clone(), e.source.clone()));
|
||||
}
|
||||
assert!(!e.translation.contains("..."), "use … rather than three dots: {:?}", e.translation);
|
||||
assert_eq!(edges(&e.translation), edges(&e.source), "stray whitespace: {:?}", e.translation);
|
||||
if e.context == "@plural" {
|
||||
assert!(!e.translation.contains('|'), "Vietnamese @plural is one form: {:?}", e.translation);
|
||||
}
|
||||
}
|
||||
assert!(unchanged.is_empty(), "unapproved English Vietnamese catalog entries: {unchanged:#?}");
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn slovene_is_registered_and_persists() {
|
||||
let sl = Lang::from_code("sl").expect("sl registered");
|
||||
|
||||
File diff suppressed because it is too large
Load Diff
@@ -313,6 +313,7 @@ fn primary_ui_fonts_cover_latin_and_cyrillic_catalogs() {
|
||||
("tr", include_str!("../src/i18n/tr.tsv")),
|
||||
("sl", include_str!("../src/i18n/sl.tsv")),
|
||||
("lt", include_str!("../src/i18n/lt.tsv")),
|
||||
("vi", include_str!("../src/i18n/vi.tsv")),
|
||||
] {
|
||||
let mut text = String::from("áčďéěíňóřšťúůýžÁČĎÉĚÍŇÓŘŠŤÚŮÝŽãõçâêôàÃÕÇÂÊÔÀñÑüÜäÄöÖőűß„“¿¡«»…ґҐєЄіІїЇәғқңөұүһӘҒҚҢӨҰҮҺ");
|
||||
for line in catalog.lines().filter(|l| !l.starts_with('#')) {
|
||||
|
||||
+8
-7
@@ -1,6 +1,6 @@
|
||||
# Where PdfCraft falls short of Acrobat Pro
|
||||
|
||||
> **Last reviewed:** 2026-10-12 · **Last updated:** 2026-10-12 · **Change:** minor (gap 6: #1398 stamps read upright on rotated pages; estimates unchanged) · **Target:** Adobe Acrobat Pro (Acrobat DC, continuous track 26.002.21931, macOS)
|
||||
> **Last reviewed:** 2026-10-12 · **Last updated:** 2026-10-13 · **Change:** minor (gap 14: Vietnamese catalog shipped; Hindi/Indonesian/Korean and Arabic RTL remain) · **Target:** Adobe Acrobat Pro (Acrobat DC, continuous track 26.002.21931, macOS)
|
||||
> **Last reviewed:** 2026-10-12 · **Last updated:** 2026-10-12 · **Change:** minor (gap 6: #1317, #1327, #1302, #1315 and #1340; estimates unchanged) · **Target:** Adobe Acrobat Pro (Acrobat DC, continuous track 26.002.21931, macOS)
|
||||
> **Last reviewed:** 2026-10-10 · **Last updated:** 2026-10-12 · **Change:** minor (#1316: replacing a line keeps a neighbouring line whose glyph box overlaps; gap 3 remains) · **Target:** Adobe Acrobat Pro (Acrobat DC, continuous track 26.002.21931, macOS)
|
||||
> **Last reviewed:** 2026-10-10 · **Last updated:** 2026-10-12 · **Change:** gap 4 closed: the web build prints through the browser's print dialog; exclusive image-crop resize follow-up; minor (Word invoice regression coverage, #773; Office fidelity still partial; gap 2: hayro patch counts after the native-gradient backport) · **Target:** Adobe Acrobat Pro (Acrobat DC, continuous track 26.002.21931, macOS)
|
||||
@@ -38,7 +38,7 @@ update the numbers.
|
||||
| 11 | Accessibility: checker only, no tagging tools | feature | 40–70 | [target-app-parity.md](target-app-parity.md) |
|
||||
| 12 | No measured fidelity harness against Acrobat | quality | (in 2) | [target-app-parity.md](target-app-parity.md) |
|
||||
| 13 | Performance budgets and lazy loading of very large files | performance | 25–45 | this file |
|
||||
| 14 | Arabic not mirrored; no Hindi, Indonesian, Korean, Vietnamese | localization | 30–50 | [localization-parity.md](localization-parity.md) |
|
||||
| 14 | Arabic not mirrored; no Hindi, Indonesian, Korean | localization | 25–45 | [localization-parity.md](localization-parity.md) |
|
||||
| 15 | Forms: JavaScript object model, actions, submit, XFA depth | feature | 40–75 | [pdf-spec-parity.md](pdf-spec-parity.md) |
|
||||
| 16 | UI precision gaps (multi-select move, rulers, accelerators, windows) | UI/UX | 30–60 | [ui-parity.md](ui-parity.md) |
|
||||
| 17 | Certificate security and other protect gaps | feature, spec | 15–30 | [pdf-spec-parity.md](pdf-spec-parity.md) |
|
||||
@@ -272,14 +272,15 @@ update the numbers.
|
||||
to edit.
|
||||
- **Estimate:** 25–45 h. **Doc:** this file.
|
||||
|
||||
### 14. Arabic not mirrored; no Hindi, Indonesian, Korean, Vietnamese
|
||||
### 14. Arabic not mirrored; no Hindi, Indonesian, Korean
|
||||
|
||||
- **Missing:** a right-to-left layout (panels, menus, alignment), bidirectional text in the text
|
||||
layout itself (wrapping, typed text); catalogs for Hindi, Indonesian, Korean and Vietnamese
|
||||
(Acrobat ships Korean; it ships Arabic in its Middle East edition; it ships none of the other
|
||||
three).
|
||||
layout itself (wrapping, typed text); catalogs for Hindi, Indonesian and Korean (Acrobat ships
|
||||
Korean; it ships Arabic in its Middle East edition; it ships none of Hindi or Indonesian).
|
||||
Vietnamese ships as a catalog (`vi`, 100% of the current localizable union) but stays `partial`
|
||||
like other non-English languages (no translated help; engine/OS details stay English).
|
||||
- **Evidence:** [localization.md](localization.md) §Arabic; `misc.rtl-ui` planned.
|
||||
- **Estimate:** 30–50 h (Arabic RTL 20–35, four catalogs 10–15 plus review). **Doc:**
|
||||
- **Estimate:** 25–45 h (Arabic RTL 20–35, three catalogs 8–12 plus review). **Doc:**
|
||||
[localization-parity.md](localization-parity.md).
|
||||
|
||||
### 15. Forms: JavaScript object model, actions, submit, XFA depth
|
||||
|
||||
+19
-15
@@ -1,6 +1,6 @@
|
||||
# Localization parity
|
||||
|
||||
> **Last reviewed:** 2026-10-10 · **Last updated:** 2026-10-12 · **Change:** minor (list all registered interface languages; preserve the original coverage snapshot) · **Target:** Adobe Acrobat Pro (Acrobat DC, continuous track 26.002.21931, macOS)
|
||||
> **Last reviewed:** 2026-10-10 · **Last updated:** 2026-10-13 · **Change:** minor (Vietnamese 100% of current key union; native-speaker community review; twelve-language mean ≈68%) · **Target:** Adobe Acrobat Pro (Acrobat DC, continuous track 26.002.21931, macOS)
|
||||
|
||||
Per-language status of PdfCraft's interface against Acrobat Pro's. How translations work and how to
|
||||
add a language: [localization.md](localization.md). Summary in [ROADMAP.md](../ROADMAP.md).
|
||||
@@ -8,10 +8,11 @@ add a language: [localization.md](localization.md). Summary in [ROADMAP.md](../R
|
||||
## How this was measured
|
||||
|
||||
- **Strings translated** is measured: the catalogs in `crates/ui-egui/src/i18n/<code>.tsv`, counted
|
||||
as distinct `(context, English)` keys with a non-empty translation. The denominator is the union
|
||||
of keys across all catalogs, **2,143**. Cross-check: of the 1,151 distinct `tl!("…")` literals in
|
||||
`crates/ui-egui/src`, every full catalog covers 96.5–99.4% (7 literals are in no catalog yet).
|
||||
English is built in.
|
||||
as distinct `(context, English)` keys with a non-empty translation. The **current** denominator is
|
||||
the union of keys across all catalogs (**2,590** plain keys as of 2026-10-13, plus language-specific
|
||||
context rows such as Vietnamese `theme`/`Appearance`). The twelve-language percentage table below
|
||||
still quotes the **historical 2,143-key snapshot** from 2026-10-10 for every language except
|
||||
Vietnamese (re-measured against the current union). English is built in.
|
||||
- **Not counted:** error details from the engine and the operating system, which are shown in
|
||||
English inside a translated frame; there is no translated user documentation or help yet (Acrobat
|
||||
ships localized help).
|
||||
@@ -38,15 +39,17 @@ add a language: [localization.md](localization.md). Summary in [ROADMAP.md](../R
|
||||
| Japanese | `ja` | 2,123 (99.1%) | dialogs and tooltips; no help | BIZ UDPGothic from craft-fonts; IME patched on macOS, Windows, X11, Wayland; vertical text renders but can't be edited | no | partial | yes | 4–8 |
|
||||
| German | `de` | 2,128 (99.3%) | dialogs and tooltips; no help | Latin | community, in progress (#828: terminology, clipping) | partial | yes | 2–4 |
|
||||
| Korean | `ko` | 0 | none | Hangul IME patched (Microsoft and Apple Korean, `vendor/README.md` winit 3, 6, 9, 10); no Hangul UI face in craft-fonts | no | none | yes | 6–10 |
|
||||
| Vietnamese | `vi` | 0 | none | Latin with stacked diacritics; bundled Inter covers them (not tested) | no | none | no | 3–5 |
|
||||
| Vietnamese | `vi` | 2,591 (100% of current union + theme Appearance) | dialogs and tooltips; no help | Latin with stacked diacritics; bundled Inter covers them (fonts test) | community | partial | no | 2–4 |
|
||||
|
||||
**Localization dimension ≈ 59%**: the mean over the twelve, counting a catalog's percentage but only
|
||||
60% for Chinese and Arabic until their script problems are fixed, and 0 for the four missing
|
||||
languages. **≈ 60–115 h** to bring all twelve to `full`, of which ≈ 35–60 h is engineering (Hans
|
||||
face, RTL layout, bidi text) and the rest catalogs and fixes; native-speaker review needs humans.
|
||||
**Localization dimension ≈ 68%**: the mean over the twelve, counting a catalog's percentage but only
|
||||
60% for Chinese and Arabic until their script problems are fixed, and 0 for Hindi, Indonesian and
|
||||
Korean (still missing). Vietnamese moved from 0% to 100% in that mean (59% → 68%). **≈ 55–110 h**
|
||||
to bring all twelve to `full`, of which ≈ 35–60 h is engineering (Hans face, RTL layout, bidi text)
|
||||
and the rest catalogs and fixes; native-speaker review needs humans.
|
||||
|
||||
No language is `full` except English: every catalog leaves engine error details in English, and
|
||||
none has translated help.
|
||||
none has translated help. Vietnamese's **To `full`** hours are therefore residual help/docs polish
|
||||
(catalog and script work are already done), not missing UI strings.
|
||||
|
||||
## Other shipped languages
|
||||
|
||||
@@ -65,15 +68,16 @@ none has translated help.
|
||||
| Slovenian | `sl` | 2,324 entries | partial | no |
|
||||
| Greek | `el` | 2,341 entries | partial | no |
|
||||
| Turkish | `tr` | 2,313 entries | partial | yes |
|
||||
| Lithuanian | `lt` | 2,578 entries | partial; community native-speaker review ([#1401](https://github.com/storytold/pdfcraft/pull/1401)) | no |
|
||||
| Lithuanian | `lt` | 2,585 entries | partial; community native-speaker review ([#1401](https://github.com/storytold/pdfcraft/pull/1401)) | no |
|
||||
|
||||
**14 other languages** ship (21 catalogs plus English, 22 interface languages in Preferences).
|
||||
The entry counts without percentages were measured on 2026-10-12; 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, Finnish, Norwegian, Swedish, Hebrew.
|
||||
**14 other languages** ship (22 catalogs plus English = **23** 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.
|
||||
Acrobat languages PdfCraft lacks: Danish, Dutch, Finnish, Norwegian, Swedish, Hebrew. Vietnamese is shipped here but not by Acrobat.
|
||||
|
||||
## Revision history
|
||||
|
||||
| Date | Change | Summary |
|
||||
|---|---|---|
|
||||
| 2026-10-13 | minor | Vietnamese (`vi`) catalog: 2,591 entries (100% of current key union + `theme`/`Appearance`); twelve-language mean ≈ 68%; native-speaker community review; registered with `plural_none` |
|
||||
| 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 |
|
||||
|
||||
@@ -1,10 +1,10 @@
|
||||
# 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** or **Lietuvių**. 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ų** or **Tiếng Việt**. 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.
|
||||
|
||||
**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. 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, and Vietnamese locales (`vi`, `vi_VN`) the Vietnamese 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`:
|
||||
|
||||
@@ -12,16 +12,17 @@ The control channel exposes the setting through `ui.set`:
|
||||
{"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`), 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` or `lt`). 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` or `vi`), 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` or `vi`). 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!`). Japanese, Traditional Chinese, Simplified Chinese, Russian, Bulgarian, German, Spanish, French, Telugu, Hungarian, Ukrainian, Italian, Brazilian Portuguese and Arabic translate them (about 2,000–2,140 entries each); Czech and Turkish cover the menus so far. 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. 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!`). Japanese, Traditional Chinese, Simplified Chinese, Russian, Bulgarian, German, Spanish, French, Telugu, Hungarian, Ukrainian, Italian, Brazilian Portuguese, Arabic and Slovenian translate them (about 2,000–2,190 entries each); Czech covers the menus so far. 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. 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); Czech still covers mainly the menus. 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).
|
||||
|
||||
Whole sentences that the layers below word — what the accessibility checker found, what the PDF/A verifier reports, what a signature's details say, what is wrong with the pages to print, what an edit is called in the history — are translated too, because those layers hand the sentence over apart from the document's own words. See [Sentences the engine words](#sentences-the-engine-words).
|
||||
|
||||
The Ukrainian catalog has 2,047 entries, written from the meaning of the English labels using Ukrainian computing vocabulary. Commands use infinitives; fixed-count messages use count-neutral wording. Plural-aware messages use the three [CLDR Ukrainian integer forms](https://www.unicode.org/cldr/charts/48/supplemental/language_plural_rules.html#uk): `1 сторінка`, `2 сторінки`, `5 сторінок`; 11–14 take the third form, while 21 and 22 return to the first and second. Calendar dates use genitive month names (`9 жовтня 2026`), while headings use standalone names (`Жовтень 2026`). Home recommendation cards grow to fit translated text; the toolbar search hint truncates before the shortcut, with the full label available on hover.
|
||||
|
||||
The Lithuanian catalog has 2,578 entries and three integer plural forms. Calendar headings use standalone month names; dates use genitive forms. Coverage tests check commands/tools, scanned literal and direct lookups, and source/context keys in the other bundled catalogs. Calendar, inspector and password-dialog UI regressions check dynamic labels and preserve document values. A native Lithuanian-speaking contributor has reviewed the translation.
|
||||
The Lithuanian catalog has 2,585 entries and three integer plural forms. Calendar headings use standalone month names; dates use genitive forms. Coverage tests check commands/tools, scanned literal and direct lookups, and source/context keys in the other bundled catalogs. Calendar, inspector and password-dialog UI regressions check dynamic labels and preserve document values. A native Lithuanian-speaking contributor has reviewed the translation.
|
||||
|
||||
The Vietnamese catalog has 2,591 entries and one plural form (`plural_none`). Preferences theme `Appearance` uses context `theme` → `Giao diện`; object/comment/signature appearance stays plain `Diện mạo`. Form fields use `Trường`; redaction apply wording states permanent removal. Calendar months are numeric (`Tháng 1`…`12` / `tháng 1`…`12`). Coverage tests require every current command, UI literal, bundled key and credit label. A native Vietnamese-speaking contributor has reviewed the translation. Status stays `partial`: no translated help, and engine/OS details stay English inside a translated frame.
|
||||
|
||||
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. 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.
|
||||
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
# PdfCraft parity with Adobe Acrobat Pro
|
||||
|
||||
> **Last reviewed:** 2026-10-10 · **Last updated:** 2026-10-12 · **Change:** web printing shipped, gap 4 closed, scores not re-judged; exclusive image-crop resize follow-up; minor (document-font subset completion, source styles and curved/shared image clips in the contributor fork; closed issues synced, scores not re-judged: #774, #756, #740, #759, #799–#821, #825, #844; earlier: readiness table with hours per audience; full number restated as the standard's additive weighted sum, 47% → 49%; earlier: second look: cross-cutting double count removed, mainstream-practitioner number added, core-workflow gate applied → alpha) · **Target:** Adobe Acrobat Pro (Acrobat DC, continuous track 26.002.21931, macOS)
|
||||
> **Last reviewed:** 2026-10-10 · **Last updated:** 2026-10-13 · **Change:** minor (localization ≈59% → ≈68%; weighted readiness 49.25 → 49.7 ≈ 50%) · **Target:** Adobe Acrobat Pro (Acrobat DC, continuous track 26.002.21931, macOS)
|
||||
|
||||
The authoritative assessment of how close PdfCraft is to Acrobat Pro. [ROADMAP.md](../ROADMAP.md)
|
||||
summarizes it; [gaps.md](gaps.md) lists every shortfall one by one; the deep-dive checklists are
|
||||
@@ -21,7 +21,7 @@ Approved Trust List) are out of scope: 23 such features are tracked as `na` in
|
||||
| Feature breadth, unweighted | 56.9% (423 shipped + 74 partial of 808) | measured (`cargo xtask parity` arithmetic) |
|
||||
| Must-have (P0) features | 94.2% (226 + 23 partial of 252) | measured |
|
||||
| Feature breadth, mainstream practitioner | ≈ 70% tier-weighted (63.2% unweighted: 292 + 44 partial of 497; P0 95.3%) | measured ([split below](#mainstream-practitioner-vs-full-acrobat-pro)) |
|
||||
| **Ready for real work**, full Acrobat Pro | **≈ 49%** (range 45–53%) | estimated (weighted sum over dimensions, [below](#by-dimension)) |
|
||||
| **Ready for real work**, full Acrobat Pro | **≈ 50%** (range 45–53%) | estimated (weighted sum over dimensions, [below](#by-dimension)) |
|
||||
| **Ready for real work**, mainstream practitioner | **≈ 55%** (range 50–60%) | estimated ([split below](#mainstream-practitioner-vs-full-acrobat-pro)) |
|
||||
| **Ready for real work**, essentials user | **≈ 66%** (range 60–70%) | estimated ([below](#essentials-user)) |
|
||||
| **Stage** | **alpha** (passes the core-workflow gate) | see [Stage](#stage) |
|
||||
@@ -32,7 +32,7 @@ Approved Trust List) are out of scope: 23 such features are tracked as `na` in
|
||||
|
||||
| Audience | Ready | Opus 5.5 agent-hours to ≈ 95% | Work that dominates |
|
||||
|---|---|---|---|
|
||||
| Full Acrobat Pro (ready for real work) | ≈ 49% | ≈ 780–1,400 h (≈ 300–550 h wall clock with 3–5 agents; 60–70% parallelizes) | Own renderer and fidelity harness, editing existing content, OCR and Office conversion, Preflight and PDF/A/X/UA, accessibility tagging, signatures for institutions |
|
||||
| Full Acrobat Pro (ready for real work) | ≈ 50% | ≈ 780–1,400 h (≈ 300–550 h wall clock with 3–5 agents; 60–70% parallelizes) | Own renderer and fidelity harness, editing existing content, OCR and Office conversion, Preflight and PDF/A/X/UA, accessibility tagging, signatures for institutions |
|
||||
| Mainstream practitioner | ≈ 55% | ≈ 500–900 h (≈ 200–400 h wall clock; ≈ 60% parallelizes) | Rendering fidelity, editing existing text (CJK, RTL, subsets), conversion (Word layout, Excel, PowerPoint, Office→PDF), OCR languages, the bug backlog |
|
||||
| Essentials user | ≈ 66% | ≈ 180–330 h (≈ 90–180 h wall clock; ≈ 50% parallelizes) | Rendering fidelity on everyday files, launch and GPU stability, simple text fixes, Word export layout, Chinese UI glyphs |
|
||||
|
||||
@@ -86,8 +86,8 @@ The **full ready-for-real-work number is an additive weighted sum over the dimen
|
||||
method; weights by what decides whether a professional can switch): features 40%, file formats and
|
||||
conversion 12%, UI/UX 10%, PDF specification 8%, stability 8%, performance 6%, localization 5%,
|
||||
platforms 5%, hardware 4%, ecosystem 1%, AI 1%:
|
||||
0.40 × 49 + 0.12 × 40 + 0.10 × 55 + 0.08 × 60 + 0.08 × 50 + 0.06 × 40 + 0.05 × 59 + 0.05 × 70 +
|
||||
0.04 × 35 + 0.01 × 30 + 0.01 × 0 = 49.25 → **≈ 49%**.
|
||||
0.40 × 49 + 0.12 × 40 + 0.10 × 55 + 0.08 × 60 + 0.08 × 50 + 0.06 × 40 + 0.05 × 68 + 0.05 × 70 +
|
||||
0.04 × 35 + 0.01 × 30 + 0.01 × 0 = 49.7 → **≈ 50%**.
|
||||
|
||||
History: the first version of this file subtracted 9 points from the area figure for cross-cutting
|
||||
shortfalls (40%); the second look found that a double count (Windows printing, encrypted-output
|
||||
@@ -181,13 +181,13 @@ features total and are shown for orientation; the others add to it.
|
||||
| File formats and conversion | ≈ 40% | 12% | 120–220 | within features | PDF read/write strong, encrypted output included (#774 fixed); export Word is layout-poor (#773); no Excel/PowerPoint export, no Office/HTML/PostScript import | [file-format-parity.md](file-format-parity.md) |
|
||||
| PDF specification and standards | ≈ 60% | 8% | 150–260 | within features | ISO 32000 syntax, encryption, annotations and AcroForm strong; rendering and fonts borrowed from `hayro`; PDF 2.0 extras, linearization, public-key security absent; PDF/A partial, PDF/X/UA none | [pdf-spec-parity.md](pdf-spec-parity.md) |
|
||||
| Hardware | ≈ 35% | 4% | 25–50 | additional | Page raster is CPU only (Acrobat has GPU rendering); Windows CNG and macOS Keychain keys sign, no PKCS #11; no scanners; web printing goes through the browser's print dialog as pictures, not vectors | [hardware-parity.md](hardware-parity.md) |
|
||||
| Localization | ≈ 59% over the 12 key languages | 5% | 35–60 | additional | 15 catalogs, 13 at 94–100%; Chinese UI shows tofu in releases (#826, #688); Arabic not mirrored; no Hindi, Indonesian, Korean, Vietnamese | [localization-parity.md](localization-parity.md) |
|
||||
| Localization | ≈ 68% over the 12 key languages | 5% | 35–60 | additional | 22 catalogs + English (23 interface languages); Chinese UI shows tofu in releases (#826, #688); Arabic not mirrored; no Hindi, Indonesian, Korean | [localization-parity.md](localization-parity.md) |
|
||||
| Performance | ≈ 40% | 6% | 25–45 | additional | 500-page smooth scrolling and large-file memory work (#307) done; no performance budgets (`misc.performance-budgets`), no lazy loading of GB files; blurry text at 1080p/1440p (#730) | [gaps.md](gaps.md) |
|
||||
| Stability | ≈ 50% | 8% | 45–80 | additional | Never-crash rules and nightly fuzzing; 245 open issues on 2026-10-10, many of them `fix(...)` reports of wrong results in shipped features (the #799–#821 batch is fixed) | [gaps.md](gaps.md) |
|
||||
| Platforms | ≈ 70% | 5% | 15–30 | additional | macOS (signed, notarized), Windows x64/x86/ARM64 (signed), Linux (AppImage, deb, rpm, Flatpak), FreeBSD, web (WASM). Acrobat has no Linux, FreeBSD or full web editor; we lack web OCR and web recovery | [hardware-parity.md](hardware-parity.md) |
|
||||
| Ecosystem and plug-ins | ≈ 30% | 1% | 10–20 | within features | No plug-in SDK or folder-level JavaScript; Action Wizard partial. Agent automation (CLI, MCP, UI control channel) has no Acrobat equivalent | [gaps.md](gaps.md) |
|
||||
| AI features | ≈ 0% (in scope) | 1% | 20–40 | within features | Acrobat's AI Assistant is cloud-only (out of scope). Local, opt-in provider interface (`misc.ai-provider`) not started | [gaps.md](gaps.md) |
|
||||
| **Total (weighted sum)** | **≈ 49%** | 100% | **≈ 780–1,400** | | | |
|
||||
| **Total (weighted sum)** | **≈ 50%** | 100% | **≈ 780–1,400** | | | |
|
||||
|
||||
## By feature area
|
||||
|
||||
@@ -222,7 +222,7 @@ with evidence when next touched.
|
||||
|
||||
## Stage
|
||||
|
||||
**alpha.** Ready for real work, ≈ 49%, is inside the 40–75% band, and every one of Acrobat Pro's
|
||||
**alpha.** Ready for real work, ≈ 50%, is inside the 40–75% band, and every one of Acrobat Pro's
|
||||
six everyday workflows can be completed end to end on macOS. Under the gate rule shared by all the
|
||||
Crafting Apps, a workflow blocks only when a typical user can't complete it at all; lossy exchange
|
||||
or a missing sub-case is a beta item. **Converting and sharing** is therefore partial, not
|
||||
@@ -241,7 +241,7 @@ stage set to pre-alpha; see the revision history.)
|
||||
| Edit text and images | partial, not blocking (Latin text with embedded or standard fonts) |
|
||||
| Convert and share (Office ↔ PDF, password protection) | partial, not blocking (Office→PDF and Word layout #773 are beta items; encryption interop #774 fixed) |
|
||||
|
||||
**To reach beta** (≈ 75% ready, no blocking gap in PDF interchange), ≈ 26 points and
|
||||
**To reach beta** (≈ 75% ready, no blocking gap in PDF interchange), ≈ 25 points and
|
||||
**≈ 400–700 agent-hours**:
|
||||
|
||||
| Beta requirement | Hours |
|
||||
|
||||
Reference in New Issue
Block a user