mirror of
https://github.com/storytold/figcraft.git
synced 2026-10-13 23:17:43 +00:00
M9: French, German and Brazilian Portuguese draft catalogs
- New UI catalogs `crates/ui-egui/locales/fr.toml`, `de.toml` and `pt-BR.toml`: every UI string (1,351 keys, 36 with plural forms), every command label (519) and every main-menu wording (29) translated, 1,899 entries each. Machine-drafted from our English, marked `status = "draft - needs native review"` like Spanish, so Preferences shows the draft note. Placeholders are identical to English; `|context` keys are translated per context (sizing "Fill", stroke "Join", glass "Light", open comments). - Language conventions: French uses no-break spaces (` `) before `: ; ? !`, inside guillemets and before `%`; German uses formal address and German quotes; Brazilian Portuguese uses Brazilian vocabulary (arquivo, salvar, tela, voce). - Not added to `COMPLETE`: that list forces every later PR to translate new strings into each complete language, so the three drafts are registered as incomplete and fall back to English per key; Spanish stays the only enforced language. `docs/translating.md` says so and how to list what a draft lacks. - Plurals: `plural_one` now takes 0 as singular for French and Brazilian Portuguese (CLDR integer rules); English, Spanish and German stay `n == 1`. - Language choice: Preferences > Language lists Francais, Deutsch and Portugues (Brasil) by their own names (from the catalogs). System now walks the OS's preferred-language list and takes the first language with a catalog: Windows' full `GlobalizationPreferences.Languages`, all of macOS's `AppleLanguages`, gettext's `LANGUAGE` then the locale variables on Linux/BSD; the browser still uses `navigator.language`. A tag with only a regional catalog reads it (`pt-PT`, `pt` -> `pt-BR`). `pref.language` docs list `fr|de|pt-BR`. - Overflow (German is the longest): `widgets::dropdown` now keeps its label on one line, ending it with an ellipsis and showing it in full on hover (it used to wrap mid-word, e.g. the auto layout sizing and stacking dropdowns); `elided_dropdown` merged into it. Two-column sublabels in the right panel and the home screen's cards elide the same way. English is unchanged. - Docs: localization-parity (languages, string counts, status, hours), translating, target-app-parity, ROADMAP, gaps (gap 5), roadmap (current focus 4), mcp. Localization 20% -> 30%, 45-75 h -> 40-65 h; ready for real work stays ~52%; full parity ~289-564 h -> ~284-554 h; beta ~170-305 h -> ~162-292 h.
This commit is contained in:
1 parent
1f6e75e0b6
commit
1e0b51398a
21 files changed
+5940
-119
No files matched your search
+16
-14
@@ -1,8 +1,8 @@
|
||||
# FigCraft Roadmap
|
||||
|
||||
**Stage: alpha** · next: beta, ~26 points and ~170–305 h away (one Opus 5.5 agent; ~35–70 h wall-clock with 5 agents)
|
||||
**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 (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 (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
|
||||
@@ -18,14 +18,14 @@ start with [`docs/gaps.md`](docs/gaps.md).
|
||||
| Ready for real work | **~52%** | estimated, same doc (written dimension weights and scores) |
|
||||
| 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 | **~170–305 h** one agent | estimated |
|
||||
| Remaining to full parity | **~286–558 h** one agent (~86–150 h wall-clock with 5 agents) | estimated |
|
||||
| Remaining to beta | **~162–292 h** one agent | estimated |
|
||||
| Remaining to full parity | **~281–548 h** one agent (~85–148 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) | ~52% | ~291–560 h | multiplayer (42–84), localization (45–75), plugins (15–40), 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) | ~52% | ~286–550 h | multiplayer (42–84), localization (40–65), plugins (15–40), 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) |
|
||||
|
||||
@@ -50,7 +50,7 @@ against real Figma documents. Ready-for-real-work is ~52%, inside alpha's ~40–
|
||||
~75% **and** reliable `.fig` import: the `.fig` import's tail (5–10 h), a real-file corpus with measured
|
||||
fidelity (15–30 h), the interaction pass (25–40 h of the 40–60), core depth to ~80% (40–75 h),
|
||||
basic multiplayer on the landed core (30–55 h), stability and platform QA (20–40 h), a partial GPU path (15–30 h) and
|
||||
Figma's own languages on the landed localization layer (15–25 h).
|
||||
Figma's last two languages, Japanese and Korean, on the landed localization layer (7–12 h).
|
||||
|
||||
## By dimension
|
||||
|
||||
@@ -63,7 +63,7 @@ Figma's own languages on the landed localization layer (15–25 h).
|
||||
| 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 | 20% (layer; English, Spanish draft) | 45–75 | [localization-parity](docs/localization-parity.md) |
|
||||
| Localization | 30% (layer; English; Spanish, French, German, Brazilian Portuguese drafts) | 40–65 | [localization-parity](docs/localization-parity.md) |
|
||||
| Platforms | 75% | 10–20 | [hardware-parity](docs/hardware-parity.md) |
|
||||
| Ecosystem / plugins | 28% (own WASM plugins with panels and settings) | 15–40 | [gaps](docs/gaps.md) |
|
||||
| AI features | 0% | 30–60 | [gaps](docs/gaps.md) |
|
||||
@@ -103,18 +103,19 @@ Figma's own languages on the landed localization layer (15–25 h).
|
||||
| Spanish | es | 100% | draft (needs native review) |
|
||||
| Hindi | hi | 0% | none |
|
||||
| Arabic | ar | 0% | none |
|
||||
| French | fr | 0% | none |
|
||||
| Portuguese | pt | 0% | none |
|
||||
| French | fr | 100% | draft (needs native review) |
|
||||
| Portuguese (Brazil) | pt-BR | 100% | draft (needs native review) |
|
||||
| Indonesian | id | 0% | none |
|
||||
| Japanese | ja | 0% | none |
|
||||
| German | de | 0% | none |
|
||||
| German | de | 100% | draft (needs native review) |
|
||||
| Korean | ko | 0% | none |
|
||||
| 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 a Spanish draft (Preferences ▸ Language;
|
||||
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).
|
||||
(Brazil); FigCraft has the localization layer, English and Spanish, French, German and Brazilian
|
||||
Portuguese drafts (Preferences ▸ Language; 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).
|
||||
|
||||
## Upcoming
|
||||
|
||||
@@ -123,7 +124,7 @@ Ranked; detail and milestones in [`docs/roadmap.md`](docs/roadmap.md).
|
||||
1. `.fig` import's tail: fixes from real files, connector labels, table cells, filled areas with their own fills (the best-effort import, editable vector networks, the other layer kinds and variable prototype actions landed 2026-10-11): 5–10 h.
|
||||
2. Real-file corpus and measured fidelity: 15–30 h.
|
||||
3. Tool-by-tool interaction pass against Figma: 40–60 h.
|
||||
4. Languages on the landed localization layer: ja, ko, pt-BR, fr, de, and a native review of es: 15–30 h.
|
||||
4. Languages on the landed localization layer: ja and ko (7–12 h), and native reviews of the es, fr, de and pt-BR drafts (people).
|
||||
5. Multiplayer (M9): accounts and teams on top of the live sessions and the standalone server (transport, hosting, invite links that open the app, document sync, presence with remote text carets, cursor chat and spotlight, per-user undo, reconnect with offline edits, collision-free ids for styles, variables and comments, the browser client, `figcraft-server` with persistence and share links to edit or view, team libraries on the server landed): 42–84 h.
|
||||
|
||||
## Progress log
|
||||
@@ -222,6 +223,7 @@ Ranked; detail and milestones in [`docs/roadmap.md`](docs/roadmap.md).
|
||||
|
||||
| Date | Change | Summary |
|
||||
|---|---|---|
|
||||
| 2026-10-12 | minor | French, German and Brazilian Portuguese draft catalogs (gap 5): every UI string, command label and menu wording (1,899 entries) machine-drafted in each and marked as drafts awaiting native review, falling back to English per key (new strings need only English and Spanish); 0 counts as singular in French and Brazilian Portuguese; System picks the first OS language with a catalog (pt-PT and pt read pt-BR); long translations elide with a tooltip in dropdowns, two-column labels and the home cards. Localization 20% → 30%, 45–75 h → 40–65 h; ready for real work unchanged (~52%); full parity ~289–564 h → ~284–554 h; beta ~170–305 h → ~162–292 h |
|
||||
| 2026-10-12 | minor | Cursor chat and spotlight in live sessions: `/` opens a chat bubble at your pointer that everyone sees as you type and that fades a few seconds after the last change; a spotlight (your avatar's menu, `collab.spotlight`) makes everyone follow your view and page until they move their own. Ephemeral presence the host validates and rate-limits, passed only to clients that read it (older clients keep working). Multiplayer & sharing breadth 15% → 17%, ready unchanged (5%), 45–90 h → 42–84 h; collaboration 13% unchanged, 49–98 h → 46–92 h; feature breadth (~84%) and ready for real work (~51%) unchanged at this rounding; full parity ~301–587 h → ~298–581 h |
|
||||
| 2026-10-12 | minor | Layers and Assets panels behaviour suite written from the public documentation (6 engine tests with about 50 table rows and 5 panel tests driven through the UI; about 30 rows that the code before the fixes contradicts) and its fixes (several layers dropped together, Collapse layers, eye and padlock sweeps, Rename layers dialog with the documented number codes, page dragging, dividers, "Copy of" duplicates, Assets folders, hidden-from-publishing names both ways). Layers panel 98%/82% → 99%/85%; percentages and full parity hours unchanged at this rounding |
|
||||
| 2026-10-12 | minor | Vector multi-edit and the vector edit tail, with a behaviour suite written from the public documentation (5 tests, about 30 table rows and steps, and a junk-parameter sweep of every vector command in multi-edit mode): several vector layers are edited at once (Enter, double-click or `vector.edit.begin {ids}`; a press on another layer's point, handle or segment makes it the active layer; Shift-click, marquee and lasso select across layers; dragging, the arrows, Delete, delete and heal, mirroring, smoothing, corner radius and point caps and joins reach every layer's selected points in one undo step; the pen, bend, paint bucket, cut and eraser work in the active layer); several Bezier handles are selected with Shift-click and dragged together (`vector.edit.moveHandles`); a sharp point next to a curved segment rounds (the curve is cut where the arc meets it and keeps its shape); a point can have its own stroke cap and join (`vector.edit.strokeCap`, `vector.edit.strokeJoin`, the Point section of the Design panel; canvas, PNG, SVG and PDF, outline stroke); the Pen outside edit mode connects to one of its own points and ends the path, and continues a selected open path from an end point. Left: corner smoothing beyond rectangles, the transform box around points of several layers, per-point caps and joins on dashed, variable-width and brush strokes. Shapes & vector 98%/72% → 99%/73%, 2–4 h → 1–3 h; Figma Draw extras 80%/55% → 88%/60%, 2–5 h → 1–3 h (gap 9: pen pressure on macOS and Linux tablets left). Totals recomputed by the doc's method, which rows since the last recompute had left stale: the area table's weighted breadth is 83.7% (~83%; core 94.1%, ~94%) and ready 63.1% (~63%; core 72.1%, ~72%), hours 133–284 (core 38–83); Features (depth) 61% → 63%, 46–94 h → 34–76 h; ready for real work 52.6% (~52%); mainstream weekly areas 71.0% → 72.1% (× 0.9 × 0.9 × 0.85 = 49.7%, ~49%, rounded down); essentials unchanged (~57%); full parity ~306–595 h → ~294–572 h (on top of the rows merged since: ~301–587 h → ~289–564 h; area-table totals recomputed on the merged table and rounded down: breadth ~84%, ready ~63%, core ~94%/~72%, mainstream weekly areas 72.4%, ~49%) |
|
||||
|
||||
@@ -52,14 +52,14 @@ impl Desktop {
|
||||
}
|
||||
}
|
||||
|
||||
/// The system's preferred interface language as a tag ("es-ES", "es_ES.UTF-8"): Windows' language
|
||||
/// list, macOS's AppleLanguages, the locale variables elsewhere.
|
||||
/// The system's preferred interface languages, most preferred first, comma-separated
|
||||
/// ("nl-NL,de-DE,en-US"): Windows' language list, macOS's AppleLanguages, `LANGUAGE` and the
|
||||
/// locale variables elsewhere. The UI shows the first one it has a catalog for.
|
||||
fn system_language() -> Option<String> {
|
||||
#[cfg(target_os = "windows")]
|
||||
{
|
||||
windows::System::UserProfile::GlobalizationPreferences::Languages()
|
||||
.and_then(|l| l.GetAt(0))
|
||||
.map(|s| s.to_string())
|
||||
.map(|l| l.into_iter().map(|s| s.to_string()).collect::<Vec<_>>().join(","))
|
||||
.inspect_err(|e| log::info!("language: can't read the system languages: {e}"))
|
||||
.ok()
|
||||
}
|
||||
@@ -67,11 +67,20 @@ fn system_language() -> Option<String> {
|
||||
#[cfg(target_os = "macos")]
|
||||
{
|
||||
let out = std::process::Command::new("defaults").args(["read", "-g", "AppleLanguages"]).output().ok()?;
|
||||
String::from_utf8_lossy(&out.stdout).split(['(', ',', ')']).map(|s| s.trim().trim_matches('"').to_string()).find(|s| !s.is_empty())
|
||||
let list: Vec<String> = String::from_utf8_lossy(&out.stdout)
|
||||
.split(['(', ',', ')'])
|
||||
.map(|s| s.trim().trim_matches('"').to_string())
|
||||
.filter(|s| !s.is_empty())
|
||||
.collect();
|
||||
Some(list.join(","))
|
||||
}
|
||||
// gettext's `LANGUAGE` priority list ("fr:de"), then the locale ("fr_FR.UTF-8").
|
||||
#[cfg(all(unix, not(target_os = "macos")))]
|
||||
{
|
||||
["LC_ALL", "LC_MESSAGES", "LANG"].iter().filter_map(|k| std::env::var(k).ok()).find(|v| !v.is_empty() && v != "C" && v != "POSIX")
|
||||
let var = |k: &str| std::env::var(k).ok().filter(|v| !v.is_empty() && v != "C" && v != "POSIX");
|
||||
let locale = ["LC_ALL", "LC_MESSAGES", "LANG"].into_iter().find_map(var);
|
||||
let list: Vec<String> = var("LANGUAGE").map(|l| l.replace(':', ",")).into_iter().chain(locale).collect();
|
||||
(!list.is_empty()).then(|| list.join(","))
|
||||
}
|
||||
#[cfg(not(any(windows, unix)))]
|
||||
{
|
||||
|
||||
@@ -272,7 +272,7 @@ pub fn specs() -> Vec<CommandSpec> {
|
||||
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|… (a language code; no catalog: English)}", false, |s, p| {
|
||||
spec!("pref.language", "Interface language", "{language: system|en|es|fr|de|pt-BR|… (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".
|
||||
|
||||
@@ -525,7 +525,7 @@ pub struct Prefs {
|
||||
pub number_keys_opacity: bool,
|
||||
pub show_dimensions: bool,
|
||||
pub theme: String,
|
||||
/// The interface language: a language code ("en", "es") or "system" (the OS or browser
|
||||
/// The interface language: a language code ("en", "es", "pt-BR") or "system" (the OS or browser
|
||||
/// language). Languages without a catalog show English.
|
||||
pub language: String,
|
||||
/// The name comments are signed with (and whose comments count as yours).
|
||||
|
||||
File diff suppressed because it is too large.
Load diff
File diff suppressed because it is too large.
Load diff
File diff suppressed because it is too large.
Load diff
@@ -822,7 +822,7 @@ fn pattern_controls(app: &mut FigApp, ui: &mut Ui, target: &PickerTarget, paint:
|
||||
let sources = pattern_sources(app, target);
|
||||
ui.horizontal(|ui| {
|
||||
ui.add_space(16.0);
|
||||
let resp = widgets::elided_dropdown(ui, &name, 208.0);
|
||||
let resp = widgets::dropdown(ui, &name, 208.0, None);
|
||||
crate::a11y::combo(&resp, tr("Pattern source"), &name, crate::a11y::menu_open(&resp));
|
||||
crate::right::menu_popup(&resp).show(|ui| {
|
||||
for (id, label) in &sources {
|
||||
|
||||
@@ -7,6 +7,7 @@ use crate::FigApp;
|
||||
use crate::i18n::{tr, tr_noop};
|
||||
use crate::icons::{self, Icon};
|
||||
use crate::theme::{ACCENT, Tokens, regular, semibold};
|
||||
use crate::widgets;
|
||||
|
||||
pub fn show(app: &mut FigApp, ui: &mut Ui) {
|
||||
let t = Tokens::get(ui.ctx());
|
||||
@@ -40,14 +41,20 @@ pub fn show(app: &mut FigApp, ui: &mut Ui) {
|
||||
(Icon::Page, tr_noop("Open file"), tr_noop("Open .figcraft, .svg or images")),
|
||||
] {
|
||||
let r = Rect::from_min_size(pos2(x, cy), vec2(260.0, 72.0));
|
||||
let resp = ui.interact(r, ui.id().with(("card", title)), Sense::click());
|
||||
let mut resp = ui.interact(r, ui.id().with(("card", title)), Sense::click());
|
||||
// One line each inside the card; a long translation ends with an ellipsis, in full on hover.
|
||||
let text_w = r.width() - 72.0;
|
||||
let (title_shown, sub_shown) = (widgets::elide_font(ui, tr(title), text_w, semibold(12.0)), widgets::elide(ui, tr(sub), text_w));
|
||||
if title_shown != tr(title) || sub_shown != tr(sub) {
|
||||
resp = resp.on_hover_text(format!("{}\n{}", tr(title), tr(sub)));
|
||||
}
|
||||
ui.painter().rect_filled(r, CornerRadius::same(8), if resp.hovered() { t.hover } else { t.panel });
|
||||
ui.painter().rect_stroke(r, CornerRadius::same(8), Stroke::new(1.0, t.border), StrokeKind::Inside);
|
||||
let ir = Rect::from_min_size(pos2(r.min.x + 16.0, r.center().y - 16.0), vec2(32.0, 32.0));
|
||||
ui.painter().rect_filled(ir, CornerRadius::same(6), ACCENT);
|
||||
icons::paint(ui.painter(), ir.shrink(8.0), icon, egui::Color32::WHITE);
|
||||
ui.painter().text(pos2(r.min.x + 60.0, r.center().y - 8.0), Align2::LEFT_CENTER, tr(title), semibold(12.0), t.text);
|
||||
ui.painter().text(pos2(r.min.x + 60.0, r.center().y + 9.0), Align2::LEFT_CENTER, tr(sub), regular(11.0), t.text_secondary);
|
||||
ui.painter().text(pos2(r.min.x + 60.0, r.center().y - 8.0), Align2::LEFT_CENTER, title_shown, semibold(12.0), t.text);
|
||||
ui.painter().text(pos2(r.min.x + 60.0, r.center().y + 9.0), Align2::LEFT_CENTER, sub_shown, regular(11.0), t.text_secondary);
|
||||
if resp.clicked() {
|
||||
if title.starts_with("New") {
|
||||
new_file = true;
|
||||
|
||||
+18
-10
@@ -49,19 +49,25 @@ pub fn current() -> Option<&'static Lang> {
|
||||
}
|
||||
|
||||
/// Show the language of `pref` (a language code, or "system" for `system`, the OS or browser
|
||||
/// language). Unknown languages show English.
|
||||
/// languages, most preferred first and comma-separated). Unknown languages show English.
|
||||
pub fn set(pref: &str, system: Option<&str>) {
|
||||
CURRENT.store(resolve(pref, system), Ordering::Relaxed);
|
||||
}
|
||||
|
||||
/// The [`LANGS`] index for a preference: the exact tag ("pt-BR"), else its base language ("es"
|
||||
/// for "es_ES.UTF-8"), else English.
|
||||
/// The [`LANGS`] index for a preference: the first language in it the UI has, else English.
|
||||
fn resolve(pref: &str, system: Option<&str>) -> usize {
|
||||
let want = if pref.is_empty() || pref == "system" { system.unwrap_or("en") } else { pref };
|
||||
let tag = want.split(['.', '@']).next().unwrap_or("").replace('_', "-");
|
||||
want.split(',').find_map(resolve_one).unwrap_or(0)
|
||||
}
|
||||
|
||||
/// 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").
|
||||
fn resolve_one(want: &str) -> Option<usize> {
|
||||
let tag = want.trim().split(['.', '@']).next().unwrap_or("").replace('_', "-");
|
||||
let base = tag.split('-').next().unwrap_or("");
|
||||
let find = |t: &str| LANGS.iter().position(|l| l.code.eq_ignore_ascii_case(t));
|
||||
find(&tag).or_else(|| find(base)).unwrap_or(0)
|
||||
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)
|
||||
}
|
||||
|
||||
fn find(table: &'static [(&'static str, Text)], key: &str) -> Option<&'static Text> {
|
||||
@@ -106,11 +112,13 @@ pub fn trn(key: &str, n: usize, args: &[(&str, &dyn Display)]) -> String {
|
||||
fill(text, &all)
|
||||
}
|
||||
|
||||
/// Whether count `n` takes the "one" form.
|
||||
// shortcut: n == 1 is CLDR's rule for English and Spanish (Spanish "many" for millions reads as
|
||||
// "other" here); add per-language rules when a language needs them (French: 0 and 1).
|
||||
fn plural_one(_lang: usize, n: usize) -> bool {
|
||||
n == 1
|
||||
/// Whether count `n` takes the "one" form: 0 and 1 in French and Brazilian Portuguese, 1 elsewhere
|
||||
/// (CLDR's integer rules).
|
||||
// shortcut: CLDR's "many" (French, Spanish and Portuguese millions) reads as "other" here; add it
|
||||
// when a language needs a third form.
|
||||
fn plural_one(lang: usize, n: usize) -> bool {
|
||||
let zero_is_one = LANGS.get(lang).is_some_and(|l| matches!(l.code, "fr" | "pt-BR"));
|
||||
n == 1 || (n == 0 && zero_is_one)
|
||||
}
|
||||
|
||||
/// Replace each `{name}` in `text` with its argument, in one pass (an argument's own braces stay).
|
||||
|
||||
@@ -301,6 +301,33 @@ fn lookups_fall_back_to_english_and_fill_placeholders() {
|
||||
assert!(matches!(lookup(es, k), Some(x) if std::ptr::eq(x, t)));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn system_languages_pick_the_first_one_with_a_catalog() {
|
||||
let code = |pref: &str, system: Option<&str>| LANGS[resolve(pref, system)].code;
|
||||
assert_eq!(code("system", Some("fr-CA")), "fr");
|
||||
assert_eq!(code("system", Some("de_CH.UTF-8")), "de");
|
||||
assert_eq!(code("system", Some("pt-BR")), "pt-BR");
|
||||
// Portugal's Portuguese and a bare "pt" read the Brazilian catalog.
|
||||
assert_eq!(code("system", Some("pt-PT")), "pt-BR");
|
||||
assert_eq!(code("pt", None), "pt-BR");
|
||||
// An OS list: the first language there is a catalog for (English counts).
|
||||
assert_eq!(code("system", Some("nl-NL,de-DE,en-US")), "de");
|
||||
assert_eq!(code("system", Some("nl-NL, en-US,fr-FR")), "en");
|
||||
assert_eq!(code("system", Some("nl,sv")), "en");
|
||||
assert_eq!(code("fr", Some("de-DE")), "fr", "a chosen language beats the system's");
|
||||
}
|
||||
|
||||
#[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);
|
||||
for code in ["fr", "pt-BR"] {
|
||||
assert!(one(code, 0) && one(code, 1) && !one(code, 2), "{code}");
|
||||
}
|
||||
for code in ["en", "es", "de"] {
|
||||
assert!(!one(code, 0) && one(code, 1) && !one(code, 2), "{code}");
|
||||
}
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn the_key_scanner_reads_rust_literals() {
|
||||
let src = r#"x(tr("A \"b\"")); trf(
|
||||
|
||||
@@ -944,7 +944,7 @@ fn variable_modes(app: &mut FigApp, ui: &mut Ui, doc: &Document, sel: &[NodeId])
|
||||
if name != c.name || r.library.is_some() {
|
||||
ui.interact(rect, Id::new(("var_mode_name", &c.id)), Sense::hover()).on_hover_text(&hover);
|
||||
}
|
||||
let resp = widgets::elided_dropdown(ui, &label, pick_w);
|
||||
let resp = widgets::dropdown(ui, &label, pick_w, None);
|
||||
menu_popup(&resp).width(180.0).show(|ui| {
|
||||
let mut pick = None;
|
||||
if menu_check(ui, own == Some(None), &auto, "") {
|
||||
|
||||
@@ -673,10 +673,10 @@ fn text_properties(ui: &mut Ui, id: &str, def: &TextStyle, out: &mut Frame) {
|
||||
// The dropdowns' menus open outside the dark scope (they're regular popups).
|
||||
let (family_resp, style_resp) = crate::theme::on_dark(ui, |ui| {
|
||||
ui.spacing_mut().item_spacing = vec2(8.0, 6.0);
|
||||
let family_resp = widgets::elided_dropdown(ui, &def.font_family, W).on_hover_text(tr("Font"));
|
||||
let family_resp = widgets::dropdown(ui, &def.font_family, W, None).on_hover_text(tr("Font"));
|
||||
let mut style_resp = None;
|
||||
ui.horizontal(|ui| {
|
||||
style_resp = Some(widgets::elided_dropdown(ui, &def.font_style, half).on_hover_text(tr("Font style")));
|
||||
style_resp = Some(widgets::dropdown(ui, &def.font_style, half, None).on_hover_text(tr("Font style")));
|
||||
if let Some(widgets::FieldEdit::Set(v) | widgets::FieldEdit::Scrub(v, true)) =
|
||||
widgets::number_field(ui, Id::new(("ts_size", id)), widgets::Lead::Text(tr("Size")), Some(def.font_size), "", half, 1.0)
|
||||
{
|
||||
|
||||
@@ -179,7 +179,7 @@ fn font_rows(app: &mut FigApp, ui: &mut Ui, ids: &[NodeId], styles: &[TextStyle]
|
||||
let fstyle = uniform(styles, |s| s.font_style.clone());
|
||||
row(ui, 32.0, |ui| {
|
||||
let w = ui.available_width();
|
||||
let resp = widgets::elided_dropdown(ui, family.as_deref().unwrap_or(tr(MIXED)), w);
|
||||
let resp = widgets::dropdown(ui, family.as_deref().unwrap_or(tr(MIXED)), w, None);
|
||||
menu_popup(&resp).width(w.max(240.0)).close_behavior(egui::PopupCloseBehavior::CloseOnClickOutside).show(|ui| {
|
||||
if let Some(f) = font_picker(ui, family.as_deref(), resp.clicked()) {
|
||||
remember_family(ui.ctx(), &f);
|
||||
@@ -192,7 +192,7 @@ fn font_rows(app: &mut FigApp, ui: &mut Ui, ids: &[NodeId], styles: &[TextStyle]
|
||||
// Style + size.
|
||||
let half = (ui.available_width() - PAD * 2.0 - 8.0) / 2.0;
|
||||
row(ui, 32.0, |ui| {
|
||||
let resp = widgets::elided_dropdown(ui, fstyle.as_deref().unwrap_or(tr(MIXED)), half);
|
||||
let resp = widgets::dropdown(ui, fstyle.as_deref().unwrap_or(tr(MIXED)), half, None);
|
||||
let fam = family.clone();
|
||||
menu_popup(&resp).width(200.0).show(|ui| match &fam {
|
||||
Some(fam) => {
|
||||
@@ -736,7 +736,7 @@ fn named_instances(
|
||||
// An inline list (a menu would close the Type settings popover it opens from).
|
||||
let open_id = Id::new("ty_instances_open");
|
||||
let mut open = ui.data(|d| d.get_temp::<bool>(open_id)).unwrap_or(false);
|
||||
if widgets::elided_dropdown(ui, &label, ui.available_width()).on_hover_text(tr("Named instance")).clicked() {
|
||||
if widgets::dropdown(ui, &label, ui.available_width(), None).on_hover_text(tr("Named instance")).clicked() {
|
||||
open = !open;
|
||||
}
|
||||
if open {
|
||||
|
||||
@@ -503,32 +503,23 @@ pub fn dropdown_lead(ui: &mut Ui, label: &str, width: f32, lead: Option<DropLead
|
||||
}
|
||||
None => {}
|
||||
}
|
||||
let galley = ui.painter().layout(label.to_string(), regular(11.0), t.text, rect.width() - (x - rect.min.x) - 20.0);
|
||||
// One line: a label longer than the field (a variable font style like "SemiBold SemiCondensed",
|
||||
// a long translation) ends with an ellipsis and shows in full on hover.
|
||||
let shown = elide(ui, label, rect.max.x - x - 20.0);
|
||||
let galley = ui.painter().layout_no_wrap(shown.clone(), regular(11.0), t.text);
|
||||
ui.painter().galley(pos2(x, rect.center().y - galley.size().y / 2.0), galley, t.text);
|
||||
icons::paint(ui.painter(), Rect::from_center_size(pos2(rect.max.x - 10.0, rect.center().y), vec2(12.0, 12.0)), Icon::Chevron, t.icon_secondary);
|
||||
resp
|
||||
if shown != label { resp.on_hover_text(label) } else { resp }
|
||||
}
|
||||
|
||||
/// Horizontal space a dropdown takes besides its label (padding and chevron).
|
||||
const DROPDOWN_CHROME: f32 = 30.0;
|
||||
|
||||
/// A dropdown whose label stays on one line: long names (variable font styles like "SemiBold
|
||||
/// SemiCondensed") end with an ellipsis and show in full on hover.
|
||||
pub fn elided_dropdown(ui: &mut Ui, label: &str, width: f32) -> egui::Response {
|
||||
let shown = elide(ui, label, width - DROPDOWN_CHROME);
|
||||
let resp = dropdown(ui, &shown, width, None);
|
||||
if shown != label {
|
||||
// Screen readers get the whole name.
|
||||
a11y::combo(&resp, "", label, a11y::menu_open(&resp));
|
||||
resp.on_hover_text(label)
|
||||
} else {
|
||||
resp
|
||||
}
|
||||
}
|
||||
|
||||
/// `s` shortened with an ellipsis to fit `max_w` on one line.
|
||||
/// `s` shortened with an ellipsis to fit `max_w` on one line at the UI's 11 px.
|
||||
pub fn elide(ui: &Ui, s: &str, max_w: f32) -> String {
|
||||
let fits = |s: &str| ui.painter().layout_no_wrap(s.to_string(), regular(11.0), Color32::WHITE).size().x <= max_w;
|
||||
elide_font(ui, s, max_w, regular(11.0))
|
||||
}
|
||||
|
||||
/// [`elide`] in `font`.
|
||||
pub fn elide_font(ui: &Ui, s: &str, max_w: f32, font: egui::FontId) -> String {
|
||||
let fits = |s: &str| ui.painter().layout_no_wrap(s.to_string(), font.clone(), Color32::WHITE).size().x <= max_w;
|
||||
if fits(s) {
|
||||
return s.to_string();
|
||||
}
|
||||
@@ -899,10 +890,15 @@ pub fn sublabels(ui: &mut Ui, a: &str, b: Option<&str>) {
|
||||
let t = Tokens::get(ui.ctx());
|
||||
let w = ui.available_width();
|
||||
let (r, _) = ui.allocate_exact_size(vec2(w, 18.0), Sense::hover());
|
||||
ui.painter().text(pos2(r.min.x + PAD, r.max.y - 2.0), Align2::LEFT_BOTTOM, a, regular(10.0), t.text_secondary);
|
||||
if let Some(b) = b {
|
||||
let col = col_width(w);
|
||||
ui.painter().text(pos2(r.min.x + PAD + col + 8.0, r.max.y - 2.0), Align2::LEFT_BOTTOM, b, regular(10.0), t.text_secondary);
|
||||
let col = col_width(w);
|
||||
// Each label fits its column on one line (long translations end with an ellipsis, in full on hover).
|
||||
let a_w = if b.is_some() { col } else { w - PAD * 2.0 };
|
||||
for (i, (text, x, max_w)) in [(a, PAD, a_w)].into_iter().chain(b.map(|b| (b, PAD + col + 8.0, w - col - 8.0 - PAD * 2.0))).enumerate() {
|
||||
let shown = elide_font(ui, text, max_w, regular(10.0));
|
||||
let at = ui.painter().text(pos2(r.min.x + x, r.max.y - 2.0), Align2::LEFT_BOTTOM, &shown, regular(10.0), t.text_secondary);
|
||||
if shown != text {
|
||||
ui.interact(at, ui.id().with(("sublabel", i)), Sense::hover()).on_hover_text(text);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
+13
-9
@@ -1,6 +1,6 @@
|
||||
# Where FigCraft falls short of Figma
|
||||
|
||||
> **Last reviewed:** 2026-10-10 · **Last updated:** 2026-10-12 · **Change:** minor (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 (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 and a Spanish draft only (the layer landed 2026-10-11) | Localization | 45–75 | [localization-parity](localization-parity.md) |
|
||||
| 5 | Localization: English plus unreviewed Spanish, French, German and Brazilian Portuguese drafts; no Japanese or Korean | Localization | 40–65 | [localization-parity](localization-parity.md) |
|
||||
| 6 | Plugins: no distribution or widgets; Figma's plugins don't run | Ecosystem | 15–40 | [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) |
|
||||
@@ -100,18 +100,21 @@ No alpha blockers: the alpha gate in [`roadmap.md`](roadmap.md#alpha-gate) passe
|
||||
- **Impact:** Figma users notice the feel within minutes; it decides whether they stay.
|
||||
- **Estimate:** 40–60 h. Detail in [`ui-parity.md`](ui-parity.md).
|
||||
|
||||
### 5. Localization: English and a Spanish draft only
|
||||
- **Missing:** Figma's other UI languages (Japanese, Korean, French, German, Portuguese (Brazil)), a
|
||||
native review of the Spanish draft, localized engine messages and number formatting.
|
||||
### 5. Localization: English plus unreviewed drafts; no Japanese or Korean
|
||||
- **Missing:** Figma's Japanese and Korean, native reviews of the Spanish, French, German and
|
||||
Brazilian Portuguese drafts, localized engine messages and number formatting.
|
||||
- **Evidence (measured from source):** the layer landed 2026-10-11: catalogs in
|
||||
`crates/ui-egui/locales/` (1,280 UI keys plus 501 command labels), Preferences ▸ Language,
|
||||
`pref.language`; `en.toml` is complete and `es.toml` is a complete machine draft. Engine error
|
||||
`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` and `pt-BR.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). Engine error
|
||||
messages and Dev Mode values stay English.
|
||||
- **Impact:** keeps out non-English designers, including Figma's large Japanese and Korean user
|
||||
bases.
|
||||
- **Estimate:** 3–7 h per language plus native review, 4–6 h for engine messages and numbers;
|
||||
45–75 h for the 11 key languages. Detail in [`localization-parity.md`](localization-parity.md);
|
||||
how to add one in [`translating.md`](translating.md).
|
||||
40–65 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).
|
||||
|
||||
### 6. Plugins: no distribution or widgets; Figma's plugins don't run
|
||||
- **Landed (#101):** sandboxed WebAssembly plugins (wasmi, metered, memory cap, no WASI, also in
|
||||
@@ -324,6 +327,7 @@ No alpha blockers: the alpha gate in [`roadmap.md`](roadmap.md#alpha-gate) passe
|
||||
|
||||
| Date | Change | Summary |
|
||||
|---|---|---|
|
||||
| 2026-10-12 | minor | French, German and Brazilian Portuguese draft catalogs (gap 5): every UI string, command label and menu wording (1,899 entries) machine-drafted in each and marked as drafts awaiting native review, falling back to English per key (new strings need only English and Spanish); 0 counts as singular in French and Brazilian Portuguese; System picks the first OS language with a catalog (pt-PT and pt read pt-BR); long translations elide with a tooltip in dropdowns, two-column labels and the home cards. Gap 5 45–75 h → 40–65 h, rank unchanged |
|
||||
| 2026-10-12 | minor | Cursor chat and spotlight in live sessions (gap 2): `/` opens a chat bubble at your pointer, a spotlight makes everyone follow your view; ephemeral presence the host validates and rate-limits. Gap 2 45–90 h → 42–84 h |
|
||||
| 2026-10-12 | minor | Layers and Assets panels behaviour suite and fixes added to gap 4's landed list; ranking and hours unchanged |
|
||||
| 2026-10-12 | minor | Vector multi-edit and the vector edit tail, with a behaviour suite written from the public documentation (5 tests, about 30 table rows and steps, and a junk-parameter sweep of every vector command in multi-edit mode): several vector layers are edited at once (Enter, double-click or `vector.edit.begin {ids}`; a press on another layer's point, handle or segment makes it the active layer; Shift-click, marquee and lasso select across layers; dragging, the arrows, Delete, delete and heal, mirroring, smoothing, corner radius and point caps and joins reach every layer's selected points in one undo step; the pen, bend, paint bucket, cut and eraser work in the active layer); several Bezier handles are selected with Shift-click and dragged together (`vector.edit.moveHandles`); a sharp point next to a curved segment rounds (the curve is cut where the arc meets it and keeps its shape); a point can have its own stroke cap and join (`vector.edit.strokeCap`, `vector.edit.strokeJoin`, the Point section of the Design panel; canvas, PNG, SVG and PDF, outline stroke); the Pen outside edit mode connects to one of its own points and ends the path, and continues a selected open path from an end point. Left: corner smoothing beyond rectangles, the transform box around points of several layers, per-point caps and joins on dashed, variable-width and brush strokes. Gap 9 2–5 h → 1–3 h (pen pressure on macOS and Linux tablets left, blocked on winit). Shapes & vector 98%/72% → 99%/73%, 2–4 h → 1–3 h; Figma Draw extras 80%/55% → 88%/60%, 2–5 h → 1–3 h (gap 9: pen pressure on macOS and Linux tablets left). Totals recomputed by the doc's method, which rows since the last recompute had left stale: the area table's weighted breadth is 83.7% (~83%; core 94.1%, ~94%) and ready 63.1% (~63%; core 72.1%, ~72%), hours 133–284 (core 38–83); Features (depth) 61% → 63%, 46–94 h → 34–76 h; ready for real work 52.6% (~52%); mainstream weekly areas 71.0% → 72.1% (× 0.9 × 0.9 × 0.85 = 49.7%, ~49%, rounded down); essentials unchanged (~57%); full parity ~306–595 h → ~294–572 h (on top of the rows merged since: ~301–587 h → ~289–564 h; area-table totals recomputed on the merged table and rounded down: breadth ~84%, ready ~63%, core ~94%/~72%, mainstream weekly areas 72.4%, ~49%) |
|
||||
|
||||
+35
-27
@@ -1,11 +1,11 @@
|
||||
# Localization parity
|
||||
|
||||
> **Last reviewed:** 2026-10-10 · **Last updated:** 2026-10-11 · **Change:** major (localization layer; English catalog; Spanish as a draft) · **Target:** Figma Design (desktop app 126.10.7)
|
||||
> **Last reviewed:** 2026-10-10 · **Last updated:** 2026-10-12 · **Change:** minor (French, German and Brazilian Portuguese drafts) · **Target:** Figma Design (desktop app 126.10.7)
|
||||
|
||||
FigCraft's interface has a localization layer with an English catalog and a complete Spanish draft
|
||||
(machine-drafted, waiting for a native speaker's 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).
|
||||
FigCraft's interface has a localization layer with an English catalog and complete Spanish, French,
|
||||
German and Brazilian Portuguese 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
|
||||
|
||||
@@ -16,49 +16,56 @@ browser's page translation. The `.lproj` folders in `/Applications/Figma.app` (6
|
||||
Electron/Chromium locale packs, not Figma's own UI languages. Text *content* in any script is
|
||||
supported by both apps (CJK, Arabic and Devanagari shaping, IME); Figma has no vertical text.
|
||||
|
||||
## What FigCraft has (measured 2026-10-11, from source)
|
||||
## What FigCraft has (measured 2026-10-12, from source)
|
||||
|
||||
- **Layer:** one TOML catalog per language in `crates/ui-egui/locales/`, baked into the binary by
|
||||
the UI crate's build script (desktop and web; nothing read at run time). Keys are the English text
|
||||
(`tr("Fill")`, `trf` for `{named}` placeholders, `trn` for counts with one/other plural forms), so
|
||||
a missing translation shows English, never a raw key. One English text that needs two
|
||||
translations takes a context (`"Fill|sizing"`).
|
||||
- **Strings:** 1,280 UI keys in `en.toml` (35 with plural forms; ~110 are labels from engine and
|
||||
document data, such as tool names, blend modes and Dev Mode properties) plus 501 command labels
|
||||
and 29 menu wordings by command id. Command ids, the CLI, MCP and the control channel stay
|
||||
English. Accessibility names come from the same strings.
|
||||
- **Language preference:** Preferences ▸ Language (System, English, Español), applied at once
|
||||
without a restart; System follows the OS (Windows language list, macOS AppleLanguages, the
|
||||
locale variables on Linux and BSD) or the browser's language on the web. `pref.language
|
||||
{language}` sets it from the CLI, MCP and the control channel.
|
||||
- **Strings:** 1,351 UI keys in `en.toml` (36 with plural forms; ~110 are labels from engine and
|
||||
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.
|
||||
- **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.
|
||||
complete languages (Spanish), anything untranslated. The French, German and Brazilian Portuguese
|
||||
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 rules beyond one/other
|
||||
(enough for English and Spanish).
|
||||
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; CLDR's "many" for millions
|
||||
reads as the plural).
|
||||
- **Script support in documents:** Unicode bidi with Arabic shaping and mirrored brackets in text
|
||||
layers; right-to-left list markers and indents; right-to-left names in the layers panel, tabs
|
||||
and canvas labels (visual order, joined Arabic); inline IME composition with the candidate window
|
||||
at the caret; system-font fallback for scripts the UI fonts lack. Devanagari shapes through
|
||||
harfrust. No vertical text (Figma has none either).
|
||||
- **Script support in the UI:** menus widen to their longest label; no mirrored (right-to-left)
|
||||
layout of panels.
|
||||
- **Script support in the UI:** menus widen to their longest label; dropdowns, the two-column
|
||||
labels of the right panel and the home screen's cards keep a long translation on one line with an
|
||||
ellipsis and show it in full on hover (checked in German, the longest); no mirrored
|
||||
(right-to-left) layout of panels.
|
||||
|
||||
## Languages
|
||||
|
||||
| Language | Code | UI strings translated | Dialogs / tooltips / help | Script support | Native review | Status | Hours to full |
|
||||
|---|---|---|---|---|---|---|---|
|
||||
| English | en | 1,810 / 1,810 (100%, source) | yes | Latin | yes (source) | full | 0 |
|
||||
| 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; UI font needs CJK coverage | no | none | 4–7 |
|
||||
| Spanish | es | 1,810 / 1,810 (100%, draft) | yes | Latin | no | draft | 1–2 (es-419 differences, after native review) |
|
||||
| Spanish | es | 1,899 / 1,899 (100%, draft) | yes | Latin | no | draft | 1–2 (es-419 differences, 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 | 0 (0%) | no | Latin | no | none | 3–5 |
|
||||
| Portuguese | pt (pt-BR) | 0 (0%) | no | Latin | no | none | 3–5 |
|
||||
| French | fr | 1,899 / 1,899 (100%, draft) | yes | Latin | no | draft | 1–2 (after native review) |
|
||||
| Portuguese | pt-BR | 1,899 / 1,899 (100%, draft) | yes | Latin | no | draft | 1–2 (after native review) |
|
||||
| Indonesian | id | 0 (0%) | no | Latin | no | none | 3–5 |
|
||||
| Japanese | ja | 0 (0%) | no | CJK text and IME work in documents; UI font needs CJK coverage | no | none | 4–7 |
|
||||
| German | de | 0 (0%) | no | Latin; long words need wider panels | no | none | 3–5 |
|
||||
| German | de | 1,899 / 1,899 (100%, draft) | yes | Latin; long words elide in narrow fields | no | draft | 1–2 (after native review) |
|
||||
| Korean | ko | 0 (0%) | no | Hangul text and IME work in documents; UI font needs coverage | no | none | 3–5 |
|
||||
| Vietnamese | vi | 0 (0%) | no | Latin with stacked diacritics | no | none | 3–5 |
|
||||
|
||||
@@ -68,14 +75,15 @@ 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: **45–75 h**.
|
||||
right-to-left mirroring of the panels is in Arabic's hours. Total: **40–65 h**.
|
||||
|
||||
Priority: Japanese, Korean, Portuguese (Brazil), French and German next (with Spanish, Figma's own
|
||||
languages, so their users expect them), and a native review of the Spanish draft.
|
||||
Priority: Japanese and Korean next (the last of Figma's own languages, so their users expect
|
||||
them), and native reviews of the Spanish, French, German and Brazilian Portuguese drafts.
|
||||
|
||||
## Revision history
|
||||
|
||||
| Date | Change | Summary |
|
||||
|---|---|---|
|
||||
| 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 |
|
||||
| 2026-10-10 | major | Created: English only, string counts measured from source, Figma's eight UI languages from its help center |
|
||||
+1
-1
@@ -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|…}`: the app's interface language, applied live ("system":
|
||||
- `pref.language {language: system|en|es|fr|de|pt-BR|…}`: 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.
|
||||
|
||||
|
||||
+4
-3
@@ -1,6 +1,6 @@
|
||||
# FigCraft roadmap: milestones and what's next
|
||||
|
||||
> **Last reviewed:** 2026-10-10 · **Last updated:** 2026-10-11 · **Change:** minor (`.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 (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
|
||||
@@ -15,8 +15,8 @@ are one Opus 5.5 agent; most milestones split across 4–6 agents on disjoint cr
|
||||
exports, turn estimated percentages into measured ones: 15–30 h.
|
||||
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, Figma's own first (ja, ko, pt-BR, fr, de; es is
|
||||
a draft awaiting native review): 3–7 h per language plus native review.
|
||||
4. **Languages** on the landed localization layer, Figma's own first (ja, ko; es, fr, de and pt-BR
|
||||
are drafts awaiting native review): 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.
|
||||
|
||||
## Alpha gate
|
||||
@@ -72,6 +72,7 @@ blockers.
|
||||
|
||||
| Date | Change | Summary |
|
||||
|---|---|---|
|
||||
| 2026-10-12 | minor | French, German and Brazilian Portuguese draft catalogs (gap 5): every UI string, command label and menu wording (1,899 entries) machine-drafted in each and marked as drafts awaiting native review, falling back to English per key (new strings need only English and Spanish); 0 counts as singular in French and Brazilian Portuguese; System picks the first OS language with a catalog (pt-PT and pt read pt-BR); long translations elide with a tooltip in dropdowns, two-column labels and the home cards. Current focus 4 now lists Japanese and Korean |
|
||||
| 2026-10-11 | minor | `.fig` import, best effort: current focus 1 and the alpha gate's `.fig` row move to the import's tail (30–60 h → 8–15 h); M6 ~55% → ~65%, 37–75 h → 15–30 h |
|
||||
| 2026-10-11 | minor | Display P3 honest minimum (gap 18): the colour picker of a Display P3 file warns when a colour is outside sRGB (Outside sRGB, with what the canvas and exports do), and View ▸ Soft-proof sRGB (`view.proofSrgb`) stripes the canvas pixels sRGB can't show. Why the canvas still shows sRGB: wgpu 30 can present Display P3 or extended-range sRGB surfaces (Metal, DX12, Vulkan, WebGPU canvases), but eframe/egui-wgpu 0.36 configure an 8-bit sRGB surface with no option for the format or colour space, and egui's renderer writes sRGB-encoded UI colours; showing P3 needs those two upstream (or a patched egui-wgpu). Hardware 13–27 h → 12–26 h; gap 18 6–12 h → 5–11 h; percentages, feature breadth (~79%) and ready for real work (~47%) unchanged at this rounding; full parity ~363–697 h → ~362–696 h |
|
||||
| 2026-10-11 | minor | Several windows (gap 18): File ▸ New window, a tab's Open in new window or a tab dragged off the tab strip opens files in another window with its own tabs, panels and canvas, sharing the files, preferences, libraries and clipboard; each window draws only when it needs to (idle: ~0% CPU), closing one closes its files after asking about unsaved changes (the main window too, which takes over another window instead of quitting; the last window quits), and the control channel and MCP open, list, focus and capture windows (`window.new`, `window.list`, `window.focus`, `window` on any request); the screen-reader tree covers the main window only (eframe 0.36), so with a screen reader running files stay tabs; one window in the browser. Hardware 55% → 60%, 13–27 h → 10–21 h; gap 18 6–12 h → 4–8 h (P3 on screen left); feature breadth (~79%) and ready for real work (~47%) unchanged at this rounding; full parity ~363–697 h → ~360–691 h |
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
# FigCraft parity with Figma
|
||||
|
||||
> **Last reviewed:** 2026-10-10 · **Last updated:** 2026-10-12 · **Change:** minor (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 (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
|
||||
@@ -20,8 +20,8 @@ summarizes it; [`gaps.md`](gaps.md) is the ranked work list; the measured menu c
|
||||
| Ready for real work | **~52%** | estimated (dimension table below: written weights × written per-dimension scores) |
|
||||
| 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 | **~170–305 h** one Opus 5.5 agent (~35–70 h wall-clock with 5 parallel agents) | estimated |
|
||||
| Remaining to full parity | **~286–558 h** one agent (~86–150 h wall-clock with 5 parallel agents) | estimated |
|
||||
| Remaining to beta | **~162–292 h** one Opus 5.5 agent (~33–67 h wall-clock with 5 parallel agents) | estimated |
|
||||
| Remaining to full parity | **~281–548 h** one agent (~85–148 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) | ~52% | ~291–560 h | multiplayer (42–84), localization (45–75), plugins (15–40), 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) | ~52% | ~286–550 h | multiplayer (42–84), localization (40–65), plugins (15–40), 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 | 20% | 45–75 | The layer, an English catalog and a complete Spanish draft (unreviewed); Figma ships 7 more UI languages; engine messages stay English | [localization-parity](localization-parity.md) |
|
||||
| Localization | 3 | 30% | 40–65 | The layer, an English catalog and complete machine drafts of Spanish, French, German and Brazilian Portuguese (none reviewed by a native speaker); Figma's Japanese, Korean and Latin American Spanish are missing; 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 | 28% | 15–40 | Own WASM plugins with a Rust SDK and two examples (#101), plugin panels and settings (ABI v2); no Figma plugin compatibility, no widgets, no distribution 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 | **~52%** | **~286–558** | | |
|
||||
| **Ready for real work** | 100 | **~52%** | **~281–548** | | |
|
||||
|
||||
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
|
||||
@@ -612,6 +612,7 @@ Carried over from the ROADMAP's "Shipped so far" (2026-10-10). The test count th
|
||||
|
||||
| Date | Change | Summary |
|
||||
|---|---|---|
|
||||
| 2026-10-12 | minor | French, German and Brazilian Portuguese draft catalogs (gap 5): every UI string, command label and menu wording (1,899 entries) machine-drafted in each and marked as drafts awaiting native review, falling back to English per key (new strings need only English and Spanish); 0 counts as singular in French and Brazilian Portuguese; System picks the first OS language with a catalog (pt-PT and pt read pt-BR); long translations elide with a tooltip in dropdowns, two-column labels and the home cards. Localization 20% → 30%, 45–75 h → 40–65 h; ready for real work unchanged (~52%; 52.6 → 52.9); full parity ~289–564 h → ~284–554 h; beta ~170–305 h → ~162–292 h (Figma's languages left on the way to beta: Japanese and Korean, 7–12 h) |
|
||||
| 2026-10-12 | minor | Cursor chat and spotlight in live sessions: `/` opens a chat bubble at your pointer that everyone sees as you type and that fades a few seconds after the last change; a spotlight (your avatar's menu, `collab.spotlight`) makes everyone follow your view and page, latecomers included, until they move their own. Both are ephemeral presence the host validates and rate-limits, passed only to clients that read them. Multiplayer & sharing breadth 15% → 17%, ready unchanged (5%), 45–90 h → 42–84 h; the Collaboration dimension's hours 55–110 → 52–104 h (the dimension hours still add up to the total); feature breadth (~84%) and ready for real work (~51%) unchanged at this rounding; full parity ~301–587 h → ~298–581 h |
|
||||
| 2026-10-12 | minor | Layers and Assets panels behaviour suite written from the public documentation (6 engine tests with about 50 table rows and 5 panel tests driven through the UI; about 30 rows that the code before the fixes contradicts) and its fixes: several layers dropped together in the layers panel land together (layers from apart split around the drop point); Collapse layers keeps the selection's rows open (it closed everything); the eye and padlock stay under the pointer (they vanished as it reached them, so clicks could miss) and dragging across them sets several layers in one undo step; several layers selected open a Rename layers dialog (match as text or a regular expression, Rename to, current name and number buttons, a start value, preview) instead of renaming the top one; `object.batchRename` counts `$n` up and `$N` down (they were swapped), pads with `$nnn`, takes `start`, regular expressions with `$1`…`$9`, `` $` `` / `$'` and codes in the replacement; pages are dragged to reorder, a blank page named with a hyphen is a divider (never opened, holds no layers), duplicates are named "Copy of …", the last page that isn't a divider can't be deleted; the Assets panel files components by page, frame and slash names in folders that fold away (`component.list` gives each `folder`); a leading `.` or `_` now keeps a component out of the library even after it was published, and removing it brings the component back. Pinned where the documentation is silent: Alt-click on an arrow, numbering in stacking order, hidden layers and locked frames taking drops, locking deselects. Layers panel 98%/82% → 99%/85%; percentages and full parity hours unchanged at this rounding |
|
||||
| 2026-10-12 | minor | Vector multi-edit and the vector edit tail, with a behaviour suite written from the public documentation (5 tests, about 30 table rows and steps, and a junk-parameter sweep of every vector command in multi-edit mode): several vector layers are edited at once (Enter, double-click or `vector.edit.begin {ids}`; a press on another layer's point, handle or segment makes it the active layer; Shift-click, marquee and lasso select across layers; dragging, the arrows, Delete, delete and heal, mirroring, smoothing, corner radius and point caps and joins reach every layer's selected points in one undo step; the pen, bend, paint bucket, cut and eraser work in the active layer); several Bezier handles are selected with Shift-click and dragged together (`vector.edit.moveHandles`); a sharp point next to a curved segment rounds (the curve is cut where the arc meets it and keeps its shape); a point can have its own stroke cap and join (`vector.edit.strokeCap`, `vector.edit.strokeJoin`, the Point section of the Design panel; canvas, PNG, SVG and PDF, outline stroke); the Pen outside edit mode connects to one of its own points and ends the path, and continues a selected open path from an end point. Left: corner smoothing beyond rectangles, the transform box around points of several layers, per-point caps and joins on dashed, variable-width and brush strokes. Shapes & vector 98%/72% → 99%/73%, 2–4 h → 1–3 h; Figma Draw extras 80%/55% → 88%/60%, 2–5 h → 1–3 h (gap 9: pen pressure on macOS and Linux tablets left). Totals recomputed by the doc's method, which rows since the last recompute had left stale: the area table's weighted breadth is 83.7% (~83%; core 94.1%, ~94%) and ready 63.1% (~63%; core 72.1%, ~72%), hours 133–284 (core 38–83); Features (depth) 61% → 63%, 46–94 h → 34–76 h; ready for real work 52.6% (~52%); mainstream weekly areas 71.0% → 72.1% (× 0.9 × 0.9 × 0.85 = 49.7%, ~49%, rounded down); essentials unchanged (~57%); full parity ~306–595 h → ~294–572 h (on top of the rows merged since: ~301–587 h → ~289–564 h; area-table totals recomputed on the merged table and rounded down: breadth ~84%, ready ~63%, core ~94%/~72%, mainstream weekly areas 72.4%, ~49%) |
|
||||
|
||||
+23
-6
@@ -3,8 +3,13 @@
|
||||
FigCraft's interface strings live in one TOML catalog per language in
|
||||
[`crates/ui-egui/locales/`](../crates/ui-egui/locales). The build bakes every catalog into the
|
||||
binary (desktop and web); nothing is read at run time. Preferences ▸ Language picks the language
|
||||
(System follows the OS, or the browser on the web) and the interface switches at once. `pref.language
|
||||
{language}` does the same from the CLI, MCP and the control channel.
|
||||
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`).
|
||||
`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`) and
|
||||
Brazilian Portuguese (`pt-BR`), machine-drafted and waiting for native speakers' review.
|
||||
|
||||
## The catalogs
|
||||
|
||||
@@ -22,6 +27,10 @@ binary (desktop and web); nothing is read at run time. Preferences ▸ Language
|
||||
|
||||
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.
|
||||
|
||||
## Adding a language
|
||||
|
||||
1. Copy `crates/ui-egui/locales/es.toml` to `<code>.toml` (a BCP 47 code: `fr`, `pt-BR`, `ja`) and
|
||||
@@ -29,14 +38,22 @@ A key a catalog lacks shows its English text, so a partial catalog is safe to sh
|
||||
2. Translate every value; keep the keys. Keep `{placeholders}` exactly, "…" where English has it,
|
||||
brand names and keyboard glyphs. English labels of commands: `figcraft-cli commands`.
|
||||
3. Plurals: give `one` only if the language has a singular form (Japanese, Korean and Chinese give
|
||||
only `other`). `trn` picks `one` for a count of 1; a language with other rules (French: 0 and 1)
|
||||
needs its rule in `plural_one` in [`crates/ui-egui/src/i18n.rs`](../crates/ui-egui/src/i18n.rs).
|
||||
only `other`). `trn` picks `one` for a count of 1, and also for 0 in French and Brazilian
|
||||
Portuguese (keep `{n}` in their `one` forms); a language with other rules needs its rule in
|
||||
`plural_one` in [`crates/ui-egui/src/i18n.rs`](../crates/ui-egui/src/i18n.rs).
|
||||
4. To require full coverage, add the code to `COMPLETE` in
|
||||
[`crates/ui-egui/src/i18n_tests.rs`](../crates/ui-egui/src/i18n_tests.rs).
|
||||
[`crates/ui-egui/src/i18n_tests.rs`](../crates/ui-egui/src/i18n_tests.rs). Every later PR that
|
||||
adds a string then has to translate it into each complete language, so only Spanish is there:
|
||||
French, German and Brazilian Portuguese are complete today but registered as drafts, and a
|
||||
string added later shows English in them until it is translated. To list what a draft lacks,
|
||||
add its code to `COMPLETE` locally and run the check.
|
||||
5. Run the check and look at the panels in the app (longer words must fit the right panel):
|
||||
|
||||
cargo test -p figcraft-ui-egui i18n
|
||||
|
||||
Dropdowns, the right panel's two-column labels and the home cards cut a label that doesn't fit
|
||||
with "…" and show it in full on hover; elsewhere, look for clipped or wrapped text.
|
||||
|
||||
The check fails on keys the code uses that `en.toml` lacks (it prints the lines to paste), keys no
|
||||
code uses, unknown command ids, placeholders that don't match English, and, for complete languages,
|
||||
anything untranslated. Machine-drafted catalogs stay marked as drafts until a native speaker has
|
||||
@@ -48,7 +65,7 @@ Wrap every string a user sees or a screen reader hears: `tr` for plain text, `tr
|
||||
placeholders, `trn` for counts, `tr_noop` for literals in `const` tables that are looked up later
|
||||
with `tr(label)`. The key must be a string literal as the first argument (the check reads the
|
||||
source). Don't wrap ids, command ids, JSON, log messages, file extensions or document content. Then
|
||||
add the key to `en.toml` and to each complete language.
|
||||
add the key to `en.toml` and to each complete language (Spanish); the drafts may follow later.
|
||||
|
||||
Labels from the engine and document crates (tool names, blend modes, easing curves…) are looked up
|
||||
at display with `tr(x.label())`; they are listed in `dynamic_labels` in `i18n_tests.rs`.
|
||||
Reference in new issue
Block a user