M9: add Russian interface translation

This commit is contained in:
VanTan
2026-10-14 09:33:58 +03:00
parent bcedfeba10
commit 7a0bd980c6
15 changed files with 2366 additions and 70 deletions
+1 -1
View File
@@ -227,7 +227,7 @@ The desktop app also offers a loopback-only control channel (`figcraft --control
- **Its own renderer:** tiny-skia on every core for the canvas and exports, with layer blending, effects and masks; booleans from linesweeper, geometry from kurbo, text from skrifa and HarfRust.
- **Runs in the browser:** the whole engine and UI compile to WebAssembly.
- **Accessible:** screen readers get names, roles, values and states for the tools, layer rows, fields, dropdowns, tabs and menus (AccessKit on desktop), F6 moves the keyboard between the toolbar, layers, canvas and properties (Tab within them, Escape back to the canvas), the system's reduce-motion setting is honoured, and panel text meets WCAG AA contrast in both themes.
- **Languages:** English and Spanish (a draft waiting for a native speaker's review), picked in Preferences or taken from the system or browser and switched live; adding a language is one catalog file ([`docs/translating.md`](docs/translating.md)).
- **Languages:** English, Russian (reviewed by VanTan), and Spanish, French, German, Brazilian Portuguese, Japanese, Korean and Simplified Chinese drafts, picked in Preferences or taken from the system or browser and switched live; adding a language is one catalog file ([`docs/translating.md`](docs/translating.md)).
- **Plugins:** sandboxed WebAssembly plugins (a metered, pure-Rust interpreter with a memory cap and no file or network access, in the browser too) that act through the same commands, each run one undo step, within the permissions their manifest asks for. A Rust SDK and two example plugins are in [`plugins/`](plugins); see [`docs/plugins.md`](docs/plugins.md).
- **Live sessions (preview):** share the open file from the app (Share ▸ Start sharing: online through a server, or on your local network), give the others the short join code it shows (or the invite link), and edit it together: everyone's changes sync live, with cursors, selections, avatars and follow, and undo takes back only your own changes. Others join from the desktop app (invite links open it) or from the browser build. See [`docs/collab-protocol.md`](docs/collab-protocol.md).
- **A server of your own (preview):** `figcraft-server` keeps a team's files, publishes them from the app (File ▸ Publish to server…, or Share ▸ Online with your server in Preferences) and hands out share links and join codes that let people edit or only view; files survive restarts and apps reconnect where they were. No accounts yet: links are the access. Releases ship it for Linux, and the desktop app joins over `wss://`; [hosting it on a rented Linux server](docs/server.md#deploy-on-a-linux-server) takes a systemd unit and Caddy or nginx (or Docker). See [`docs/server.md`](docs/server.md).
+6 -4
View File
@@ -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-14 · **Change:** minor (recent files as cards with thumbnails on the home screen; community fixes: lost images reported on loading (#207), SVG import budgets (#190), Leptos code (#223), full screen (#227), server snapshots readable again (#188), owner-only preferences (#200), ruzstd 0.9.1 (#182), layer cycle repair (#205), batch rollback, Direct3D 12 on Windows, plugin metering (#192); vector edit mode regions: hover hatch, selection and drag; SVG/PDF exports refuse over-depth documents (#208); bounded MCP/control framing, connections and queues (#201); plugin log limits (#191); live-session revert guard (#198); attached MCP no longer replays after a lost reply (#194); repeat command recursion fix; the Layout panel's constrain-proportions link scales the other side, #220; core editor leftovers: component playground, auto gap handles, variables inside styles, inner shadows and background blur on groups, command-made text readable on dark pages, a one-step frame/group convert, delays on mouse triggers and manual overlay offsets, wrapped-line alignment; `.fig` export: instance sizes, variables with modes, shared styles, variable-width strokes; starter templates, a Templates tab and Getting started tips on the home screen; your online files; everyday editing partials: number fields, outline options, Hide when publishing, cursors, panel widths, dark pages; sharing with a join code; text box sizes measured against documents re-saved by other software; live sessions per document; .fig export checked by opening written files in other software; a public figcraft-server: open hosting, owner tokens, per-address limits, idle expiry, moderation, install.sh; experimental .fig export; keyboard shortcut parity suite and bindings; Simplified Chinese draft catalog; 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)
> **Last reviewed:** 2026-10-10 · **Last updated:** 2026-10-14 · **Change:** minor (Russian catalog and integer plural forms; recent files as cards with thumbnails on the home screen; community fixes: lost images reported on loading (#207), SVG import budgets (#190), Leptos code (#223), full screen (#227), server snapshots readable again (#188), owner-only preferences (#200), ruzstd 0.9.1 (#182), layer cycle repair (#205), batch rollback, Direct3D 12 on Windows, plugin metering (#192); vector edit mode regions: hover hatch, selection and drag; SVG/PDF exports refuse over-depth documents (#208); bounded MCP/control framing, connections and queues (#201); plugin log limits (#191); live-session revert guard (#198); attached MCP no longer replays after a lost reply (#194); repeat command recursion fix; the Layout panel's constrain-proportions link scales the other side, #220; core editor leftovers: component playground, auto gap handles, variables inside styles, inner shadows and background blur on groups, command-made text readable on dark pages, a one-step frame/group convert, delays on mouse triggers and manual overlay offsets, wrapped-line alignment; `.fig` export: instance sizes, variables with modes, shared styles, variable-width strokes; starter templates, a Templates tab and Getting started tips on the home screen; your online files; everyday editing partials: number fields, outline options, Hide when publishing, cursors, panel widths, dark pages; sharing with a join code; text box sizes measured against documents re-saved by other software; live sessions per document; .fig export checked by opening written files in other software; a public figcraft-server: open hosting, owner tokens, per-address limits, idle expiry, moderation, install.sh; experimental .fig export; keyboard shortcut parity suite and bindings; Simplified Chinese draft catalog; 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
@@ -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 | 39% (layer; English; Spanish with a Latin American overlay, French, German, Brazilian Portuguese, Japanese, Korean, Simplified Chinese drafts; CJK UI fonts) | 29–51 | [localization-parity](docs/localization-parity.md) |
| Localization | 39% (layer; English; Spanish with a Latin American overlay, French, German, Brazilian Portuguese, Japanese, Korean, Simplified Chinese drafts; reviewed Russian; CJK UI fonts) | 29–51 | [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) |
@@ -99,6 +99,7 @@ native speakers' reviews of the six language drafts need people.
| Language | Code | Strings | Status |
|---|---|---|---|
| English | en | 100% | full |
| Russian | ru | 100% (2,165 entries) | wording reviewed by VanTan |
| Simplified Chinese | zh-Hans | 100% | draft (needs native review) |
| Spanish | es, es-419 | 100% (es-419: an overlay of 98 entries) | draft (needs native review) |
| Hindi | hi | 0% | none |
@@ -111,9 +112,9 @@ native speakers' reviews of the six language drafts need people.
| Korean | ko | 100% | draft (needs native review) |
| Vietnamese | vi | 0% | none |
No other languages. Figma ships English, Japanese, Korean, French, German, Spanish and Portuguese
Figma ships English, Japanese, Korean, French, German, Spanish and Portuguese
(Brazil); FigCraft has the localization layer, English and Spanish (Spain and Latin America),
French, German, Brazilian Portuguese, Japanese, Korean and Simplified Chinese drafts (Preferences ▸
French, German, Brazilian Portuguese, Japanese, Korean and Simplified Chinese drafts, and Russian reviewed by VanTan (Preferences ▸
Language; Traditional Chinese has no catalog; 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,
@@ -312,6 +313,7 @@ Ranked; detail and milestones in [`docs/roadmap.md`](docs/roadmap.md).
| Date | Change | Summary |
|---|---|---|
| 2026-10-14 | minor | Russian UI catalog (M9): 1,577 UI keys, 557 command labels and 31 menu wordings, reviewed and approved by VanTan. Preferences lists Русский; ru regional tags follow it. Russian integer plurals use one/few/many with other as fallback; existing languages keep their rules. Contributor display name VanTan added. Headline estimates unchanged. |
| 2026-10-14 | minor | Home screen recent files as cards (M7): each card shows the picture its file carries on its first page's colour, the file's icon, name and "Edited ... ago"; sort by name, creation or last change either way; a table view; a click selects, a double-click or Enter opens, arrow keys move; Open, Show in folder and Remove from recents on right-click. Numbers unchanged. |
| 2026-10-14 | minor | Lost images reported on loading (#207): a `.figcraft` file whose image member is damaged, too large or missing no longer loads in silence; each lost image (up to 20, then a count) is listed in `repairs` on `file.open` with its hash and the reason, and the app shows it as a toast. Saving still writes the file without the lost images. Numbers unchanged |
| 2026-10-14 | minor | SVG import budgets (#190): gzipped SVG is unpacked by FigCraft within 64 MiB before parsing (it was unpacked without a limit), so the nesting check sees the real document; SVG documents embedded in `<image>` data URIs share one 32 MiB budget and at most 4 levels, and one past a budget is left out. Linked files stay unread. Numbers unchanged |
+3
View File
@@ -16,3 +16,6 @@
#
# To remove your names, delete your entry (or set fields empty) in a PR. The next
# contributors.py run, and the next build, drop them.
[people.VanTan666]
display_name = "VanTan"
+17 -11
View File
@@ -369,18 +369,24 @@ pub fn specs() -> Vec<CommandSpec> {
Ok(json!({"url": s.prefs.publish_server, "default": crate::live::DEFAULT_SERVER}))
}
),
spec!("pref.language", "Interface language", "{language: system|en|es|es-419|fr|de|pt-BR|ja|ko|zh-Hans|…; else 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".
let tag = lang.len() <= 35
&& lang.split(['-', '_']).all(|part| !part.is_empty() && part.len() <= 8 && part.chars().all(|c| c.is_ascii_alphanumeric()));
if !tag {
return Err(bad("pref.language", format!("`{lang}` is not a language code")));
spec!(
"pref.language",
"Interface language",
"{language: system|en|ru|es|es-419|fr|de|pt-BR|ja|ko|zh-Hans|…; else 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".
let tag = lang.len() <= 35
&& lang.split(['-', '_']).all(|part| !part.is_empty() && part.len() <= 8 && part.chars().all(|c| c.is_ascii_alphanumeric()));
if !tag {
return Err(bad("pref.language", format!("`{lang}` is not a language code")));
}
s.prefs.language = lang.to_string();
Ok(json!({"language": s.prefs.language}))
}
s.prefs.language = lang.to_string();
Ok(json!({"language": s.prefs.language}))
}),
),
spec!("app.preferences", "Preferences", "{}", false, |s, _| ui(s, "dialog.preferences")),
spec!("app.actions", "Actions", "{}", false, |s, _| ui(s, "palette.actions")),
spec!("help.shortcuts", "Keyboard shortcuts", "{}", false, |s, _| ui(s, "panel.shortcuts")),
+11 -3
View File
@@ -71,7 +71,7 @@ fn locales() -> String {
o
}
/// The rows of one catalog table: `"key" = "text"` or `"key" = { one = "…", other = "…" }`.
/// The rows of one catalog table: strings or plural forms (`other` required).
fn table(o: &mut String, path: &std::path::Path, t: Option<&toml::Value>) {
let Some(t) = t.and_then(toml::Value::as_table) else { return };
// Sorted by key (byte order), as the lookup's binary search needs.
@@ -81,8 +81,16 @@ fn table(o: &mut String, path: &std::path::Path, t: Option<&toml::Value>) {
let s = |k: &str| v.get(k).and_then(toml::Value::as_str);
match (v.as_str(), s("one"), s("other")) {
(Some(text), ..) => _ = writeln!(o, " ({k:?}, Text::Plain({text:?})),"),
(None, one, Some(other)) => _ = writeln!(o, " ({k:?}, Text::Plural({}, {other:?})),", name(one)),
_ => println!("cargo::warning={}: `{k}` is neither a string nor {{ one, other }}; left out", path.display()),
(None, one, Some(other)) => {
_ = writeln!(
o,
" ({k:?}, Text::Plural {{ one: {}, few: {}, many: {}, other: {other:?} }}),",
name(one),
name(s("few")),
name(s("many")),
);
}
_ => println!("cargo::warning={}: `{k}` is neither a string nor plural forms with `other`; left out", path.display()),
}
}
}
File diff suppressed because it is too large Load Diff
+55 -17
View File
@@ -13,8 +13,13 @@ use std::sync::atomic::{AtomicUsize, Ordering};
/// One catalog entry.
pub enum Text {
Plain(&'static str),
/// Plural forms: `one` (when the language has it) and `other`.
Plural(Option<&'static str>, &'static str),
/// Optional language-specific plural forms, with `other` as the fallback.
Plural {
one: Option<&'static str>,
few: Option<&'static str>,
many: Option<&'static str>,
other: &'static str,
},
}
/// A language's catalog (generated from `locales/<code>.toml`).
@@ -104,7 +109,7 @@ fn lookup(lang: usize, key: &str) -> Option<&'static Text> {
/// two translations gets a context after `|` ("Fill|sizing"); the English catalog maps it back.
pub fn tr(key: &str) -> &str {
match lookup(CURRENT.load(Ordering::Relaxed), key) {
Some(Text::Plain(s) | Text::Plural(_, s)) => s,
Some(Text::Plain(s) | Text::Plural { other: s, .. }) => s,
None => key,
}
}
@@ -123,26 +128,59 @@ pub fn trf(key: &str, args: &[(&str, &dyn Display)]) -> String {
/// The plural form of `key` (its English "other" form) for count `n`, with `{n}` and `args` filled.
pub fn trn(key: &str, n: usize, args: &[(&str, &dyn Display)]) -> String {
let lang = CURRENT.load(Ordering::Relaxed);
let text = match lookup(lang, key) {
Some(Text::Plural(one, other)) => one.filter(|_| plural_one(lang, n)).unwrap_or(other),
Some(Text::Plain(s)) => s,
None => key,
};
let text = counted(lang, key, n);
let mut all: Vec<(&str, &dyn Display)> = vec![("n", &n)];
all.extend_from_slice(args);
fill(text, &all)
}
/// Pick a plural using the catalog that supplies the text: an English fallback keeps English's
/// rule, even when the selected language is Russian.
fn counted(lang: usize, key: &str, n: usize) -> &str {
chain(lang)
.find_map(|l| {
find(l.ui, key).map(|text| match text {
Text::Plain(s) => *s,
Text::Plural { one, few, many, other } => match plural(l.code, n) {
Plural::One => one.unwrap_or(other),
Plural::Few => few.unwrap_or(other),
Plural::Many => many.unwrap_or(other),
Plural::Other => other,
},
})
})
.unwrap_or(key)
}
#[derive(Debug, PartialEq, Eq)]
enum Plural {
One,
Few,
Many,
Other,
}
/// CLDR integer rules. Existing one/other catalogs may omit the extra forms.
fn plural(code: &str, n: usize) -> Plural {
match code {
"ru" if n % 10 == 1 && n % 100 != 11 => Plural::One,
"ru" if (2..=4).contains(&(n % 10)) && !(12..=14).contains(&(n % 100)) => Plural::Few,
"ru" => Plural::Many,
"ja" | "ko" | "zh-Hans" => Plural::Other,
"fr" | "pt-BR" if n <= 1 => Plural::One,
_ if n == 1 => Plural::One,
_ => Plural::Other,
}
}
// CLDR's million-count "many" in French, Spanish and Portuguese still reads their existing
// "other" wording. Only Russian currently supplies separate few/many forms.
/// Whether count `n` takes the "one" form: 0 and 1 in French and Brazilian Portuguese, never in
/// Japanese, Korean and Chinese (no plural forms: always "other"), 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.
#[cfg(test)]
fn plural_one(lang: usize, n: usize) -> bool {
match LANGS.get(lang).map(|l| l.code) {
Some("ja" | "ko" | "zh-Hans") => false,
Some("fr" | "pt-BR") => n <= 1,
_ => n == 1,
}
plural(LANGS.get(lang).map_or("en", |l| l.code), n) == Plural::One
}
/// Replace each `{name}` in `text` with its argument, in one pass (an argument's own braces stay).
@@ -186,8 +224,8 @@ 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 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),
Some(Text::Plain(s) | Text::Plural { other: s, .. }) if english.ends_with('…') && !s.ends_with('…') => Cow::Owned(format!("{s}…")),
Some(Text::Plain(s) | Text::Plural { other: s, .. }) => Cow::Borrowed(s),
None => Cow::Borrowed(english),
}
}
+49 -5
View File
@@ -6,7 +6,7 @@ use std::collections::BTreeSet;
use super::*;
/// Languages that must translate every UI string and command label.
const COMPLETE: &[&str] = &["es"];
const COMPLETE: &[&str] = &["es", "ru"];
/// Keys looked up from data rather than written as `tr("…")` in the code: the main menu's
/// submenu names (the engine's menu catalog) and labels of engine and document enums.
@@ -216,7 +216,7 @@ fn placeholders(s: &str) -> BTreeSet<&str> {
fn forms(t: &Text) -> Vec<&'static str> {
match t {
Text::Plain(s) => vec![s],
Text::Plural(one, other) => one.iter().copied().chain([*other]).collect(),
Text::Plural { one, few, many, other } => one.iter().chain(few).chain(many).copied().chain([*other]).collect(),
}
}
@@ -265,10 +265,10 @@ fn catalogs_have_known_keys_and_matching_placeholders() {
for (i, form) in forms(t).iter().enumerate() {
let got = placeholders(form);
// A "one" form may spell the count out ("una capa").
let ok = if matches!(t, Text::Plural(Some(_), _)) && i == 0 { got.is_subset(&want) } else { got == want };
let ok = if matches!(t, Text::Plural { one: Some(_), .. }) && i == 0 { got.is_subset(&want) } else { got == want };
assert!(ok, "{}: {k:?} has placeholders {got:?}, English has {want:?}", lang.code);
}
assert_eq!(matches!(t, Text::Plural(..)), matches!(src, Text::Plural(..)), "{}: {k:?} plural or not as in English", lang.code);
assert_eq!(matches!(t, Text::Plural { .. }), matches!(src, Text::Plural { .. }), "{}: {k:?} plural or not as in English", lang.code);
}
for (id, t) in lang.commands.iter().chain(lang.menu) {
assert!(ids.contains(id), "{}: unknown command id {id:?}", lang.code);
@@ -369,7 +369,7 @@ fn japanese_korean_and_chinese_have_no_plural_forms() {
for code in ["ja", "ko", "zh-Hans"] {
let lang = LANGS.iter().position(|l| l.code == code).unwrap();
assert!(!plural_one(lang, 0) && !plural_one(lang, 1) && !plural_one(lang, 2), "{code}");
let ones: Vec<_> = LANGS[lang].ui.iter().filter(|(_, t)| matches!(t, Text::Plural(Some(_), _))).map(|(k, _)| k).collect();
let ones: Vec<_> = LANGS[lang].ui.iter().filter(|(_, t)| matches!(t, Text::Plural { one: Some(_), .. })).map(|(k, _)| k).collect();
assert!(ones.is_empty(), "{code}: plural entries give only `other`: {ones:?}");
}
}
@@ -397,6 +397,50 @@ fn the_key_scanner_reads_rust_literals() {
assert_eq!(literals(src), vec!["A \"b\"", "C {n}", "DE"]);
}
#[test]
fn russian_language_tags_and_integer_plurals() {
let ru = LANGS.iter().position(|l| l.code == "ru").unwrap();
assert_eq!(LANGS[ru].name, "Русский");
for tag in ["ru", "ru-RU", "ru_RU.UTF-8", "RU", "ru-BY", "ru-Cyrl-RU"] {
assert_eq!(resolve(tag, None), ru, "{tag}");
assert_eq!(resolve("system", Some(tag)), ru, "{tag}");
}
assert_eq!(resolve("system", Some("nl,ru-RU,en-US")), ru);
for n in [1, 21, 31, 101, 1_001] {
assert_eq!(plural("ru", n), Plural::One, "{n}");
assert_eq!(fill(counted(ru, "{n} layers", n), &[("n", &n)]), format!("{n} слой"));
}
for n in [2, 3, 4, 22, 23, 24, 102, 104, 1_002] {
assert_eq!(plural("ru", n), Plural::Few, "{n}");
assert_eq!(fill(counted(ru, "{n} layers", n), &[("n", &n)]), format!("{n} слоя"));
}
for n in [0, 5, 10, 11, 12, 13, 14, 19, 20, 25, 100, 111, 112, 114, 1_000_000] {
assert_eq!(plural("ru", n), Plural::Many, "{n}");
assert_eq!(fill(counted(ru, "{n} layers", n), &[("n", &n)]), format!("{n} слоёв"));
}
for (key, text) in LANGS[ru].ui {
if let Text::Plural { one, few, many, .. } = text {
assert!(one.is_some() && few.is_some() && many.is_some(), "{key}: missing Russian plural forms");
}
}
}
#[test]
fn plural_fallback_uses_the_language_of_its_catalog() {
let la = LANGS.iter().position(|l| l.code == "es-419").unwrap();
assert_eq!(counted(la, "{n} layers", 1), "{n} capa");
assert_eq!(counted(la, "{n} layers", 21), "{n} capas");
// Unsupported optional categories use other; existing one/other languages are unchanged.
for code in ["en", "es", "de"] {
for n in [0, 2, 11, 21, 1_000_000] {
assert_eq!(plural(code, n), Plural::Other, "{code}: {n}");
}
assert_eq!(plural(code, 1), Plural::One, "{code}");
}
assert_eq!(counted(0, "{n} layers", 21), "{n} layers");
assert_eq!(counted(0, "unknown {n}", 21), "unknown {n}");
}
#[test]
fn the_language_preference_takes_language_codes() {
let mut s = figcraft_engine::Session::new();
+3 -2
View File
@@ -111,7 +111,8 @@ pub fn rail(app: &mut FigApp, ui: &mut Ui) {
ui.painter().rect_filled(ir, CornerRadius::same(6), t.hover);
}
icons::paint(ui.painter(), Rect::from_center_size(ir.center(), vec2(16.0, 16.0)), icon, if active { t.accent_text } else { t.icon });
ui.painter().text(pos2(r.center().x, y + 38.0), Align2::CENTER_CENTER, label, regular(10.0), t.text);
let shown = crate::widgets::elide_font(ui, label, (r.width() - 6.0).max(0.0), regular(10.0));
ui.painter().text(pos2(r.center().x, y + 38.0), Align2::CENTER_CENTER, shown, regular(10.0), t.text);
if resp.clicked() {
match tab {
Some(tb) => {
@@ -677,7 +678,7 @@ fn layers(app: &mut FigApp, ui: &mut Ui) {
ui.horizontal(|ui| {
ui.add_space(16.0);
let hint = if filter.is_empty() { tr("Add a frame (F) or a shape (R) to get started") } else { tr("No matching layers") };
crate::widgets::label(ui, hint, t.text_secondary);
ui.add(egui::Label::new(egui::RichText::new(hint).font(regular(11.0)).color(t.text_secondary)).wrap());
});
}
// Dragging near the list's top or bottom edge scrolls it, faster closer to the edge.
+7 -5
View File
@@ -1,6 +1,6 @@
# Where FigCraft falls short of Figma
> **Last reviewed:** 2026-10-10 · **Last updated:** 2026-10-13 · **Change:** minor (`.fig` export: instance sizes, variables with modes, shared styles, variable-width strokes; sharing with a join code; text box sizes measured against documents re-saved by other software; live sessions per document; .fig export checked by opening written files in other software; experimental .fig export; keyboard shortcut parity suite and bindings; Simplified Chinese draft catalog; 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)
> **Last reviewed:** 2026-10-10 · **Last updated:** 2026-10-14 · **Change:** minor (reviewed Russian catalog and integer plurals; `.fig` export: instance sizes, variables with modes, shared styles, variable-width strokes; sharing with a join code; text box sizes measured against documents re-saved by other software; live sessions per document; .fig export checked by opening written files in other software; experimental .fig export; keyboard shortcut parity suite and bindings; Simplified Chinese draft catalog; 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 (and Latin American Spanish), French, German, Brazilian Portuguese, Japanese, Korean and Simplified Chinese drafts | Localization | 29–51 | [localization-parity](localization-parity.md) |
| 5 | Localization: English, reviewed Russian, plus unreviewed Spanish (and Latin American Spanish), French, German, Brazilian Portuguese, Japanese, Korean and Simplified Chinese drafts | Localization | 29–51 | [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) |
@@ -142,7 +142,7 @@ 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 plus unreviewed drafts
### 5. Localization: English, reviewed Russian, plus unreviewed drafts
- **Missing:** native reviews of the Spanish, French, German, Brazilian Portuguese, Japanese,
Korean and Simplified Chinese drafts and the Latin American Spanish overlay, the 25 newest strings
in five drafts, Hindi, Arabic, Indonesian and Vietnamese, localized engine messages and number
@@ -152,8 +152,9 @@ No alpha blockers: the alpha gate in [`roadmap.md`](roadmap.md#alpha-gate) passe
Preferences ▸ Language, `pref.language`; `en.toml` is complete, and `es.toml`, `fr.toml`,
`de.toml`, `pt-BR.toml`, `ja.toml`, `ko.toml` and `zh-Hans.toml` (2026-10-12) are machine drafts
(all but `zh-Hans.toml` lack the 25 newest strings), 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).
listing only what differs from Spanish (Spanish and Russian are held to completeness by the check; the
others show English for strings added later). Russian adds all 2,165 current entries,
reviewed and approved by VanTan, with one/few/many integer plurals.
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
@@ -394,6 +395,7 @@ No alpha blockers: the alpha gate in [`roadmap.md`](roadmap.md#alpha-gate) passe
| Date | Change | Summary |
|---|---|---|
| 2026-10-14 | minor | Russian catalog (M9), reviewed and approved by VanTan: all 2,165 current entries, Русский in Preferences and regional ru tags. Russian integer plurals use one/few/many; contributor display name VanTan added. Existing estimates unchanged. |
| 2026-10-13 | minor | Gap 1: the experimental `.fig` export writes instance sizes (resized, filling an auto layout, scaled), variables with modes and bindings, shared styles and variable-width strokes, and the import reads them back. Checked by opening written files in other software: resized and scaled instances keep their size and lay their layers out for it, variables with their modes and colour bindings resolve there (a frame set to another mode shows that mode's colours, also inside instances), colour, text, effect and grid styles are listed, and variable-width strokes that are the same on both sides of the path draw with their width; number, string and boolean bindings and bindings on effects, layout guides and gradient stops come across as the values they give but did not show as bound there (their entries now also name the layer field they set, not yet checked there), and strokes wider on one side of the path are written as filled outlines, no longer editable strokes there (the stroke itself is kept as plugin data and our own import puts it back; not yet checked there). Hours and the ranking unchanged |
| 2026-10-12 | minor | Gap 2: your online files landed (the app records the files it put on a server with their owner tokens, Home ▸ Online lists them and opens them again as their owner, with sharing, rename, removal and forget from the list; one request per server tells which are still there; tabs mark files in a live session). What is left is unchanged: accounts, teams and per-person permissions, shared comments and versions. Hours unchanged |
| 2026-10-12 | minor | Gap 2: sharing with a join code (Share ▸ Online or Local network, one Start sharing button, a short code to give others, one join field for a code or a link; the server names links with codes and limits wrong ones) added to the landed list; usability, so the estimate and ranking are unchanged |
+19 -12
View File
@@ -1,10 +1,10 @@
# Localization parity
> **Last reviewed:** 2026-10-10 · **Last updated:** 2026-10-12 · **Change:** minor (draft catalogs catch up: every draft translates every string again; Simplified Chinese draft catalog) · **Target:** Figma Design (desktop app 126.10.7)
> **Last reviewed:** 2026-10-10 · **Last updated:** 2026-10-14 · **Change:** minor (reviewed Russian catalog and integer plurals; draft catalogs catch up: every draft translates every string again; Simplified Chinese draft catalog) · **Target:** Figma Design (desktop app 126.10.7)
FigCraft's interface has a localization layer with an English catalog and Spanish (with a Latin
American overlay), French, German, Brazilian Portuguese, Japanese, Korean and Simplified Chinese
drafts (machine-drafted, waiting for native speakers' review). This page lists the twelve key
drafts (machine-drafted, waiting for native speakers' review), plus Russian reviewed and approved by VanTan. 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).
@@ -17,19 +17,19 @@ 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-12, from source)
## What FigCraft has (measured 2026-10-14, 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
(`tr("Fill")`, `trf` for `{named}` placeholders, `trn` for counts with one/few/many/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,417 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 536 command labels
and 29 menu wordings by command id: 1,982 entries. Command ids, the CLI, MCP and the control
- **Strings:** 1,577 UI keys in `en.toml` (38 with plural forms; ~110 are labels from engine and
document data, such as tool names, blend modes and Dev Mode properties) plus 557 command labels
and 31 menu wordings by command id: 2,165 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, Español
(Latinoamérica), Français, 日本語, 한국어, Português (Brasil), 简体中文), applied at once without a
(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
@@ -42,13 +42,13 @@ supported by both apps (CJK, Arabic and Devanagari shaping, IME); Figma has no v
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, overlay
entries that read as in their base language and, for complete languages (Spanish), anything
entries that read as in their base language and, for complete languages (Spanish and Russian), anything
untranslated. The other drafts are not held to it: a string added later shows English there
until someone translates it. Every draft translates every string as of 2026-10-12 (checked by
adding each to `COMPLETE` locally and running the check).
- **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, Korean and Chinese
such as "inside"), number formatting (decimal points stay `.`), and additional plural rules for languages other than Russian
(Russian has one/few/many integer forms; French and Brazilian Portuguese take the singular for 0 as well; Japanese, Korean and Chinese
have one form; 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
@@ -91,7 +91,13 @@ supported by both apps (CJK, Arabic and Devanagari shaping, IME); Figma has no v
| Korean | ko | 2,076 / 2,076 (100%, draft) | yes | Hangul text and IME in documents; UI text in an installed Korean face (web: the `FIGCRAFT_EMBED_CJK` build) | no | draft | 1–2 (after native review) |
| Vietnamese | vi | 0 (0%) | no | Latin with stacked diacritics | no | none | 3–5 |
Other languages: none shipped (Figma ships none beyond the eight above).
Russian (`ru`): all 2,165 entries translated, including dialogs, tooltips, commands and menu wording;
wording reviewed and approved by VanTan. The picker shows Русский, and `ru-RU`, `ru-BY` and
`ru_RU.UTF-8` use it. Russian uses egui's bundled Cyrillic glyphs; no font asset was added.
The catalog keeps Frame, Dev Mode and Smart animate untranslated. Existing languages keep their
one/other behavior; Russian counts use one/few/many. Other languages beyond these: none shipped.
The navigation rail elides long labels with the full wording in its tooltip; the empty-layer hint
wraps to the panel width. Both were checked in Russian in native macOS screenshots.
The layer is done. Per-language hours above are agent hours for a catalog of ~1,800 entries
(a machine draft, then a pass over the panels for overflow), plus each language's plural rule where
@@ -107,6 +113,7 @@ Vietnamese and Arabic. Traditional Chinese is not planned (no catalog; its users
| Date | Change | Summary |
|---|---|---|
| 2026-10-14 | minor | Russian catalog: 1,577 UI keys (38 with plural forms), 557 command labels and 31 menu wordings (2,165 entries), reviewed by VanTan. Русский in Preferences; ru tags resolve to it. Catalog generation supports optional few/many forms; Russian integer rules distinguish 1/21, 2–4/22–24 and 0/5–20, including 11–14. The catalog supplying a fallback selects its own plural rule. Spanish and Russian are checked for completeness. Estimates unchanged. |
| 2026-10-12 | minor | Drafts catch up again, and the counts with them: French, German, Brazilian Portuguese, Japanese, Korean and Simplified Chinese translate the 71 UI strings and 5 command labels they lacked since sharing with a join code and open hosting (the Share popover, join codes, owner tokens, reports, the .fig export notes), and with Spanish the 26 strings and 3 command labels of your online files (Home ▸ Online, its menu and dialogs, the marks on tabs); machine-drafted in each file's conventions, still drafts. The Latin American overlay adds computadora and agrega where the new Spanish says ordenador and añade (98 entries). 2,076 entries per catalog (1,503 UI strings, 544 command labels, 29 menu wordings) |
| 2026-10-12 | minor | Draft catalogs catch up: French, German, Brazilian Portuguese, Japanese and Korean translate the 66 UI strings and 17 command labels added since they were drafted (cursor chat and spotlight, the plugin directory, Rename layers, presentation and snapping keys, list levels, the rotation origin, placing with Enter…), Simplified Chinese the 46 and 12 it lacked; machine-drafted from the English in each file's conventions, still drafts (not in `COMPLETE`). The Latin American overlay adds Control for a gamepad controller and presiona in the plugin directory hint (90 entries, with Enter for the keycap). String counts re-measured: 1,417 UI keys, 536 command labels and 29 menu wordings (1,982 entries), every draft at 100%. Localization grade unchanged |
| 2026-10-12 | minor | Simplified Chinese draft catalog (gap 5): `zh-Hans.toml` translates every UI string (1,371 keys; plurals give only `other`, and `plural_one` never picks "one"), command label (524) and menu wording (29), machine-drafted in a concise UI style with full-width punctuation and marked as a draft awaiting native review; Preferences ▸ Language lists 简体中文, System maps zh, zh-CN, zh-SG and zh-Hans to it, and Traditional Chinese (zh-Hant, zh-TW, zh-HK, zh-MO) has no catalog and stays English (or the next OS language), never Simplified. A Chinese UI draws Han in a Chinese face first (Microsoft YaHei, PingFang SC, Noto Sans CJK SC), ahead of the craft-fonts build's Japanese BIZ UDPGothic, and changing the UI language lays the CJK faces out again (switching from Japanese to Chinese no longer keeps the Japanese face in front). String counts re-measured: 1,371 UI keys, 524 command labels and 29 menu wordings (1,924 entries); the French, German, Brazilian Portuguese, Japanese and Korean drafts have 1,899 of them (the 25 newer strings read English). Localization 36% → 39%, 31–54 h → 29–51 h |
+1 -1
View File
@@ -349,7 +349,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|es-419|fr|de|pt-BR|ja|ko|zh-Hans|…}`: the app's interface language, applied live ("system":
- `pref.language {language: system|en|ru|es|es-419|fr|de|pt-BR|ja|ko|zh-Hans|…}`: 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.
+3 -2
View File
@@ -1,6 +1,6 @@
# FigCraft roadmap: milestones and what's next
> **Last reviewed:** 2026-10-10 · **Last updated:** 2026-10-14 · **Change:** minor (recent files as cards with thumbnails on the home screen; community fixes: lost images reported on loading, SVG import budgets, Leptos code, full screen, server snapshots, owner-only preferences, ruzstd 0.9.1, layer cycle repair, batch rollback, Direct3D 12 on Windows, plugin metering; vector edit mode regions: hover hatch, selection and drag; repeat command recursion fix; the Layout panel's constrain-proportions link scales the other side, #220; Simplified Chinese draft catalog; 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)
> **Last reviewed:** 2026-10-10 · **Last updated:** 2026-10-14 · **Change:** minor (reviewed Russian catalog and integer plurals; recent files as cards with thumbnails on the home screen; community fixes: lost images reported on loading, SVG import budgets, Leptos code, full screen, server snapshots, owner-only preferences, ruzstd 0.9.1, layer cycle repair, batch rollback, Direct3D 12 on Windows, plugin metering; vector edit mode regions: hover hatch, selection and drag; repeat command recursion fix; the Layout panel's constrain-proportions link scales the other side, #220; Simplified Chinese draft catalog; 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
@@ -17,7 +17,7 @@ are one Opus 5.5 agent; most milestones split across 4–6 agents on disjoint cr
40–60 h, parallel by tool.
4. **Languages** on the landed localization layer: native reviews of the es, fr, de, pt-BR, ja,
ko and zh-Hans drafts and the es-419 overlay (people), then the other key languages: 3–7 h per language
plus native review.
plus native review. Russian's 2,165-entry catalog was reviewed and approved by VanTan.
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
@@ -73,6 +73,7 @@ blockers.
| Date | Change | Summary |
|---|---|---|
| 2026-10-14 | minor | Russian catalog (M9), reviewed and approved by VanTan: all 2,165 current entries, Русский in Preferences and regional ru tags. Russian integer plurals use one/few/many; contributor display name VanTan added. Existing estimates unchanged. |
| 2026-10-14 | minor | M7: Home screen recent files as cards: each card shows the picture its file carries on its first page's colour, the file's icon, name and "Edited ... ago"; sort by name, creation or last change either way; a table view; a click selects, a double-click or Enter opens, arrow keys move; Open, Show in folder and Remove from recents on right-click. Numbers unchanged. |
| 2026-10-14 | minor | Lost images reported on loading (#207): a `.figcraft` file whose image member is damaged, too large or missing no longer loads in silence; each lost image (up to 20, then a count) is listed in `repairs` on `file.open` with its hash and the reason, and the app shows it as a toast. Saving still writes the file without the lost images. Numbers unchanged |
| 2026-10-14 | minor | SVG import budgets (#190): gzipped SVG is unpacked by FigCraft within 64 MiB before parsing (it was unpacked without a limit), so the nesting check sees the real document; SVG documents embedded in `<image>` data URIs share one 32 MiB budget and at most 4 levels, and one past a budget is left out. Linked files stay unread. Numbers unchanged |
+3 -2
View File
@@ -1,6 +1,6 @@
# FigCraft parity with Figma
> **Last reviewed:** 2026-10-10 · **Last updated:** 2026-10-13 · **Change:** minor (vector edit mode regions: hover hatch, selection and drag; plugin log byte/count limits (#191); core editor leftovers: component playground, auto gap handles, variables inside styles, inner shadows and background blur on groups, command-made text readable on dark pages, a one-step frame/group convert, delays on mouse triggers and manual overlay offsets, wrapped-line alignment; starter templates, a Templates tab and Getting started tips on the home screen: essentials ~57% → ~59%; everyday editing partials: number fields, outline options, Hide when publishing, cursors, panel widths, dark pages, numbers unchanged but menu breadth 211/211; text box sizes measured against documents re-saved by other software: whole-pixel line heights, advances on a 1024-per-em grid, letter spacing between glyphs only, numbers unchanged; .fig export checked by opening written files in other software, numbers unchanged; a public figcraft-server: open hosting, owner tokens, per-address limits, idle expiry, moderation, install.sh; experimental .fig export; keyboard shortcut parity suite and bindings; Simplified Chinese draft catalog; 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)
> **Last reviewed:** 2026-10-10 · **Last updated:** 2026-10-14 · **Change:** minor (reviewed Russian catalog and integer plurals; vector edit mode regions: hover hatch, selection and drag; plugin log byte/count limits (#191); core editor leftovers: component playground, auto gap handles, variables inside styles, inner shadows and background blur on groups, command-made text readable on dark pages, a one-step frame/group convert, delays on mouse triggers and manual overlay offsets, wrapped-line alignment; starter templates, a Templates tab and Getting started tips on the home screen: essentials ~57% → ~59%; everyday editing partials: number fields, outline options, Hide when publishing, cursors, panel widths, dark pages, numbers unchanged but menu breadth 211/211; text box sizes measured against documents re-saved by other software: whole-pixel line heights, advances on a 1024-per-em grid, letter spacing between glyphs only, numbers unchanged; .fig export checked by opening written files in other software, numbers unchanged; a public figcraft-server: open hosting, owner tokens, per-address limits, idle expiry, moderation, install.sh; experimental .fig export; keyboard shortcut parity suite and bindings; Simplified Chinese draft catalog; 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
@@ -129,7 +129,7 @@ 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 | 39% | 29–51 | The layer, an English catalog and machine drafts of Spanish (with a Latin American overlay), French, German, Brazilian Portuguese, Japanese, Korean and Simplified Chinese (none reviewed by a native speaker; every draft covers all current strings), 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) |
| Localization | 3 | 39% | 29–51 | The layer, English, Russian reviewed by VanTan, and machine drafts of Spanish (with a Latin American overlay), French, German, Brazilian Portuguese, Japanese, Korean and Simplified Chinese (none reviewed by a native speaker; every draft covers all current strings), 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) |
@@ -628,6 +628,7 @@ Carried over from the ROADMAP's "Shipped so far" (2026-10-10). The test count th
| Date | Change | Summary |
|---|---|---|
| 2026-10-14 | minor | Russian catalog (M9), reviewed and approved by VanTan: all 2,165 current entries, Русский in Preferences and regional ru tags. Russian integer plurals use one/few/many; contributor display name VanTan added. Existing estimates unchanged. |
| 2026-10-13 | minor | Vector edit mode regions: the filled area under the pointer is hatched (clear of points and segments, holes left out, spacing by octave folding fitted to measurements at several zoom levels, fixed to the shape); click, Shift-click and drag select and move areas with their points (drawn unselected); a click on an empty spot clears them and stays in edit mode (see [ui-parity](ui-parity.md)) |
| 2026-10-13 | minor | Plugin log limits (#191): cap each UTF-8 line at 8 KiB, total log bytes at 1 MiB and lines at 1,000 per run/callback before copying guest memory or forwarding to the host. Over-budget runs fail and roll back document edits; callbacks get fresh budgets. Added sandbox boundary/forwarding tests and an engine rollback regression. Percentages and hours unchanged. |
| 2026-10-13 | minor | Component playground (M3): with an instance, a component or a component set selected, the Inspect tab shows a Playground: a preview of the component and a control for each variant, boolean and text property. What is tried there changes the preview and the code in the Code section, never the file (no undo step, the canvas instance stays as it is); Reset goes back to the component's or the instance's own values. `component.playground {id?, properties?, language?, path?}` gives the same to scripts: the properties with their values, the code, and a PNG on request (`playground_tests`). Left: instance-swap and slot properties in the playground (the command takes an instance swap; the panel has no control for it), a code connect template's snippet with the tried values. Numbers unchanged. |
+10 -5
View File
@@ -15,6 +15,8 @@ Simplified.
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`), Korean (`ko`)
and Simplified Chinese (`zh-Hans`), machine-drafted and waiting for native speakers' review.
Russian (`ru`, Русский) was reviewed and approved by VanTan; its catalog keeps Frame, Dev Mode
and Smart animate as familiar tool names and uses « » quotes.
## The catalogs
@@ -59,12 +61,15 @@ language also needs its script in `cjk::language`.
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`, and `plural_one` returns false for them). `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).
only `other`). `other` is required; `one`, `few` and `many` are optional. `trn` picks `one` for
1 (also 0 in French and Brazilian Portuguese). Russian uses `one` for 1, 21, 31… except 11;
`few` for 2–4, 22–24… except 12–14; and `many` for the other integers, including 0. Keep `{n}`
in all Russian forms. Add new rules to `plural` in
[`crates/ui-egui/src/i18n.rs`](../crates/ui-egui/src/i18n.rs). A missing form reads `other`;
a missing key uses the fallback catalog's own plural rule.
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). Every later PR that
adds a string then has to translate it into each complete language, so only Spanish is there:
adds a string then has to translate it into each complete language: Spanish and Russian are there.
French, German, Brazilian Portuguese, Japanese, Korean and Simplified Chinese are 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.
@@ -86,7 +91,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 (Spanish); the drafts may follow later.
add the key to `en.toml` and to each complete language (Spanish and Russian); 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`.