From 08df188e1a4cbb5fb21b84b6354158e067cca057 Mon Sep 17 00:00:00 2001 From: bflatastic Date: Mon, 12 Oct 2026 05:40:35 +0700 Subject: [PATCH] M9: Latin American Spanish overlay - `crates/ui-egui/locales/es-419.toml`: Latin American Spanish as an overlay on `es.toml`. It lists only the 87 entries (61 UI strings, 26 command labels) whose wording differs in Latin American usage: agregar for anadir, presionar for pulsar, configuracion for settings, Enter for the Intro key, control for a gamepad, resorte for a spring. Everything else reads `es.toml`, then English. Same tu register as `es.toml`. Marked as a draft awaiting native review (not in `COMPLETE`). - Fallback chain: a regional catalog whose language has a catalog overlays it. `build.rs` bakes a `parent` index into each `Lang` (es-419 -> es; others -> English), and `i18n::chain` walks overlay -> base -> English for UI strings, command labels and menu wordings. Catalogs are now sorted by code, so the picker lists Espanol before Espanol (Latinoamerica). - System detection: es-419, es-MX, es-AR, es-CO, es-US and the other Latin American tags (also `es_CO.UTF-8`, `es-Latn-MX`) resolve to es-419; es, es-ES and other regions stay es. - Preferences > Language shows "Espanol (Latinoamerica)" (in Spanish spelling) with the draft note. - `pref.language` params doc and `docs/mcp.md` list es-419. - Docs: localization-parity (overlay section, language preference, Spanish row, total), translating (overlays, detection, wording), target-app-parity, ROADMAP, gaps (gap 5), roadmap (focus item 4). Localization 35% -> 36%, 32-55 h -> 31-54 h; ready for real work unchanged (~53%); full parity ~268-528 h -> ~267-527 h. --- ROADMAP.md | 17 ++--- crates/engine/src/cmd/view.rs | 2 +- crates/ui-egui/build.rs | 24 ++++--- crates/ui-egui/locales/es-419.toml | 101 +++++++++++++++++++++++++++++ crates/ui-egui/src/i18n.rs | 29 +++++++-- crates/ui-egui/src/i18n_tests.rs | 30 +++++++++ docs/gaps.md | 16 +++-- docs/localization-parity.md | 43 ++++++------ docs/mcp.md | 2 +- docs/roadmap.md | 5 +- docs/target-app-parity.md | 11 ++-- docs/translating.md | 16 +++-- 12 files changed, 233 insertions(+), 63 deletions(-) create mode 100644 crates/ui-egui/locales/es-419.toml diff --git a/ROADMAP.md b/ROADMAP.md index 567bc318..bc679a50 100644 --- a/ROADMAP.md +++ b/ROADMAP.md @@ -2,7 +2,7 @@ **Stage: alpha** · next: beta, ~26 points and ~162–292 h away (one Opus 5.5 agent; ~33–67 h wall-clock with 5 agents) -> **Last reviewed:** 2026-10-10 · **Last updated:** 2026-10-12 · **Change:** minor (prototyping tail: gamepad triggers, widgets in prototypes, mode lookups; plugin directory on figcraft-server; Japanese and Korean draft catalogs and CJK UI fonts; plugin widgets and the plugin panel tail; French, German and Brazilian Portuguese draft catalogs; cursor chat and spotlight in live sessions; layers and assets panels behaviour suite and fixes; vector multi-edit and edit tail; dev resources links; extended collections tail; branch version history and finer list merges; headline percentages recomputed from the area table: feature breadth ~84%, core ~94%, mainstream ~50%; text lists, hanging punctuation and decoration styles; comments and version history behaviour suite and fixes; canvas navigation, guides and grids behaviour suite and fixes; export and inspect behaviour suite and fixes; .fig import: editable vector networks and more layer kinds; shapes and vector behaviour suite and fixes; paint and effects behaviour suite and fixes; frames, groups, sections and masks behaviour suite and fixes; styles and variables behaviour suite and fixes; prototyping behaviour suite and fixes; components behaviour suite and fixes; embedded image colour profiles; layers panel keyboard: F2 rename, Shift+F10 context menu, keyboard box selection, 10k-layer QA; per-side stroke weight variables; text behaviour suite: CJK line breaking, metrics, vertical trim; selection and transform behaviour suite and fixes; `.fig` import, best effort; Windows QA pass with eight fixes; auto layout behaviour suite and fixes; Display P3: gamut warning and soft-proof (true P3 blocked upstream); several windows; localization layer with a draft Spanish catalog; hosting figcraft-server: wss in the desktop app, server releases, deployment guide; plugin panels and settings; canvas fixes: boolean hit testing on the result, group drags, frame outlines and slices; code connect; stretch and scatter brushes, pen pressure; text on a path and pattern fills; team libraries on the server and swap library; branches: create, update from main, review and merge; animated GIF fills and TIFF images; Open recent, outlines for the selection and mask outlines: menu breadth 100%; extended collections, variables import/export, gradient stop and variant bindings; feature breadth ~80%; ready ~49%) · **Target:** Figma Design (desktop app 126.10.7) +> **Last reviewed:** 2026-10-10 · **Last updated:** 2026-10-12 · **Change:** minor (Latin American Spanish overlay; prototyping tail: gamepad triggers, widgets in prototypes, mode lookups; plugin directory on figcraft-server; Japanese and Korean draft catalogs and CJK UI fonts; plugin widgets and the plugin panel tail; French, German and Brazilian Portuguese draft catalogs; cursor chat and spotlight in live sessions; layers and assets panels behaviour suite and fixes; vector multi-edit and edit tail; dev resources links; extended collections tail; branch version history and finer list merges; headline percentages recomputed from the area table: feature breadth ~84%, core ~94%, mainstream ~50%; text lists, hanging punctuation and decoration styles; comments and version history behaviour suite and fixes; canvas navigation, guides and grids behaviour suite and fixes; export and inspect behaviour suite and fixes; .fig import: editable vector networks and more layer kinds; shapes and vector behaviour suite and fixes; paint and effects behaviour suite and fixes; frames, groups, sections and masks behaviour suite and fixes; styles and variables behaviour suite and fixes; prototyping behaviour suite and fixes; components behaviour suite and fixes; embedded image colour profiles; layers panel keyboard: F2 rename, Shift+F10 context menu, keyboard box selection, 10k-layer QA; per-side stroke weight variables; text behaviour suite: CJK line breaking, metrics, vertical trim; selection and transform behaviour suite and fixes; `.fig` import, best effort; Windows QA pass with eight fixes; auto layout behaviour suite and fixes; Display P3: gamut warning and soft-proof (true P3 blocked upstream); several windows; localization layer with a draft Spanish catalog; hosting figcraft-server: wss in the desktop app, server releases, deployment guide; plugin panels and settings; canvas fixes: boolean hit testing on the result, group drags, frame outlines and slices; code connect; stretch and scatter brushes, pen pressure; text on a path and pattern fills; team libraries on the server and swap library; branches: create, update from main, review and merge; animated GIF fills and TIFF images; Open recent, outlines for the selection and mask outlines: menu breadth 100%; extended collections, variables import/export, gradient stop and variant bindings; feature breadth ~80%; ready ~49%) · **Target:** Figma Design (desktop app 126.10.7) FigCraft is a clean-room, open-source, pure-Rust reimplementation of the Figma design workflow, native on macOS, Windows, Linux and FreeBSD and in the browser, fully drivable by agents through @@ -19,13 +19,13 @@ start with [`docs/gaps.md`](docs/gaps.md). | Essentials user | **~57%** | estimated: core features 77.8% × 0.92 launch/stability × 0.92 discoverability × 0.88 opening files people send ([method](docs/target-app-parity.md#essentials-user)) | | Mainstream practitioner | **~49%** | estimated: weekly areas 72.6% × 0.9 interaction × 0.9 stability × 0.85 file exchange with Figma users ([method](docs/target-app-parity.md#mainstream-practitioner)) | | Remaining to beta | **~155–280 h** one agent | estimated | -| Remaining to full parity | **~265–523 h** one agent (~83–145 h wall-clock with 5 agents) | estimated | +| Remaining to full parity | **~264–522 h** one agent (~83–145 h wall-clock with 5 agents) | estimated | ### Readiness by audience | Audience | Ready % | Opus 5.5 agent wall-clock hours to ~95% | Work that dominates | |---|---|---|---| -| Full target (ready for real work) | ~53% | ~271–526 h | multiplayer (42–84), localization (32–55), plugins (8–26), the interaction pass (40–60), GPU canvas (30–60), AI (30–60), the `.fig` import's tail (5–10) | +| Full target (ready for real work) | ~53% | ~270–525 h | multiplayer (42–84), localization (31–54), plugins (8–26), the interaction pass (40–60), GPU canvas (30–60), AI (30–60), the `.fig` import's tail (5–10) | | Mainstream practitioner | ~49% | ~130–244 h | core editor depth incl. the `.fig` import's tail (49–98), the interaction pass (40–60), real-file corpus and platform QA (25–50), `.fig` export for handing work back (15–30) | | Essentials user | ~57% | ~60–110 h | the `.fig` import's tail so the files people send open well (5–10), core tool polish (25–45), interaction pass on the core tools (15–25), stability QA (10–20), onboarding and starter templates (5–10) | @@ -63,7 +63,7 @@ native speakers' reviews of the six language drafts need people. | Stability | 55% | 20–40 | [gaps](docs/gaps.md) | | Performance | 60% | 30–60 | [hardware-parity](docs/hardware-parity.md) | | Hardware | 60% | 10–21 | [hardware-parity](docs/hardware-parity.md) | -| Localization | 35% (layer; English; Spanish, French, German, Brazilian Portuguese, Japanese, Korean drafts; CJK UI fonts) | 32–55 | [localization-parity](docs/localization-parity.md) | +| Localization | 36% (layer; English; Spanish with a Latin American overlay, French, German, Brazilian Portuguese, Japanese, Korean drafts; CJK UI fonts) | 31–54 | [localization-parity](docs/localization-parity.md) | | Platforms | 75% | 10–20 | [hardware-parity](docs/hardware-parity.md) | | Ecosystem / plugins | 38% (own WASM plugins with panels, settings and widgets; a self-hosted plugin directory) | 8–26 | [gaps](docs/gaps.md) | | AI features | 0% | 30–60 | [gaps](docs/gaps.md) | @@ -100,7 +100,7 @@ native speakers' reviews of the six language drafts need people. |---|---|---|---| | English | en | 100% | full | | Simplified Chinese | zh-Hans | 0% | none | -| Spanish | es | 100% | draft (needs native review) | +| Spanish | es, es-419 | 100% (es-419: an overlay of 87 entries) | draft (needs native review) | | Hindi | hi | 0% | none | | Arabic | ar | 0% | none | | French | fr | 100% | draft (needs native review) | @@ -112,9 +112,9 @@ native speakers' reviews of the six language drafts need people. | Vietnamese | vi | 0% | none | No other languages. Figma ships English, Japanese, Korean, French, German, Spanish and Portuguese -(Brazil); FigCraft has the localization layer, English and Spanish, French, German, Brazilian -Portuguese, Japanese and Korean drafts (Preferences ▸ Language; CJK UI text uses an installed CJK -font, loaded only when needed; adding a language: +(Brazil); FigCraft has the localization layer, English and Spanish (Spain and Latin America), +French, German, Brazilian Portuguese, Japanese and Korean drafts (Preferences ▸ Language; CJK UI +text uses an installed CJK font, loaded only when needed; adding a language: [`docs/translating.md`](docs/translating.md)). Document text in any script (bidi, Arabic shaping, IME) already works. Detail: [`docs/localization-parity.md`](docs/localization-parity.md). @@ -236,6 +236,7 @@ Ranked; detail and milestones in [`docs/roadmap.md`](docs/roadmap.md). | Date | Change | Summary | |---|---|---| +| 2026-10-12 | minor | Latin American Spanish overlay (gap 5): `es-419.toml` lists only the 87 entries (61 UI strings, 26 command labels) whose wording differs in Latin American usage (agregar for añadir, presionar for pulsar, configuración for settings, Enter for the Intro key, control for a gamepad, resorte for a spring) and reads `es.toml`, then English, for the rest (a regional catalog overlays its language's); same tú register as Spanish; a draft awaiting native review. Preferences ▸ Language lists Español (Latinoamérica); System maps es-419, es-MX, es-AR, es-CO and the other Latin American and US Spanish tags to it, es-ES and es to Spanish. Localization 35% → 36%, 32–55 h → 31–54 h; ready for real work unchanged (~53%); full parity ~268–528 h → ~267–527 h | | 2026-10-12 | minor | Prototyping tail (gap 15, the part that doesn't need video): key/gamepad triggers take controller buttons (`Gamepad A` … `Gamepad Home`, the standard layout's 17 buttons, from a Controller menu beside the key field; the REST import maps controller triggers instead of skipping them); the player reads controllers on Windows (Windows.Gaming.Input through the `windows` crate the app already used; no new dependency) and in the browser (the Gamepad API), polling only while the prototype has such a trigger and the window has the focus, and the control channel presses buttons (`ui.input {gamepad}`); macOS, Linux and FreeBSD read no controllers yet. Widgets react to clicks in a presented prototype (the canvas's callback and sandbox limits, on the prototype's copy: the file and its history don't change, restarting starts over). `proto.evaluate` computes an expression where a layer's interaction would run (MCP); the mode lookup and set-variable-mode rules are cases in a behaviour suite (they already held). Prototyping breadth 90% → 92%, ready unchanged at 74%, 3–6 h → 2–5 h; area hours 120–260 → 119–259 (core 33–75 → 32–74), Features (depth) 29–68 h → 28–67 h; percentages unchanged at this rounding; full parity ~276–538 h → ~275–537 h | | 2026-10-12 | minor | Plugin directory on `figcraft-server` (gap 6): the server keeps plugin bundles (manifest, module, up to 32 assets; an uncompressed length-prefixed format named by its SHA-256) published with the server key (`figcraft-cli plugin publish`, `plugin.directory.publish`), checked in full before they are kept (size cap, manifest and plugin API version, a version newer than the one kept, the module compiled but never run, plain asset names) and capped (`max_plugins`, `max_plugin_mb`, `max_plugins_mb`); anyone who reaches the server lists, searches and fetches them (hash-checked again on read, a shared download budget); the operator moderates with `figcraft-server plugins [remove]`. The app's Plugins ▸ Manage plugins gains a Browse tab: a server's plugins with Install / Update / Remove, an install confirmation that shows the permissions asked for, the source and the SHA-256, the download checked against the listed hash before anything is read, plugins kept per user in hash-named folders; `plugin.directory.list` / `install` (needs `confirm: true`) / `publish` / `unpublish` for scripts and MCP. Not in the browser build yet; no public catalogue, publisher accounts, signatures, ratings or background update checks. Plugins & widgets 55%/32% → 60%/35%, 10–30 h → 8–26 h; Ecosystem dimension 34% → 38%; weighted totals unchanged at this rounding; full parity ~281–548 h → ~279–544 h | | 2026-10-12 | minor | Japanese and Korean draft catalogs and CJK UI fonts (gap 5): `ja.toml` and `ko.toml` translate every UI string (1,351 keys; plurals give only `other`, and `plural_one` never picks "one" in either), command label (519) and menu wording (29), machine-drafted and marked as drafts awaiting native review; Preferences ▸ Language lists 日本語 and 한국어, System picks ja* and ko*. CJK UI text renders: once the UI language, the open file's names or comments, typed text or the language picker needs Japanese kana, Hangul or Han, the desktop app appends one installed face per script to egui's fallbacks (Windows Yu Gothic UI / Meiryo / Malgun Gothic / Microsoft YaHei, macOS Hiragino Sans / Apple SD Gothic Neo / PingFang SC, Linux Noto Sans CJK; the craft-fonts build's BIZ UDPGothic first), reading only that face's file and sharing its bytes with egui (English users load none: startup and memory unchanged); the web shows Japanese and Korean only when built with craft-fonts' Noto Sans CJK (`FIGCRAFT_EMBED_CJK`), else it keeps them out of the picker and System. Localization 30% → 35%, 40–65 h → 32–55 h; ready for real work ~52% → ~53% (52.9 → 53.1); full parity ~284–554 h → ~276–544 h; beta ~162–292 h → ~155–280 h | diff --git a/crates/engine/src/cmd/view.rs b/crates/engine/src/cmd/view.rs index 21f0cb13..a9a04f12 100644 --- a/crates/engine/src/cmd/view.rs +++ b/crates/engine/src/cmd/view.rs @@ -272,7 +272,7 @@ pub fn specs() -> Vec { s.prefs.user_name = if name.is_empty() { super::comment::DEFAULT_AUTHOR.into() } else { name }; Ok(json!({"name": s.prefs.user_name})) }), - spec!("pref.language", "Interface language", "{language: system|en|es|fr|de|pt-BR|ja|ko|… (no catalog: English)}", false, |s, p| { + spec!("pref.language", "Interface language", "{language: system|en|es|es-419|fr|de|pt-BR|ja|ko|… (no catalog: English)}", false, |s, p| { let lang = req_string("pref.language", p, "language")?; let lang = lang.trim(); // A BCP 47-style tag ("es", "pt-BR", "zh_Hans") or "system". diff --git a/crates/ui-egui/build.rs b/crates/ui-egui/build.rs index e8de6f5b..368ea626 100644 --- a/crates/ui-egui/build.rs +++ b/crates/ui-egui/build.rs @@ -39,21 +39,29 @@ fn locales() -> String { println!("cargo::rerun-if-changed={}", dir.display()); let mut files: Vec<_> = std::fs::read_dir(&dir).into_iter().flatten().flatten().map(|e| e.path()).filter(|p| p.extension().is_some_and(|x| x == "toml")).collect(); - files.sort_by_key(|p| (p.file_stem().is_none_or(|s| s != "en"), p.clone())); + // By code, so "es" comes before "es-419". + files.sort_by_key(|p| (p.file_stem().is_none_or(|s| s != "en"), p.file_stem().map(ToOwned::to_owned))); + let langs: Vec<_> = files + .into_iter() + .filter_map(|path| { + let t = read(&path, "the language is left out", |t| t.parse::().map_err(|e| e.to_string()))?; + Some((path.file_stem().map(|s| s.to_string_lossy().into_owned()).unwrap_or_default(), path, t)) + }) + .collect(); let mut o = String::from( "pub static LANGS: &[Lang] = &[ ", ); - for path in files { - let Some(t) = read(&path, "the language is left out", |t| t.parse::().map_err(|e| e.to_string())) else { continue }; - let code = path.file_stem().map(|s| s.to_string_lossy().into_owned()).unwrap_or_default(); + for (code, path, t) in &langs { let meta = |k: &str| t.get("meta").and_then(|m| m.get(k)).and_then(toml::Value::as_str).unwrap_or("").to_string(); - let _ = writeln!(o, " Lang {{ code: {code:?}, name: {:?}, draft: {}, ui: &[", meta("name"), !meta("status").is_empty()); - table(&mut o, &path, t.get("ui")); + // A regional catalog ("es-419") overlays its language's ("es"), if there is one. + let parent = code.split_once('-').and_then(|(base, _)| langs.iter().position(|(c, ..)| c == base)).unwrap_or(0); + let _ = writeln!(o, " Lang {{ code: {code:?}, name: {:?}, draft: {}, parent: {parent}, ui: &[", meta("name"), !meta("status").is_empty()); + table(&mut o, path, t.get("ui")); let _ = writeln!(o, " ], commands: &["); - table(&mut o, &path, t.get("commands")); + table(&mut o, path, t.get("commands")); let _ = writeln!(o, " ], menu: &["); - table(&mut o, &path, t.get("menu")); + table(&mut o, path, t.get("menu")); let _ = writeln!(o, " ] }},"); } o.push_str( diff --git a/crates/ui-egui/locales/es-419.toml b/crates/ui-egui/locales/es-419.toml new file mode 100644 index 00000000..e0feb12c --- /dev/null +++ b/crates/ui-egui/locales/es-419.toml @@ -0,0 +1,101 @@ +# FigCraft interface strings: Latin American Spanish. +# +# Draft — needs native review. An overlay on es.toml: it lists only the entries whose wording +# differs in Latin American usage (agregar for añadir, presionar for pulsar, configuración for +# settings, Enter for the Intro key, control for a gamepad, resorte for a spring); everything else +# reads es.toml (then English). Same tú register as es.toml. See docs/translating.md. + +[meta] +name = "Español (Latinoamérica)" +status = "draft — needs native review" + +[ui] +"A collection of this file with these modes and variables: override their values per mode and add variables of its own" = "Una colección de este archivo con estos modos y variables: modifica sus valores por modo y agrega variables propias" +"A spring plays until it settles: its mass, stiffness and damping set how long" = "Un resorte se reproduce hasta que se asienta: su masa, rigidez y amortiguación fijan cuánto dura" +"Add a category" = "Agregar una categoría" +"Add a comment" = "Agrega un comentario" +"Add a description" = "Agregar una descripción" +"Add a frame (F) or a shape (R) to get started" = "Agrega un marco (F) o una forma (R) para empezar" +"Add a layer or component to tile first" = "Agrega antes una capa o un componente para el mosaico" +"Add a note for developers" = "Agrega una nota para desarrollo" +"Add action" = "Agregar acción" +"Add actions for when no condition holds" = "Agregar acciones para cuando no se cumpla ninguna condición" +"Add an annotation for developers" = "Agrega una anotación para desarrollo" +"Add another condition" = "Agregar otra condición" +"Add auto layout" = "Agregar diseño automático" +"Add background behind overlay" = "Agregar fondo tras la superposición" +"Add content to this slot" = "Agregar contenido a esta ranura" +"Add dev resource" = "Agregar recurso de desarrollo" +"Add effect" = "Agregar efecto" +"Add export setting" = "Agregar configuración de exportación" +"Add fill" = "Agregar relleno" +"Add interaction" = "Agregar interacción" +"Add layout grid" = "Agregar cuadrícula de diseño" +"Add library" = "Agregar biblioteca" +"Add library file…" = "Agregar archivo de biblioteca…" +"Add max height" = "Agregar alto máximo" +"Add max width" = "Agregar ancho máximo" +"Add min height" = "Agregar alto mínimo" +"Add min width" = "Agregar ancho mínimo" +"Add mode" = "Agregar modo" +"Add new page" = "Agregar página nueva" +"Add or update collections from a variables JSON" = "Agregar o actualizar colecciones desde un JSON de variables" +"Add properties to make instances configurable" = "Agrega propiedades para que las instancias sean configurables" +"Add reaction" = "Agregar reacción" +"Add starting point" = "Agregar punto de inicio" +"Add stroke" = "Agregar trazo" +"Add variant" = "Agregar variante" +"Add variant with a new value" = "Agregar variante con un valor nuevo" +"Added" = "Agregado" +"Added the library \"{name}\"" = "Biblioteca \"{name}\" agregada" +"Binary assets added" = "Recursos binarios agregados" +"Drag the stroke to add a width point" = "Arrastra el trazo para agregar un punto de ancho" +"Effect settings" = "Configuración del efecto" +"Enter to save · Esc to cancel" = "Enter para guardar · Esc para cancelar" +"Export settings" = "Configuración de exportación" +"Key/Gamepad" = "Tecla/Control" +"Key/Gamepad {key}" = "Tecla/Control {key}" +"Layout grid settings" = "Configuración de la cuadrícula de diseño" +"Lines added" = "Líneas agregadas" +"No assets: no export settings, icons or images. Add export settings in the Design tab." = "No hay recursos: ni configuraciones de exportación, ni iconos, ni imágenes. Agrega configuraciones de exportación en la pestaña Diseño." +"No components yet. Select layers and press {keys}." = "Aún no hay componentes. Selecciona capas y presiona {keys}." +"No libraries yet. Add a .figcraft file that was published as a library, or paste a team library's link." = "Aún no hay bibliotecas. Agrega un archivo .figcraft publicado como biblioteca o pega el enlace de una biblioteca de equipo." +"PDF print settings" = "Configuración de impresión PDF" +"Press a key…" = "Presiona una tecla…" +"Prototype settings" = "Configuración del prototipo" +"Remove export setting" = "Quitar configuración de exportación" +"Replace mixed export settings" = "Reemplazar configuraciones de exportación mixtas" +"Select a frame and add a starting point to present it as a flow." = "Selecciona un marco y agrega un punto de inicio para presentarlo como flujo." +"Selection box, {w} by {h} at {x}, {y}: arrows move it, Shift+arrows size it, Enter selects, Escape cancels" = "Cuadro de selección, {w} por {h} en {x}, {y}: las flechas lo mueven, Mayús+flechas cambian su tamaño, Enter selecciona, Esc cancela" +"Selection box: arrows move it, Shift+arrows size it, Enter selects, Escape cancels" = "Cuadro de selección: las flechas lo mueven, Mayús+flechas cambian su tamaño, Enter selecciona, Esc cancela" +"Type settings" = "Configuración de texto" +"With the layers' export settings" = "Con la configuración de exportación de las capas" +"{n} added" = { one = "{n} agregada", other = "{n} agregadas" } + +[commands] +"annotation.add" = "Agregar anotación" +"annotation.addCategory" = "Agregar categoría de anotación" +"comment.add" = "Agregar comentario" +"component.addProperty" = "Agregar propiedad de componente" +"component.addVariant" = "Agregar variante" +"devResource.add" = "Agregar recurso de desarrollo" +"effect.add" = "Agregar efecto" +"export.add" = "Agregar configuración de exportación" +"export.remove" = "Quitar configuración de exportación" +"export.update" = "Editar configuración de exportación" +"fill.add" = "Agregar relleno" +"grid.add" = "Agregar cuadrícula de diseño" +"guide.add" = "Agregar guía" +"layout.add" = "Agregar diseño automático" +"library.add" = "Agregar biblioteca" +"page.add" = "Agregar página" +"proto.action.add" = "Agregar acción de prototipo" +"proto.conditional.addBlock" = "Agregar si no, si / si no" +"proto.flow.add" = "Agregar punto de inicio de flujo" +"proto.interaction.add" = "Agregar interacción" +"proto.settings" = "Configuración del prototipo" +"select.add" = "Agregar a la selección" +"stroke.add" = "Agregar trazo" +"variable.addMode" = "Agregar modo" +"vector.edit.addVertex" = "Agregar punto" +"widget.click" = "Hacer clic en el widget" diff --git a/crates/ui-egui/src/i18n.rs b/crates/ui-egui/src/i18n.rs index e4e3fb40..0ebe2c83 100644 --- a/crates/ui-egui/src/i18n.rs +++ b/crates/ui-egui/src/i18n.rs @@ -24,6 +24,9 @@ pub struct Lang { pub name: &'static str, /// Machine-drafted, waiting for a native speaker's review (`[meta] status`). pub draft: bool, + /// Index into [`LANGS`] of the catalog a regional one overlays ("es" under "es-419": it lists + /// only what differs), else 0 (English). + pub parent: usize, /// UI strings by English text, sorted. pub ui: &'static [(&'static str, Text)], /// Command labels by command id, sorted. @@ -60,24 +63,37 @@ fn resolve(pref: &str, system: Option<&str>) -> usize { want.split(',').find_map(resolve_one).unwrap_or(0) } +/// Regions whose Spanish reads the Latin American catalog (es-419): Latin America and the US. +const LATIN_AMERICA: &[&str] = + &["419", "AR", "BO", "CL", "CO", "CR", "CU", "DO", "EC", "GT", "HN", "MX", "NI", "PA", "PE", "PR", "PY", "SV", "US", "UY", "VE"]; + /// The [`LANGS`] index for one tag: the exact tag ("pt-BR"), else its base language ("es" for -/// "es_ES.UTF-8"), else a regional catalog of that language ("pt-BR" for "pt-PT"). A language the -/// UI can't draw (CJK on the web without CJK faces built in) doesn't count. +/// "es_ES.UTF-8"), else a regional catalog of that language ("pt-BR" for "pt-PT"); Latin American +/// Spanish ("es-MX") reads es-419. A language the UI can't draw (CJK on the web without CJK faces +/// built in) doesn't count. fn resolve_one(want: &str) -> Option { let tag = want.trim().split(['.', '@']).next().unwrap_or("").replace('_', "-"); let base = tag.split('-').next().unwrap_or(""); + let has = |subtags: &[&str]| tag.split('-').skip(1).any(|t| subtags.iter().any(|s| s.eq_ignore_ascii_case(t))); let find = |t: &str| LANGS.iter().position(|l| l.code.eq_ignore_ascii_case(t)); + let alias = (base.eq_ignore_ascii_case("es") && has(LATIN_AMERICA)).then(|| find("es-419")).flatten(); let regional = || LANGS.iter().position(|l| !base.is_empty() && l.code.split('-').next().is_some_and(|b| b.eq_ignore_ascii_case(base))); - find(&tag).or_else(|| find(base)).or_else(regional).filter(|i| LANGS.get(*i).is_some_and(|l| crate::cjk::can_show(l.code))) + alias.or_else(|| find(&tag)).or_else(|| find(base)).or_else(regional).filter(|i| LANGS.get(*i).is_some_and(|l| crate::cjk::can_show(l.code))) } fn find(table: &'static [(&'static str, Text)], key: &str) -> Option<&'static Text> { table.binary_search_by(|(k, _)| (*k).cmp(key)).ok().and_then(|i| table.get(i)).map(|(_, t)| t) } -/// `key`'s entry in language `lang`, else in English. +/// Language `lang`, the catalog it overlays ("es" under "es-419"), then English. +fn chain(lang: usize) -> impl Iterator { + let parent = LANGS.get(lang).map_or(0, |l| l.parent); + [lang, parent, 0].into_iter().filter_map(|i| LANGS.get(i)) +} + +/// `key`'s entry in language `lang`, else in the catalog it overlays, else in English. fn lookup(lang: usize, key: &str) -> Option<&'static Text> { - [lang, 0].iter().filter_map(|i| LANGS.get(*i)).find_map(|l| find(l.ui, key)) + chain(lang).find_map(|l| find(l.ui, key)) } /// The UI string for `key` (its English text) in the current language. One English text that needs @@ -164,8 +180,7 @@ pub fn menu_item<'a>(id: &str, english: &'a str) -> Cow<'a, str> { } fn label<'a>(menu: bool, id: &str, english: &'a str) -> Cow<'a, str> { - let lang = LANGS.get(CURRENT.load(Ordering::Relaxed)); - let text = lang.and_then(|l| menu.then(|| find(l.menu, id)).flatten().or_else(|| find(l.commands, id))); + let text = chain(CURRENT.load(Ordering::Relaxed)).find_map(|l| menu.then(|| find(l.menu, id)).flatten().or_else(|| find(l.commands, id))); match text { Some(Text::Plain(s) | Text::Plural(_, s)) if english.ends_with('…') && !s.ends_with('…') => Cow::Owned(format!("{s}…")), Some(Text::Plain(s) | Text::Plural(_, s)) => Cow::Borrowed(s), diff --git a/crates/ui-egui/src/i18n_tests.rs b/crates/ui-egui/src/i18n_tests.rs index a86b0fb6..b33ac41e 100644 --- a/crates/ui-egui/src/i18n_tests.rs +++ b/crates/ui-egui/src/i18n_tests.rs @@ -317,6 +317,36 @@ fn system_languages_pick_the_first_one_with_a_catalog() { assert_eq!(code("fr", Some("de-DE")), "fr", "a chosen language beats the system's"); } +#[test] +fn latin_american_spanish_overlays_spanish() { + let code = |pref: &str, system: Option<&str>| LANGS[resolve(pref, system)].code; + for tag in ["es-419", "es-MX", "es-AR", "es_CO.UTF-8", "es-US", "es-Latn-MX"] { + assert_eq!(code("system", Some(tag)), "es-419", "{tag}"); + } + for tag in ["es", "es-ES", "es_ES.UTF-8", "es-GQ"] { + assert_eq!(code("system", Some(tag)), "es", "{tag}"); + } + let es = LANGS.iter().position(|l| l.code == "es").unwrap(); + let la = LANGS.iter().position(|l| l.code == "es-419").unwrap(); + assert_eq!(LANGS[la].parent, es); + assert!(es < la, "the picker lists Español first"); + // Its own wording, else Spanish's, else English's. + assert!(matches!(lookup(la, "Add fill"), Some(Text::Plain("Agregar relleno")))); + assert!(matches!(lookup(la, "Fill"), Some(Text::Plain("Relleno")))); + let undo = |i: usize| chain(i).find_map(|l| find(l.commands, "edit.undo")).map(forms); + assert!(undo(es).is_some() && undo(la) == undo(es), "command labels fall back too"); + // An overlay lists only what differs from the catalog under it. + for lang in LANGS.iter().filter(|l| l.parent != 0) { + let under = &LANGS[lang.parent]; + for (table, base) in [(lang.ui, under.ui), (lang.commands, under.commands), (lang.menu, under.menu)] { + for (k, t) in table { + let same = find(base, k).is_some_and(|b| forms(b) == forms(t)); + assert!(!same, "{}: {k:?} reads as in {}", lang.code, under.code); + } + } + } +} + #[test] fn french_and_portuguese_count_zero_as_singular() { let one = |code: &str, n: usize| plural_one(LANGS.iter().position(|l| l.code == code).unwrap(), n); diff --git a/docs/gaps.md b/docs/gaps.md index b7682f4f..2de79d71 100644 --- a/docs/gaps.md +++ b/docs/gaps.md @@ -1,6 +1,6 @@ # Where FigCraft falls short of Figma -> **Last reviewed:** 2026-10-10 · **Last updated:** 2026-10-12 · **Change:** minor (prototyping tail: gamepad triggers, widgets in prototypes, mode lookups; plugin directory on figcraft-server; Japanese and Korean draft catalogs and CJK UI fonts; plugin widgets and the plugin panel tail; French, German and Brazilian Portuguese draft catalogs; cursor chat and spotlight in live sessions; layers and assets panels behaviour suite and fixes; vector multi-edit and edit tail; dev resources links; extended collections tail; branch version history and finer list merges; comments and version history behaviour suite and fixes; canvas navigation, guides and grids behaviour suite and fixes; `.fig` import: editable vector networks, more layer kinds, variable prototype actions; `.fig` import, best effort: gap 1 down to its tail; Windows QA pass: everyday workflows on Windows 11, eight fixes incl. unsaved work kept after a crash or a forced quit; Display P3: gamut warning and soft-proof (true P3 blocked upstream); several windows; localization layer with a draft Spanish catalog; hosting figcraft-server: wss in the desktop app, server releases, deployment guide; plugin panels and settings; canvas fixes: boolean hit testing on the result, group drags, frame outlines and slices; code connect; stretch and scatter brushes, pen pressure; text on a path and pattern fills; team libraries on the server and swap library; branches: create, update from main, review and merge; animated GIF fills and TIFF images; Open recent, outlines for the selection and mask outlines: menu breadth 100%; extended collections, variables import/export, gradient stop and variant bindings: gap 14 down to its tail) · **Target:** Figma Design (desktop app 126.10.7) +> **Last reviewed:** 2026-10-10 · **Last updated:** 2026-10-12 · **Change:** minor (Latin American Spanish overlay; prototyping tail: gamepad triggers, widgets in prototypes, mode lookups; plugin directory on figcraft-server; Japanese and Korean draft catalogs and CJK UI fonts; plugin widgets and the plugin panel tail; French, German and Brazilian Portuguese draft catalogs; cursor chat and spotlight in live sessions; layers and assets panels behaviour suite and fixes; vector multi-edit and edit tail; dev resources links; extended collections tail; branch version history and finer list merges; comments and version history behaviour suite and fixes; canvas navigation, guides and grids behaviour suite and fixes; `.fig` import: editable vector networks, more layer kinds, variable prototype actions; `.fig` import, best effort: gap 1 down to its tail; Windows QA pass: everyday workflows on Windows 11, eight fixes incl. unsaved work kept after a crash or a forced quit; Display P3: gamut warning and soft-proof (true P3 blocked upstream); several windows; localization layer with a draft Spanish catalog; hosting figcraft-server: wss in the desktop app, server releases, deployment guide; plugin panels and settings; canvas fixes: boolean hit testing on the result, group drags, frame outlines and slices; code connect; stretch and scatter brushes, pen pressure; text on a path and pattern fills; team libraries on the server and swap library; branches: create, update from main, review and merge; animated GIF fills and TIFF images; Open recent, outlines for the selection and mask outlines: menu breadth 100%; extended collections, variables import/export, gradient stop and variant bindings: gap 14 down to its tail) · **Target:** Figma Design (desktop app 126.10.7) Every known shortfall against Figma, one entry each, ranked by how much it stops a Figma user from doing real work in FigCraft. Agents: pick work from the top unless the owner says otherwise. The @@ -18,7 +18,7 @@ No alpha blockers: the alpha gate in [`roadmap.md`](roadmap.md#alpha-gate) passe | 2 | Multiplayer and sharing are a preview, without accounts (**beta blocker**) | Feature | 42–84 | [collaboration-parity](collaboration-parity.md) | | 3 | Nothing checked against real Figma files | Fidelity | 15–30 | [target-app-parity](target-app-parity.md) | | 4 | Interaction fidelity not compared tool by tool | UI/UX | 40–60 | [ui-parity](ui-parity.md) | -| 5 | Localization: English plus unreviewed Spanish, French, German, Brazilian Portuguese, Japanese and Korean drafts | Localization | 32–55 | [localization-parity](localization-parity.md) | +| 5 | Localization: English plus unreviewed Spanish (and Latin American Spanish), French, German, Brazilian Portuguese, Japanese and Korean drafts | Localization | 31–54 | [localization-parity](localization-parity.md) | | 6 | Plugins: widgets take clicks only, a self-hosted directory but no public catalogue; Figma's plugins don't run | Ecosystem | 8–26 | [target-app-parity](target-app-parity.md) | | 7 | CPU canvas where Figma uses the GPU | Hardware/performance | 30–60 | [hardware-parity](hardware-parity.md) | | 8 | Platform QA: Linux and the browser left (Windows pass done 2026-10-11) | Stability/platforms | 10–20 | [hardware-parity](hardware-parity.md) | @@ -102,19 +102,20 @@ No alpha blockers: the alpha gate in [`roadmap.md`](roadmap.md#alpha-gate) passe ### 5. Localization: English plus unreviewed drafts - **Missing:** native reviews of the Spanish, French, German, Brazilian Portuguese, Japanese and - Korean drafts, Latin American Spanish, localized engine messages and number formatting; CJK UI - text on the web without the craft-fonts CJK build. + Korean drafts and the Latin American Spanish overlay, localized engine messages and number + formatting; CJK UI text on the web without the craft-fonts CJK build. - **Evidence (measured from source):** the layer landed 2026-10-11: catalogs in `crates/ui-egui/locales/` (1,351 UI keys plus 519 command labels and 29 menu wordings), Preferences ▸ Language, `pref.language`; `en.toml` is complete, and `es.toml`, `fr.toml`, - `de.toml`, `pt-BR.toml`, `ja.toml` and `ko.toml` (2026-10-12) are complete machine drafts (only - Spanish is held to completeness by the check; the others show English for strings added later). + `de.toml`, `pt-BR.toml`, `ja.toml` and `ko.toml` (2026-10-12) are complete machine drafts, with + `es-419.toml` a Latin American overlay listing only what differs from Spanish (only Spanish is + held to completeness by the check; the others show English for strings added later). CJK UI text uses an installed CJK face the desktop app loads when something on screen needs it (`crates/ui-egui/src/cjk.rs`). Engine error messages and Dev Mode values stay English. - **Impact:** non-English designers get machine drafts nobody has reviewed yet, and English errors. - **Estimate:** 3–7 h per language plus native review, 4–6 h for engine messages and numbers; - 32–55 h for the key languages left and the drafts' follow-ups. Detail in + 31–54 h for the key languages left and the drafts' follow-ups. Detail in [`localization-parity.md`](localization-parity.md); how to add one in [`translating.md`](translating.md). @@ -349,6 +350,7 @@ No alpha blockers: the alpha gate in [`roadmap.md`](roadmap.md#alpha-gate) passe | Date | Change | Summary | |---|---|---| +| 2026-10-12 | minor | Latin American Spanish overlay (gap 5): `es-419.toml` lists only the 87 entries (61 UI strings, 26 command labels) whose wording differs in Latin American usage (agregar for añadir, presionar for pulsar, configuración for settings, Enter for the Intro key, control for a gamepad, resorte for a spring) and reads `es.toml`, then English, for the rest (a regional catalog overlays its language's); same tú register as Spanish; a draft awaiting native review. Preferences ▸ Language lists Español (Latinoamérica); System maps es-419, es-MX, es-AR, es-CO and the other Latin American and US Spanish tags to it, es-ES and es to Spanish. Gap 5 32–55 h → 31–54 h, rank unchanged | | 2026-10-12 | minor | Gap 15: controller buttons on key/gamepad triggers (Windows and the browser), widgets reacting in prototypes and `proto.evaluate` landed; gap 15 now media plus controllers off Windows, 3–6 h → 2–5 h; ranking unchanged | | 2026-10-12 | minor | Plugin directory on `figcraft-server` (publish with the server key, browse, install after a permissions confirmation, hash-checked downloads, updates by version, operator moderation) added to gap 6's landed list; gap 6 10–30 h → 8–26 h; ranking unchanged | | 2026-10-12 | minor | Japanese and Korean draft catalogs and CJK UI fonts (gap 5): `ja.toml` and `ko.toml` translate every UI string (1,351 keys; plurals give only `other`, and `plural_one` never picks "one" in either), command label (519) and menu wording (29), machine-drafted and marked as drafts awaiting native review; Preferences ▸ Language lists 日本語 and 한국어, System picks ja* and ko*. CJK UI text renders: once the UI language, the open file's names or comments, typed text or the language picker needs Japanese kana, Hangul or Han, the desktop app appends one installed face per script to egui's fallbacks (Windows Yu Gothic UI / Meiryo / Malgun Gothic / Microsoft YaHei, macOS Hiragino Sans / Apple SD Gothic Neo / PingFang SC, Linux Noto Sans CJK; the craft-fonts build's BIZ UDPGothic first), reading only that face's file and sharing its bytes with egui (English users load none: startup and memory unchanged); the web shows Japanese and Korean only when built with craft-fonts' Noto Sans CJK (`FIGCRAFT_EMBED_CJK`), else it keeps them out of the picker and System. Gap 5 40–65 h → 32–55 h, rank unchanged | diff --git a/docs/localization-parity.md b/docs/localization-parity.md index 3eb5c249..1d29f880 100644 --- a/docs/localization-parity.md +++ b/docs/localization-parity.md @@ -1,11 +1,10 @@ # Localization parity -> **Last reviewed:** 2026-10-10 · **Last updated:** 2026-10-12 · **Change:** minor (Japanese and Korean drafts, CJK UI fonts) · **Target:** Figma Design (desktop app 126.10.7) +> **Last reviewed:** 2026-10-10 · **Last updated:** 2026-10-12 · **Change:** minor (Latin American Spanish overlay) · **Target:** Figma Design (desktop app 126.10.7) -FigCraft's interface has a localization layer with an English catalog and complete Spanish, French, -German, Brazilian Portuguese, Japanese and Korean drafts (machine-drafted, waiting for native -speakers' review). This -page lists the twelve key languages of the craftrules standard, then the other languages Figma +FigCraft's interface has a localization layer with an English catalog and complete Spanish (with a +Latin American overlay), French, German, Brazilian Portuguese, Japanese and Korean drafts +(machine-drafted, waiting for native speakers' review). This page lists the twelve key languages of the craftrules standard, then the other languages Figma ships. How to add a language: [`translating.md`](translating.md). ## What Figma ships @@ -28,17 +27,22 @@ supported by both apps (CJK, Arabic and Devanagari shaping, IME); Figma has no v document data, such as tool names, blend modes and Dev Mode properties) plus 519 command labels and 29 menu wordings by command id: 1,899 entries. Command ids, the CLI, MCP and the control channel stay English. Accessibility names come from the same strings. -- **Language preference:** Preferences ▸ Language (System, English, Deutsch, Español, Français, - 日本語, 한국어, Português (Brasil)), applied at once without a restart; System takes the first of the OS's - preferred languages that has a catalog (Windows' language list, macOS AppleLanguages, `LANGUAGE` - then the locale variables on Linux and BSD; a regional tag falls back to its language, and pt-PT - or pt to pt-BR) or the browser's language on the web. `pref.language {language}` sets it from - the CLI, MCP and the control channel. +- **Language preference:** Preferences ▸ Language (System, English, Deutsch, Español, Español + (Latinoamérica), Français, 日本語, 한국어, Português (Brasil)), applied at once without a restart; + System takes the first of the OS's preferred languages that has a catalog (Windows' language + list, macOS AppleLanguages, `LANGUAGE` then the locale variables on Linux and BSD; a regional tag + falls back to its language, pt-PT or pt to pt-BR, and es-419, es-MX, es-AR, es-CO and the other + Latin American and US Spanish tags to es-419) or the browser's language on the web. + `pref.language {language}` sets it from the CLI, MCP and the control channel. +- **Regional overlays:** a regional catalog whose language has a catalog lists only what differs + (`es-419.toml` over `es.toml`); its UI strings, command labels and menu wordings read the + overlay, then the base language, then English. - **Check:** `cargo test -p figcraft-ui-egui i18n` fails on keys the code uses that `en.toml` - lacks, keys no code uses, unknown command ids, placeholders that differ from English and, for - complete languages (Spanish), anything untranslated. The French, German, Brazilian Portuguese, - Japanese and Korean drafts translate everything today but are not held to it: a string added later shows English - there until someone translates it. + lacks, keys no code uses, unknown command ids, placeholders that differ from English, overlay + entries that read as in their base language and, for complete languages (Spanish), anything + untranslated. The French, German, Brazilian Portuguese, Japanese and Korean drafts translate + everything today but are not held to it: a string added later shows English there until someone + translates it. - **Not yet localized:** messages the engine writes (errors, a few toasts, Dev Mode property values such as "inside"), number formatting (decimal points stay `.`), and plural forms beyond one/other (French and Brazilian Portuguese take the singular for 0 as well; Japanese and Korean have one @@ -71,7 +75,7 @@ supported by both apps (CJK, Arabic and Devanagari shaping, IME); Figma has no v |---|---|---|---|---|---|---|---| | English | en | 1,899 / 1,899 (100%, source) | yes | Latin | yes (source) | full | 0 | | Simplified Chinese (Mandarin) | zh-Hans | 0 (0%) | no | CJK text, IME and font fallback work in documents; CJK UI faces load on demand (desktop) | no | none | 3–5 | -| Spanish | es | 1,899 / 1,899 (100%, draft) | yes | Latin | no | draft | 1–2 (es-419 differences, after native review) | +| Spanish | es, es-419 | 1,899 / 1,899 (100%, draft); es-419 overlays 87 Latin American wordings (draft) | yes | Latin | no | draft | 0–1 (after native review) | | Hindi | hi | 0 (0%) | no | Devanagari shaping works in documents; UI font fallback to check | no | none | 4–7 | | Arabic | ar | 0 (0%) | no | Bidi, shaping and RTL names work; the UI does not mirror | no | none | 9–15 | | French | fr | 1,899 / 1,899 (100%, draft) | yes | Latin | no | draft | 1–2 (after native review) | @@ -88,15 +92,16 @@ The layer is done. Per-language hours above are agent hours for a catalog of ~1, (a machine draft, then a pass over the panels for overflow), plus each language's plural rule where it isn't one/other; every language also needs a native speaker's review (human). Left before full parity besides the languages: localizing engine messages and number formatting (4–6 h); -right-to-left mirroring of the panels is in Arabic's hours. Total: **32–55 h**. +right-to-left mirroring of the panels is in Arabic's hours. Total: **31–54 h**. -Priority: native reviews of the six drafts (every one of Figma's languages now has one; Latin -American Spanish is the last Figma variant), then Simplified Chinese, whose UI fonts already work. +Priority: native reviews of the six drafts and the es-419 overlay (every one of Figma's languages +and variants now has one), then Simplified Chinese, whose UI fonts already work. ## Revision history | Date | Change | Summary | |---|---|---| +| 2026-10-12 | minor | Latin American Spanish overlay (gap 5): `es-419.toml` lists only the 87 entries (61 UI strings, 26 command labels) whose wording differs in Latin American usage (agregar for añadir, presionar for pulsar, configuración for settings, Enter for the Intro key, control for a gamepad, resorte for a spring) and reads `es.toml`, then English, for the rest (a regional catalog overlays its language's); same tú register as Spanish; a draft awaiting native review. Preferences ▸ Language lists Español (Latinoamérica); System maps es-419, es-MX, es-AR, es-CO and the other Latin American and US Spanish tags to it, es-ES and es to Spanish. A regional catalog's lookups (UI strings, command labels, menu wordings) go overlay → base language → English; the check fails on an overlay entry that reads as in its base. Localization 35% → 36%, 32–55 h → 31–54 h | | 2026-10-12 | minor | Japanese and Korean draft catalogs and CJK UI fonts (gap 5): `ja.toml` and `ko.toml` translate every UI string (1,351 keys; plurals give only `other`, and `plural_one` never picks "one" in either), command label (519) and menu wording (29), machine-drafted and marked as drafts awaiting native review; Preferences ▸ Language lists 日本語 and 한국어, System picks ja* and ko*. CJK UI text renders: once the UI language, the open file's names or comments, typed text or the language picker needs Japanese kana, Hangul or Han, the desktop app appends one installed face per script to egui's fallbacks (Windows Yu Gothic UI / Meiryo / Malgun Gothic / Microsoft YaHei, macOS Hiragino Sans / Apple SD Gothic Neo / PingFang SC, Linux Noto Sans CJK; the craft-fonts build's BIZ UDPGothic first), reading only that face's file and sharing its bytes with egui (English users load none: startup and memory unchanged); the web shows Japanese and Korean only when built with craft-fonts' Noto Sans CJK (`FIGCRAFT_EMBED_CJK`), else it keeps them out of the picker and System. Localization 30% → 35%, 40–65 h → 32–55 h | | 2026-10-12 | minor | French, German and Brazilian Portuguese draft catalogs (gap 5): `fr.toml`, `de.toml` and `pt-BR.toml` translate every UI string (1,351 keys, 36 with plural forms), command label (519) and menu wording (29), machine-drafted and marked as drafts awaiting native review; they fall back to English per key, so new strings need only English and Spanish. French and Brazilian Portuguese count 0 as singular (`plural_one`); System picks the first OS language with a catalog (Windows' and macOS's preferred-language lists, `LANGUAGE` then the locale on Linux and BSD; pt-PT and pt read pt-BR); Preferences ▸ Language lists Français, Deutsch and Português (Brasil). Dropdowns, two-column labels and the home cards keep long translations on one line with an ellipsis and show them in full on hover. Localization 20% → 30%, 45–75 h → 40–65 h | | 2026-10-11 | major | Localization layer landed (gap 5): catalogs per language baked in at build time, English text as keys and fallback, `{named}` placeholders and one/other plurals, command labels by command id, Preferences ▸ Language (System follows the OS or the browser) applied live, `pref.language`, and a catalog check; English catalog (1,280 UI keys) and a complete Spanish draft (1,810 entries with command labels) waiting for native review; localization 10% → 20%, 60–100 h → 45–75 h | diff --git a/docs/mcp.md b/docs/mcp.md index dbedc294..a6fa2e2f 100644 --- a/docs/mcp.md +++ b/docs/mcp.md @@ -271,7 +271,7 @@ on; `key` resolves keys the way the canvas does. Commands behind keys: nested layers), Escape or a click drops it. - `pref.reduceMotion {on?}`: reduced motion (the interface's own animations off; the system's accessibility setting turns it on too). Prototype transitions still play. -- `pref.language {language: system|en|es|fr|de|pt-BR|ja|ko|…}`: the app's interface language, applied live ("system": +- `pref.language {language: system|en|es|es-419|fr|de|pt-BR|ja|ko|…}`: the app's interface language, applied live ("system": the OS or browser language; a language without a catalog shows English). Command ids, labels and every result stay English. diff --git a/docs/roadmap.md b/docs/roadmap.md index c4cb3cc6..37d7656c 100644 --- a/docs/roadmap.md +++ b/docs/roadmap.md @@ -1,6 +1,6 @@ # FigCraft roadmap: milestones and what's next -> **Last reviewed:** 2026-10-10 · **Last updated:** 2026-10-12 · **Change:** minor (prototyping tail: gamepad triggers, widgets in prototypes, mode lookups; plugin directory on figcraft-server; Japanese and Korean draft catalogs and CJK UI fonts; plugin widgets and the plugin panel tail; French, German and Brazilian Portuguese draft catalogs; `.fig` import, best effort; Display P3: gamut warning and soft-proof (true P3 blocked upstream); several windows; localization layer with a draft Spanish catalog; hosting figcraft-server: wss in the desktop app, server releases, deployment guide; plugin panels and settings; canvas fixes: boolean hit testing on the result, group drags, frame outlines and slices; code connect; stretch and scatter brushes, pen pressure; text on a path and pattern fills; team libraries on the server and swap library; branches: create, update from main, review and merge; animated GIF fills and TIFF images; Open recent, outlines for the selection and mask outlines: menu breadth 100%; extended collections, variables import/export, gradient stop and variant bindings; feature breadth ~79%) · **Target:** Figma Design (desktop app 126.10.7) +> **Last reviewed:** 2026-10-10 · **Last updated:** 2026-10-12 · **Change:** minor (Latin American Spanish overlay; prototyping tail: gamepad triggers, widgets in prototypes, mode lookups; plugin directory on figcraft-server; Japanese and Korean draft catalogs and CJK UI fonts; plugin widgets and the plugin panel tail; French, German and Brazilian Portuguese draft catalogs; `.fig` import, best effort; Display P3: gamut warning and soft-proof (true P3 blocked upstream); several windows; localization layer with a draft Spanish catalog; hosting figcraft-server: wss in the desktop app, server releases, deployment guide; plugin panels and settings; canvas fixes: boolean hit testing on the result, group drags, frame outlines and slices; code connect; stretch and scatter brushes, pen pressure; text on a path and pattern fills; team libraries on the server and swap library; branches: create, update from main, review and merge; animated GIF fills and TIFF images; Open recent, outlines for the selection and mask outlines: menu breadth 100%; extended collections, variables import/export, gradient stop and variant bindings; feature breadth ~79%) · **Target:** Figma Design (desktop app 126.10.7) Forward-looking plan. Where FigCraft stands today: [`ROADMAP.md`](../ROADMAP.md) and [`target-app-parity.md`](target-app-parity.md); the full ranked list: [`gaps.md`](gaps.md). Hours @@ -16,7 +16,7 @@ are one Opus 5.5 agent; most milestones split across 4–6 agents on disjoint cr 3. **Tool-by-tool interaction pass** against the running Figma ([`ui-parity.md`](ui-parity.md)): 40–60 h, parallel by tool. 4. **Languages** on the landed localization layer: native reviews of the es, fr, de, pt-BR, ja and - ko drafts (people), Latin American Spanish, then the other key languages: 3–7 h per language + ko drafts and the es-419 overlay (people), then the other key languages: 3–7 h per language plus native review. 5. **Multiplayer M9**: accounts and teams on top of the live sessions and the standalone server (the WebSocket transport, hosting from the app, invite links that open the app, document sync, live cursors, selections, text carets, avatars and follow, per-user undo, reconnect with offline edits, collision-free ids for styles, variables, collections and comments, joining from the browser, and `figcraft-server` with persistence, publishing from the app and share links to edit or view landed), per [`collab-protocol.md`](collab-protocol.md) and [`server.md`](server.md): 45–90 h. @@ -73,6 +73,7 @@ blockers. | Date | Change | Summary | |---|---|---| +| 2026-10-12 | minor | Latin American Spanish overlay (gap 5): `es-419.toml` lists only the 87 entries (61 UI strings, 26 command labels) whose wording differs in Latin American usage (agregar for añadir, presionar for pulsar, configuración for settings, Enter for the Intro key, control for a gamepad, resorte for a spring) and reads `es.toml`, then English, for the rest (a regional catalog overlays its language's); same tú register as Spanish; a draft awaiting native review. Preferences ▸ Language lists Español (Latinoamérica); System maps es-419, es-MX, es-AR, es-CO and the other Latin American and US Spanish tags to it, es-ES and es to Spanish. Focus item 4 no longer lists Latin American Spanish | | 2026-10-12 | minor | M5: prototyping tail: controller buttons on key/gamepad triggers (Windows and the browser), widgets reacting in prototypes, `proto.evaluate`; M5's tail 3–6 h → 2–5 h | | 2026-10-12 | minor | M11: a plugin directory on `figcraft-server` (bundles published with the server key and checked in full, browsed and installed from Plugins ▸ Manage plugins after a permissions confirmation, hash-checked downloads, updates by version, `plugin.directory.*`, `figcraft-cli plugin publish`); 10–30 h → 8–26 h | | 2026-10-12 | minor | Japanese and Korean draft catalogs and CJK UI fonts (gap 5): `ja.toml` and `ko.toml` translate every UI string (1,351 keys; plurals give only `other`, and `plural_one` never picks "one" in either), command label (519) and menu wording (29), machine-drafted and marked as drafts awaiting native review; Preferences ▸ Language lists 日本語 and 한국어, System picks ja* and ko*. CJK UI text renders: once the UI language, the open file's names or comments, typed text or the language picker needs Japanese kana, Hangul or Han, the desktop app appends one installed face per script to egui's fallbacks (Windows Yu Gothic UI / Meiryo / Malgun Gothic / Microsoft YaHei, macOS Hiragino Sans / Apple SD Gothic Neo / PingFang SC, Linux Noto Sans CJK; the craft-fonts build's BIZ UDPGothic first), reading only that face's file and sharing its bytes with egui (English users load none: startup and memory unchanged); the web shows Japanese and Korean only when built with craft-fonts' Noto Sans CJK (`FIGCRAFT_EMBED_CJK`), else it keeps them out of the picker and System. Current focus 4 now lists the drafts' native reviews | diff --git a/docs/target-app-parity.md b/docs/target-app-parity.md index 6619206b..0a93daee 100644 --- a/docs/target-app-parity.md +++ b/docs/target-app-parity.md @@ -1,6 +1,6 @@ # FigCraft parity with Figma -> **Last reviewed:** 2026-10-10 · **Last updated:** 2026-10-12 · **Change:** minor (prototyping tail: gamepad triggers, widgets in prototypes, mode lookups; plugin directory on figcraft-server; Japanese and Korean draft catalogs and CJK UI fonts; plugin widgets and the plugin panel tail; French, German and Brazilian Portuguese draft catalogs; cursor chat and spotlight in live sessions; layers and assets panels behaviour suite and fixes; vector multi-edit and edit tail; dev resources links; extended collections tail; branch version history and finer list merges; headline percentages recomputed from the area table: feature breadth ~84%, core ~94%, mainstream ~50%; text lists, hanging punctuation and decoration styles; comments and version history behaviour suite and fixes; canvas navigation, guides and grids behaviour suite and fixes; export and inspect behaviour suite and fixes; .fig import: editable vector networks and more layer kinds; shapes and vector behaviour suite and fixes; paint and effects behaviour suite and fixes; frames, groups, sections and masks behaviour suite and fixes; styles and variables behaviour suite and fixes; prototyping behaviour suite and fixes; components behaviour suite and fixes; embedded image colour profiles; layers panel keyboard: F2 rename, Shift+F10 context menu, keyboard box selection, 10k-layer QA; per-side stroke weight variables; text behaviour suite: CJK line breaking, metrics, vertical trim; selection and transform behaviour suite and fixes; `.fig` import, best effort; auto layout behaviour suite and fixes; Display P3: gamut warning and soft-proof (true P3 blocked upstream); several windows; localization layer with a draft Spanish catalog; hosting figcraft-server: wss in the desktop app, server releases, deployment guide; plugin panels and settings; canvas fixes: boolean hit testing on the result, group drags, frame outlines and slices; code connect; stretch and scatter brushes, pen pressure; text on a path and pattern fills; team libraries on the server and swap library; branches: create, update from main, review and merge; animated GIF fills and TIFF images; Open recent, outlines for the selection and mask outlines: menu breadth 100%; extended collections, variables import/export, gradient stop and variant bindings; feature breadth ~80%; ready ~49%; last major: full re-measure against Figma desktop 126.10.7) · **Target:** Figma Design (desktop app 126.10.7, macOS) +> **Last reviewed:** 2026-10-10 · **Last updated:** 2026-10-12 · **Change:** minor (Latin American Spanish overlay; prototyping tail: gamepad triggers, widgets in prototypes, mode lookups; plugin directory on figcraft-server; Japanese and Korean draft catalogs and CJK UI fonts; plugin widgets and the plugin panel tail; French, German and Brazilian Portuguese draft catalogs; cursor chat and spotlight in live sessions; layers and assets panels behaviour suite and fixes; vector multi-edit and edit tail; dev resources links; extended collections tail; branch version history and finer list merges; headline percentages recomputed from the area table: feature breadth ~84%, core ~94%, mainstream ~50%; text lists, hanging punctuation and decoration styles; comments and version history behaviour suite and fixes; canvas navigation, guides and grids behaviour suite and fixes; export and inspect behaviour suite and fixes; .fig import: editable vector networks and more layer kinds; shapes and vector behaviour suite and fixes; paint and effects behaviour suite and fixes; frames, groups, sections and masks behaviour suite and fixes; styles and variables behaviour suite and fixes; prototyping behaviour suite and fixes; components behaviour suite and fixes; embedded image colour profiles; layers panel keyboard: F2 rename, Shift+F10 context menu, keyboard box selection, 10k-layer QA; per-side stroke weight variables; text behaviour suite: CJK line breaking, metrics, vertical trim; selection and transform behaviour suite and fixes; `.fig` import, best effort; auto layout behaviour suite and fixes; Display P3: gamut warning and soft-proof (true P3 blocked upstream); several windows; localization layer with a draft Spanish catalog; hosting figcraft-server: wss in the desktop app, server releases, deployment guide; plugin panels and settings; canvas fixes: boolean hit testing on the result, group drags, frame outlines and slices; code connect; stretch and scatter brushes, pen pressure; text on a path and pattern fills; team libraries on the server and swap library; branches: create, update from main, review and merge; animated GIF fills and TIFF images; Open recent, outlines for the selection and mask outlines: menu breadth 100%; extended collections, variables import/export, gradient stop and variant bindings; feature breadth ~80%; ready ~49%; last major: full re-measure against Figma desktop 126.10.7) · **Target:** Figma Design (desktop app 126.10.7, macOS) The authoritative assessment of how close FigCraft is to Figma Design. [`ROADMAP.md`](../ROADMAP.md) summarizes it; [`gaps.md`](gaps.md) is the ranked work list; the measured menu checklist is @@ -21,7 +21,7 @@ summarizes it; [`gaps.md`](gaps.md) is the ranked work list; the measured menu c | Essentials user | **~57%** | estimated ([below](#essentials-user): core features 77.8% × discounts 0.92 × 0.92 × 0.88) | | Mainstream practitioner | **~49%** | estimated ([below](#mainstream-practitioner): weekly areas 72.6% × discounts 0.9 × 0.9 × 0.85) | | Remaining to beta | **~155–280 h** one Opus 5.5 agent (~32–64 h wall-clock with 5 parallel agents) | estimated | -| Remaining to full parity | **~265–523 h** one agent (~83–145 h wall-clock with 5 parallel agents) | estimated | +| Remaining to full parity | **~264–522 h** one agent (~83–145 h wall-clock with 5 parallel agents) | estimated | | Stage | **alpha**: the alpha gate passes ([roadmap](roadmap.md#alpha-gate)); multiplayer and checking the best-effort `.fig` import on real files are beta gaps (see [ROADMAP](../ROADMAP.md#stage)) | | The previous figures (ROADMAP of 2026-10-10, "feature depth ~79%", "full parity 115–195 h") were @@ -35,7 +35,7 @@ tool; presence was being graded as behaviour in places. | Audience | Ready % | Opus 5.5 agent wall-clock hours to ~95% | Work that dominates | |---|---|---|---| -| Full target (ready for real work) | ~53% | ~271–526 h | multiplayer (42–84), localization (32–55), plugins (8–26), the interaction pass (40–60), GPU canvas (30–60), AI (30–60), the `.fig` import's tail (5–10) | +| Full target (ready for real work) | ~53% | ~270–525 h | multiplayer (42–84), localization (31–54), plugins (8–26), the interaction pass (40–60), GPU canvas (30–60), AI (30–60), the `.fig` import's tail (5–10) | | Mainstream practitioner | ~49% | ~130–244 h | core editor depth incl. the `.fig` import's tail (49–98), the interaction pass (40–60), real-file corpus and platform QA (25–50), `.fig` export for handing work back (15–30) | | Essentials user | ~57% | ~60–110 h | the `.fig` import's tail so the files people send open well (5–10), core tool polish (25–45), interaction pass on the core tools (15–25), stability QA (10–20), onboarding and starter templates (5–10) | @@ -129,11 +129,11 @@ Breadth: does it exist. Ready: depth, correctness and fidelity for real projects | Stability | 8 | 55% | 20–40 | Never-crash lints, command guard with rollback, recovery copies, 963 tests incl. junk-parameter sweeps and a seeded retained-render walk; but two days old, one release (v0.2.0), no field reports, QA mostly on macOS | [gaps](gaps.md) | | Performance | 6 | 60% | 30–60 | `cargo xtask bench`: 50k-layer page ~8 ms zoomed out, 10k-layer edits ~0.2 ms, 2,000-instance design system edits 4–64 ms; CPU (tiny-skia) canvas where Figma renders on the GPU; nothing timed against Figma on the same file | [hardware-parity](hardware-parity.md) | | Hardware | 3 | 60% | 10–21 | Trackpad pinch and HiDPI yes; pen pressure on Windows and from browser styluses (none from macOS/Linux tablets); several windows (screen-reader tree in the main one only); no GPU canvas, no P3 on screen | [hardware-parity](hardware-parity.md) | -| Localization | 3 | 35% | 32–55 | The layer, an English catalog and complete machine drafts of Spanish, French, German, Brazilian Portuguese, Japanese and Korean (none reviewed by a native speaker), CJK UI text in installed faces loaded on demand (web: only the craft-fonts CJK build); Latin American Spanish is missing; engine messages stay English | [localization-parity](localization-parity.md) | +| Localization | 3 | 36% | 31–54 | The layer, an English catalog and complete machine drafts of Spanish (with a Latin American overlay), French, German, Brazilian Portuguese, Japanese and Korean (none reviewed by a native speaker), CJK UI text in installed faces loaded on demand (web: only the craft-fonts CJK build); engine messages stay English | [localization-parity](localization-parity.md) | | Platforms | 3 | 75% | 10–20 | macOS, Windows (x64/x86/arm64), Linux, FreeBSD, web all packaged (v0.2.0); Figma also has iOS/Android apps; non-macOS QA thin | [hardware-parity](hardware-parity.md) | | Ecosystem/plugins | 3 | 38% | 8–26 | Own WASM plugins with a Rust SDK and three examples (#101), plugin panels and settings (ABI v2), widgets that take clicks (API 3), a self-hosted plugin directory on `figcraft-server`; no Figma plugin compatibility, no public catalogue or community files; the CLI, control channel and MCP server are ahead of Figma for agents | [gaps](gaps.md) | | AI features | 2 | 0% | 30–60 | None | [gaps](gaps.md) | -| **Ready for real work** | 100 | **~53%** | **~265–523** | | | +| **Ready for real work** | 100 | **~53%** | **~264–522** | | | Dimension hours add up to the full-parity total without double counting: the feature-area rows for import/export (14), multiplayer and team libraries (17, 19), plugins (18) and AI (21) are @@ -619,6 +619,7 @@ Carried over from the ROADMAP's "Shipped so far" (2026-10-10). The test count th | Date | Change | Summary | |---|---|---| +| 2026-10-12 | minor | Latin American Spanish overlay (gap 5): `es-419.toml` lists only the 87 entries (61 UI strings, 26 command labels) whose wording differs in Latin American usage (agregar for añadir, presionar for pulsar, configuración for settings, Enter for the Intro key, control for a gamepad, resorte for a spring) and reads `es.toml`, then English, for the rest (a regional catalog overlays its language's); same tú register as Spanish; a draft awaiting native review. Preferences ▸ Language lists Español (Latinoamérica); System maps es-419, es-MX, es-AR, es-CO and the other Latin American and US Spanish tags to it, es-ES and es to Spanish. Localization 35% → 36%, 32–55 h → 31–54 h; ready for real work unchanged (~53%); full parity ~268–528 h → ~267–527 h | | 2026-10-12 | minor | Prototyping tail (gap 15, the part that doesn't need video): key/gamepad triggers take controller buttons (`Gamepad A` … `Gamepad Home`, the standard layout's 17 buttons, from a Controller menu beside the key field; the REST import maps controller triggers instead of skipping them); the player reads controllers on Windows (Windows.Gaming.Input through the `windows` crate the app already used; no new dependency) and in the browser (the Gamepad API), polling only while the prototype has such a trigger and the window has the focus, and the control channel presses buttons (`ui.input {gamepad}`); macOS, Linux and FreeBSD read no controllers yet. Widgets react to clicks in a presented prototype (the canvas's callback and sandbox limits, on the prototype's copy: the file and its history don't change, restarting starts over). `proto.evaluate` computes an expression where a layer's interaction would run (MCP); the mode lookup and set-variable-mode rules are cases in a behaviour suite (they already held). Prototyping breadth 90% → 92%, ready unchanged at 74%, 3–6 h → 2–5 h; area hours 120–260 → 119–259 (core 33–75 → 32–74), Features (depth) 29–68 h → 28–67 h; percentages unchanged at this rounding; full parity ~276–538 h → ~275–537 h | | 2026-10-12 | minor | Plugin directory on `figcraft-server` (gap 6): the server keeps plugin bundles (manifest, module, up to 32 assets; an uncompressed length-prefixed format named by its SHA-256) published with the server key (`figcraft-cli plugin publish`, `plugin.directory.publish`), checked in full before they are kept (size cap, manifest and plugin API version, a version newer than the one kept, the module compiled but never run, plain asset names) and capped (`max_plugins`, `max_plugin_mb`, `max_plugins_mb`); anyone who reaches the server lists, searches and fetches them (hash-checked again on read, a shared download budget); the operator moderates with `figcraft-server plugins [remove]`. The app's Plugins ▸ Manage plugins gains a Browse tab: a server's plugins with Install / Update / Remove, an install confirmation that shows the permissions asked for, the source and the SHA-256, the download checked against the listed hash before anything is read, plugins kept per user in hash-named folders; `plugin.directory.list` / `install` (needs `confirm: true`) / `publish` / `unpublish` for scripts and MCP. Not in the browser build yet; no public catalogue, publisher accounts, signatures, ratings or background update checks. Plugins & widgets 55%/32% → 60%/35%, 10–30 h → 8–26 h; Ecosystem dimension 34% → 38%; weighted totals unchanged at this rounding; full parity ~281–548 h → ~279–544 h | | 2026-10-12 | minor | Japanese and Korean draft catalogs and CJK UI fonts (gap 5): `ja.toml` and `ko.toml` translate every UI string (1,351 keys; plurals give only `other`, and `plural_one` never picks "one" in either), command label (519) and menu wording (29), machine-drafted and marked as drafts awaiting native review; Preferences ▸ Language lists 日本語 and 한국어, System picks ja* and ko*. CJK UI text renders: once the UI language, the open file's names or comments, typed text or the language picker needs Japanese kana, Hangul or Han, the desktop app appends one installed face per script to egui's fallbacks (Windows Yu Gothic UI / Meiryo / Malgun Gothic / Microsoft YaHei, macOS Hiragino Sans / Apple SD Gothic Neo / PingFang SC, Linux Noto Sans CJK; the craft-fonts build's BIZ UDPGothic first), reading only that face's file and sharing its bytes with egui (English users load none: startup and memory unchanged); the web shows Japanese and Korean only when built with craft-fonts' Noto Sans CJK (`FIGCRAFT_EMBED_CJK`), else it keeps them out of the picker and System. Localization 30% → 35% (drafts, not reviewed: graded low), 40–65 h → 32–55 h; ready for real work ~52% → ~53% (52.9 → 53.1 by the dimension table); full parity ~284–554 h → ~276–544 h; beta ~162–292 h → ~155–280 h (Japanese and Korean leave the beta list; their native reviews need people) | diff --git a/docs/translating.md b/docs/translating.md index 45a2e197..e6798242 100644 --- a/docs/translating.md +++ b/docs/translating.md @@ -5,12 +5,13 @@ FigCraft's interface strings live in one TOML catalog per language in binary (desktop and web); nothing is read at run time. Preferences ▸ Language picks the language and the interface switches at once. System takes the first of the OS's preferred languages that has a catalog (the browser's language on the web): a regional tag reads its language's catalog -(`fr-CA` → `fr`), and a language with only a regional catalog reads that one (`pt-PT` → `pt-BR`). +(`fr-CA` → `fr`), a language with only a regional catalog reads that one (`pt-PT` → `pt-BR`), and +Latin American and US Spanish (`es-MX`, `es-AR`, `es-CO`, `es-US`, `es-419`…) read `es-419`. `pref.language {language}` does the same from the CLI, MCP and the control channel. -The catalogs: English (`en`, the source), and Spanish (`es`), French (`fr`), German (`de`), -Brazilian Portuguese (`pt-BR`), Japanese (`ja`) and Korean (`ko`), machine-drafted and waiting for -native speakers' review. +The catalogs: English (`en`, the source), and Spanish (`es`, with the Latin American overlay +`es-419`), French (`fr`), German (`de`), Brazilian Portuguese (`pt-BR`), Japanese (`ja`) and Korean +(`ko`), machine-drafted and waiting for native speakers' review. ## The catalogs @@ -25,12 +26,17 @@ native speakers' review. English. - `[meta] name` is the language's own name (the picker shows it untranslated); `[meta] status` marks a draft (the Preferences window says so). +- A **regional overlay** (`es-419.toml`: a code with a region whose language has a catalog) lists + only the entries worded differently there; everything else reads the language's catalog, then + English. The check fails on an overlay entry that reads as in the catalog under it. A key a catalog lacks shows its English text, so a partial catalog is safe to ship. Typography follows each language: French puts a no-break space (`\u00A0` in the TOML) before `:` `;` `?` `!`, inside « » and before `%`; German uses „ “ and formal address (Sie); Brazilian -Portuguese uses Brazilian words (arquivo, salvar, tela, você), not European ones. Japanese is +Portuguese uses Brazilian words (arquivo, salvar, tela, você), not European ones. Latin American +Spanish overrides Spain's wording where it differs (agregar, presionar, configuración for settings, +Enter for the key, control for a gamepad, resorte) and keeps Spanish's tú. Japanese is concise, without です/ます endings, with full-width ()「」:、。 where Japanese UI text has them; Korean labels end without 합니다 (instructions in -세요). Neither has plural forms.