From 4823c08d9c3f6af0fd95f096b7af8c99d6ec00c6 Mon Sep 17 00:00:00 2001 From: Dmytro Matviichuk Date: Fri, 9 Oct 2026 20:54:14 +0200 Subject: [PATCH] G9: expand Ukrainian UI coverage (#333) Translate the 396 current panel catalog rows and route Ukrainian Reveal labels for Finder, Explorer and File Manager. Enforce catalog parity, placeholder preservation and platform label coverage; update language docs. Use a fixed-size OCIO row iterator to satisfy the current Clippy gate. Co-authored-by: Codex --- ROADMAP.md | 1 + crates/effects/src/ocio.rs | 2 +- crates/ui-egui/src/i18n.rs | 36 ++- crates/ui-egui/src/i18n/ui.rs | 454 +++++++++++++++++++++++++++++++++- docs/gaps.md | 2 +- docs/preferences.md | 2 +- 6 files changed, 491 insertions(+), 6 deletions(-) diff --git a/ROADMAP.md b/ROADMAP.md index 70935025..e111d7b3 100644 --- a/ROADMAP.md +++ b/ROADMAP.md @@ -1,5 +1,6 @@ # Roadmap +- 2026-10-09: Ukrainian now covers all 396 current panel-catalog strings, including dialogs, buttons and tooltips, alongside its menu translation. The catalog keeps the same source keys and placeholder counts as the Chinese catalogs; missing or engine-generated text still falls back to English. - 2026-10-09: Settings ▸ General ▸ Language now translates the interface below the menu bar as well: panels, dialogs, buttons and tooltips read their text through `crates/ui-egui/src/i18n/ui.rs`, keyed by the English source string. Simplified and Traditional Chinese are complete (384 strings); Japanese rows can follow in the same file. A test fails when a converted panel gains a hard-coded string, and another keeps the catalog free of unused rows. - 2026-10-08: `effectcraft-cli render --out` resolves a relative path against the working directory, like `--project` (it used the project's folder, which doubled a path that already named it). Render Queue outputs set in a project are unchanged. - 2026-10-08: Long-press a grouped toolbar button to choose its tools, including Horizontal/Vertical Type; releasing the hold keeps the menu open. Existing point/paragraph text creation is unchanged. diff --git a/crates/effects/src/ocio.rs b/crates/effects/src/ocio.rs index d00f6e9e..c8fcf848 100644 --- a/crates/effects/src/ocio.rs +++ b/crates/effects/src/ocio.rs @@ -372,7 +372,7 @@ pub fn parse_spimtx(text: &str) -> Option { } let mut m = [0.0; 16]; let mut offset = [0.0; 4]; - for (r, row) in v.chunks_exact(4).enumerate() { + for (r, row) in v.as_chunks::<4>().0.iter().enumerate() { m[r * 4..r * 4 + 3].copy_from_slice(&row[..3]); offset[r] = row[3] / 65535.0; } diff --git a/crates/ui-egui/src/i18n.rs b/crates/ui-egui/src/i18n.rs index 04f00d30..08a7e4d8 100644 --- a/crates/ui-egui/src/i18n.rs +++ b/crates/ui-egui/src/i18n.rs @@ -80,7 +80,17 @@ fn supported(locale: Option<&str>) -> &'static str { } pub(crate) fn label<'a>(app: &EffectcraftApp, command: &str, source: &'a str) -> &'a str { - match language(app) { + let language = language(app); + // The engine names the platform's file manager before the catalog sees the label. These + // two commands keep their ids, but on Windows/Linux their source is no longer "Finder". + if language == "uk" && matches!(command, "file.revealInFinder" | "layer.revealInFinder") { + match source { + "Reveal in Explorer" => return "Показати у Провіднику", + "Reveal in File Manager" => return "Показати у файловому менеджері", + _ => {} + } + } + match language { "ja" => lookup(JAPANESE, command, source), "zh-hans" => lookup(SIMPLIFIED_CHINESE, command, source), "zh-hant" => lookup(TRADITIONAL_CHINESE, command, source), @@ -2727,6 +2737,30 @@ mod tests { assert_eq!(japanese_keys, ukrainian_keys, "the languages must translate the same entries"); } + /// Exercise each engine spelling on every host: a macOS-only run must still catch a + /// Windows/Linux catalog miss, while filenames and unrelated commands stay untouched. + #[test] + fn ukrainian_reveal_labels_follow_the_platform_file_manager() { + let mut app = EffectcraftApp::new(effectcraft_engine::Session::default()); + app.session.execute("prefs.set", json!({"key": "general.language", "value": "uk"})).unwrap(); + for command in ["file.revealInFinder", "layer.revealInFinder"] { + for (source, translated) in [ + ("Reveal in Finder", "Показати у Finder"), + ("Reveal in Explorer", "Показати у Провіднику"), + ("Reveal in File Manager", "Показати у файловому менеджері"), + ] { + let e = MenuEntry { label: source.into(), command: command.into(), params: json!({"item": "My file.mp4"}), shortcut: None }; + assert_eq!(label(&app, command, source), translated); + assert_eq!(entry(&app, &e, source.into()), translated); + assert_eq!(label(&app, "file.openRecent", source), source); + } + assert_eq!(label(&app, command, "My file.mp4"), "My file.mp4"); + } + app.session.execute("prefs.set", json!({"key": "general.language", "value": "en"})).unwrap(); + assert_eq!(label(&app, "file.revealInFinder", "Reveal in Explorer"), "Reveal in Explorer"); + assert_eq!(label(&app, "layer.revealInFinder", "Reveal in File Manager"), "Reveal in File Manager"); + } + #[test] fn language_changes_native_labels_without_changing_commands_or_parameters() { let mut app = EffectcraftApp::new(effectcraft_engine::Session::default()); diff --git a/crates/ui-egui/src/i18n/ui.rs b/crates/ui-egui/src/i18n/ui.rs index 4afb43b1..f6846d25 100644 --- a/crates/ui-egui/src/i18n/ui.rs +++ b/crates/ui-egui/src/i18n/ui.rs @@ -19,7 +19,7 @@ use std::fmt::Display; use std::sync::OnceLock; thread_local! { - /// The resolved interface language ("en", "ja", "zh-hans", "zh-hant"); "en" until the first + /// The resolved interface language ("en", "ja", "zh-hans", "zh-hant", "uk"); "en" until the first /// frame sets it. static CURRENT: Cell<&'static str> = const { Cell::new("en") }; } @@ -42,9 +42,11 @@ type Index = HashMap<&'static str, &'static str>; fn catalog(language: &str) -> Option<&'static Index> { static HANS: OnceLock = OnceLock::new(); static HANT: OnceLock = OnceLock::new(); + static UK: OnceLock = OnceLock::new(); let (index, rows) = match language { "zh-hans" => (&HANS, SIMPLIFIED_CHINESE), "zh-hant" => (&HANT, TRADITIONAL_CHINESE), + "uk" => (&UK, UKRAINIAN), _ => return None, }; Some(index.get_or_init(|| rows.iter().copied().collect())) @@ -909,6 +911,437 @@ const TRADITIONAL_CHINESE: &[(&str, &str)] = &[ ("⚠ {} is used by:", "⚠ {} 已被以下專案使用:"), ]; +/// Ukrainian (uk): original translations of the same interface rows as the Chinese catalogs. +/// Technical names, key names and substituted project content keep their original spelling. +const UKRAINIAN: &[(&str, &str)] = &[ + (" OK ", " Гаразд "), + (" Delete ", " Видалити "), + (" Open Auto-Save ", " Відкрити автозбереження "), + ("(missing)", "(відсутній)"), + ("360 View Orientation", "Орієнтація огляду 360°"), + ("3:2 Pulldown: {}", "Перетворення кадрів 3:2: {}"), + ( + "A clean-room, open-source compositor for motion graphics and visual effects: native on macOS, Windows and Linux, and in the browser. Part of the ArtCraft family of creative apps.", + "Незалежно розроблена програма з відкритим кодом для компонування анімованої графіки та візуальних ефектів: працює на macOS, Windows і Linux, а також у браузері. Належить до родини творчих програм ArtCraft.", + ), + ("About EffectCraft", "Про EffectCraft"), + ("About...", "Про програму..."), + ("Active Camera", "Активна камера"), + ("Adapt Feature On Every Frame", "Адаптувати ознаку в кожному кадрі"), + ("Adaptive Sample Limit:", "Межа адаптивних вибірок:"), + ("Add Animator", "Додати аніматор"), + ("Add Comment", "Додати коментар"), + ("Add Formatting", "Додати форматування"), + ("Add Group", "Додати групу"), + ("Add Mirror", "Додати дзеркало"), + ("Add Selected As", "Додати виділене як"), + ("Add a comment", "Додати коментар"), + ("Add a shape, fill, stroke or path operation", "Додати фігуру, заливку, обведення або операцію з контуром"), + ("Add to Composition", "Додати до композиції"), + ("Add to Favorites", "Додати до обраного"), + ("Add:", "Додати:"), + ("Adjust Exposure (stops)", "Налаштувати експозицію (ступені)"), + ("Adjust composition duration to the time span of the selected layers", "Припасувати тривалість композиції до часового діапазону виділених шарів"), + ("Advanced 3D", "Розширений 3D"), + ("Align Layers to:", "Вирівняти шари відносно:"), + ("All six face cameras turn together; the scene is not changed.", "Усі шість камер граней повертаються разом; сцена залишається без змін."), + ("Anchor:", "Опорна точка:"), + ("Aperture", "Діафрагма"), + ("Apply Dimensions:", "Застосувати виміри:"), + ("Apply Motion To:", "Застосувати рух до:"), + ("Arrange", "Упорядкувати"), + ("Assign", "Призначити"), + ("Auto", "Автоматично"), + ("Auto-saves", "Автозбереження"), + ("Back", "Ззаду"), + ("Background Color:", "Колір тла:"), + ("Blend Mode:", "Режим змішування:"), + ("Blending Mode", "Режим змішування"), + ("Blur", "Розмиття"), + ("Blur Level", "Рівень розмиття"), + ("Browser Storage", "Сховище браузера"), + ("Browse…", "Огляд…"), + ("Brush Dynamics", "Динаміка пензля"), + ("Caching frames…", "Кешування кадрів…"), + ("Cancel", "Скасувати"), + ("Casts Shadows", "Відкидає тіні"), + ("Change Renderer…", "Змінити рушій рендерингу…"), + ( + "Changing a shortcut creates a custom preset; the default stays as it is.", + "Зміна клавіатурного скорочення створює власний набір; стандартний залишається без змін.", + ), + ("Channel", "Канал"), + ("Channel:", "Канал:"), + ("Chapter:", "Розділ:"), + ("Choose...", "Вибрати..."), + ("Classic 3D", "Класичний 3D"), + ("Clear", "Очистити"), + ("Clear list", "Очистити список"), + ("Click a key to see its commands.", "Клацніть клавішу, щоб переглянути її команди."), + ("Click adds a point (½ height = no change)", "Клацніть, щоб додати точку (½ висоти = без змін)"), + ("Click adds a point; drag one off to remove", "Клацніть, щоб додати точку; перетягніть її за межі, щоб вилучити"), + ("Click to make this viewer active", "Клацніть, щоб зробити цю область перегляду активною"), + ("Click to reveal the expression in the timeline", "Клацніть, щоб показати вираз на шкалі часу"), + ("Click to show the active composition here", "Клацніть, щоб показати тут активну композицію"), + ("Click, then click in the Composition panel", "Клацніть тут, а потім на панелі «Композиція»"), + ("Clone Options", "Параметри клонування"), + ("Clone Source Overlay:", "Накладання джерела клонування:"), + ("Close", "Закрити"), + ("Close Panel", "Закрити панель"), + ("Close tutorial", "Закрити урок"), + ("Color", "Колір"), + ("Color Depth: {}", "Глибина кольору: {}"), + ("Color:", "Колір:"), + ("Colour map", "Карта кольорів"), + ("Columns", "Стовпці"), + ("Comment:", "Коментар:"), + ("Community", "Спільнота"), + ("Composition", "Композиція"), + ("Composition Mini-Flowchart", "Мініблок-схема композиції"), + ("Composition Name:", "Назва композиції:"), + ("Composition Settings ▸ 3D Renderer", "Налаштування композиції ▸ Рушій 3D-рендерингу"), + ("Composition Settings…", "Налаштування композиції…"), + ("Composition marker bin: drag a marker to the time ruler", "Список маркерів композиції: перетягніть маркер на часову лінійку"), + ("Cone Angle", "Кут конуса"), + ("Cone Feather", "Розтушовування конуса"), + ("Constrain Proportions", "Зберігати пропорції"), + ("Continuous (Lock Outgoing to Incoming)", "Безперервна (прирівняти вихідну швидкість до вхідної)"), + ("Cue Point", "Сигнальна точка"), + ("Current Render", "Поточний рендеринг"), + ("Darker", "Темніше"), + ("Default Output:", "Стандартний вихід:"), + ("Default: {}", "Стандартне: {}"), + ("Defaults", "Стандартні значення"), + ("Delete", "Видалити"), + ("Delete Selected Points", "Видалити виділені точки"), + ("Delete selected project items", "Видалити виділені елементи проєкту"), + ("Delete this template", "Видалити цей шаблон"), + ("Depth of Field", "Глибина різкості"), + ("Discord", "Discord"), + ("Distribute Layers:", "Розподілити шари:"), + ("Don't Recover", "Не відновлювати"), + ("Don't Save", "Не зберігати"), + ("Double-click a layer to open it in the Layer panel", "Двічі клацніть шар, щоб відкрити його на панелі «Шар»"), + ("Double-click footage in the Project panel to open it here", "Двічі клацніть матеріал на панелі «Проєкт», щоб відкрити його тут"), + ("Double-click to edit the comment", "Двічі клацніть, щоб змінити коментар"), + ("Download", "Завантажити"), + ( + "Drag properties here from the timeline,\nor use Animation > Add Property to Essential Graphics.", + "Перетягніть сюди властивості зі шкали часу,\nабо скористайтеся командою «Анімація > Додати властивість до основної графіки».", + ), + ("Drag to change", "Перетягніть, щоб змінити"), + ("Drag to create a guide", "Перетягніть, щоб створити напрямну"), + ("Drag to slip the source", "Перетягніть, щоб зсунути матеріал у межах шару"), + ("Drop Frame", "Пропуск номерів"), + ("Duplicate", "Дублювати"), + ("Duration:", "Тривалість:"), + ("Edit Gradient…", "Редагувати градієнт…"), + ("Edit Target: {}", "Ціль редагування: {}"), + ("Edit in the Timeline", "Редагувати на шкалі часу"), + ("EffectCraft didn't quit normally last time.", "Минулого разу EffectCraft завершив роботу некоректно."), + ("Effects & Presets options", "Параметри ефектів і наборів"), + ("Effects: {}", "Ефекти: {}"), + ("Empty folder", "Порожня тека"), + ("Enable Depth of Field", "Увімкнути глибину різкості"), + ("Enable Expression", "Увімкнути вираз"), + ("Enhance", "Покращити"), + ("Essential Properties — {}", "Основні властивості — {}"), + ("Event", "Подія"), + ("Export Template…", "Експортувати шаблон…"), + ("Export...", "Експорт..."), + ("Expression Language Menu", "Меню мови виразів"), + ("Expression pick whip: drag onto a property", "Зв’язування виразу: перетягніть на властивість"), + ("Expression:", "Вираз:"), + ("Eyedropper: click, then click in the Composition panel", "Піпетка: клацніть тут, а потім на панелі «Композиція»"), + ("Falloff", "Згасання"), + ("Falloff Distance", "Відстань згасання"), + ("Favorites", "Обране"), + ("Field Render: {}", "Рендеринг полів: {}"), + ("Figures", "Цифри"), + ("Fill Target", "Ціль заливки"), + ("Finished", "Завершено"), + ("Flowchart: {}", "Блок-схема: {}"), + ("Focus Distance", "Відстань фокусування"), + ("Follow Feature Rotation and Scale", "Враховувати обертання та масштаб ознаки"), + ("Footage: {}", "Матеріал: {}"), + ("Format: {}", "Формат: {}"), + ("Frame Aspect Ratio: {}", "Пропорції кадру: {}"), + ("Frame Blending: {}", "Змішування кадрів: {}"), + ("Frame Rate:", "Частота кадрів:"), + ("Frame Render Time {}ms", "Час рендерингу кадру {} мс"), + ("Frame Target:", "Цільовий фрейм:"), + ("Grab bag", "Список"), + ("Grid", "Сітка"), + ("Guide Layers: {}", "Допоміжні шари: {}"), + ("Guides", "Напрямні"), + ("Height:", "Висота:"), + ("Hide This", "Приховати це"), + ("Histogram", "Гістограма"), + ("Hold In Place", "Утримувати на місці"), + ("Home", "Головна"), + ("If Confidence is Below", "Якщо достовірність нижча за"), + ("Import", "Імпортувати"), + ("Import and Add to Composition", "Імпортувати та додати до композиції"), + ("Import...", "Імпорт..."), + ("Imported media", "Імпортовані матеріали"), + ("Influence:", "Вплив:"), + ("Install from File…", "Встановити з файлу…"), + ("Intensity", "Інтенсивність"), + ("Inverted", "Інвертовано"), + ("Join the ArtCraft community on Discord", "Приєднайтеся до спільноти ArtCraft у Discord"), + ("Key: {}", "Клавіша: {}"), + ("LEARN", "НАВЧАННЯ"), + ("Label", "Мітка"), + ("Label Colors and Names:", "Кольори та назви міток:"), + ("Label:", "Мітка:"), + ("Latest auto-save: {}", "Останнє автозбереження: {}"), + ("Layer", "Шар"), + ("Layer controls", "Керування шарами"), + ("Layer {}", "Шар {}"), + ("Layer:", "Шар:"), + ("Layer: {}", "Шар: {}"), + ("Learn", "Навчання"), + ("Leave all attributes in \"{}\"", "Залишити всі атрибути в «{}»"), + ("Licence: {}", "Ліцензія: {}"), + ("Ligatures", "Лігатури"), + ("Light Type", "Тип світла"), + ("Lighter", "Світліше"), + ("Link Selected Property", "Зв’язати виділену властивість"), + ("Lock Aspect Ratio to {}", "Зафіксувати співвідношення сторін на {}"), + ("Lock to Zoom", "Прив’язати до масштабу"), + ("Locked: {} — {}", "Заблоковано: {} — {}"), + ("Log:", "Журнал:"), + ("Loop (Trace Path)", "Цикл (уздовж контуру)"), + ("MIT OR Apache-2.0 • Fonts: Inter, JetBrains Mono (OFL)", "MIT OR Apache-2.0 • Шрифти: Inter, JetBrains Mono (OFL)"), + ("Magnification ratio popup", "Меню коефіцієнта збільшення"), + ("Make Comp Size", "За розміром композиції"), + ("Masks", "Маски"), + ("Maximize Panel", "Розгорнути панель"), + ("Media Replacement", "Заміна матеріалу"), + ("Mesh:", "Сітка:"), + ("Method Used: {} Average Error: {} pixels", "Використаний метод: {} Середня похибка: {} пікселів"), + ("Models folder: {}", "Тека моделей: {}"), + ("Modifiers:", "Модифікатори:"), + ("More", "Більше"), + ("More ArtCraft apps", "Інші програми ArtCraft"), + ("More tutorials", "Інші уроки"), + ("Motion Blur", "Розмиття руху"), + ("Motion Blur: {}", "Розмиття руху: {}"), + ("Motion Sketch: drag to record", "Ескіз руху: перетягніть, щоб записати"), + ("Move Down", "Перемістити вниз"), + ("Move Up", "Перемістити вгору"), + ("Move to Project Root", "Перемістити до кореня проєкту"), + ("Name", "Назва"), + ("Name:", "Назва:"), + ("Navigation", "Навігація"), + ("New", "Створити"), + ("New Comp from Selection", "Нова композиція з виділеного"), + ("New Duration:", "Нова тривалість:"), + ("New composition name:", "Назва нової композиції:"), + ("New from Render Queue item:", "Створити з елемента черги рендерингу:"), + ("New from Template", "Створити із шаблону"), + ("Next", "Далі"), + ("Nice work. Pick another tutorial on the Home screen's Learn tab.", "Гарна робота. Виберіть інший урок на вкладці «Навчання» головного екрана."), + ( + "No VR environments yet. Create one with Composition ▸ VR ▸ Create VR Environment…", + "Середовищ VR ще немає. Створіть одне через «Композиція ▸ VR ▸ Створити середовище VR…».", + ), + ("No audio layer selected", "Аудіошар не виділено"), + ("No background jobs running", "Немає активних фонових завдань"), + ("No composition markers", "Немає маркерів композиції"), + ("No contributor data was built into this copy.", "Ця копія не містить даних про учасників."), + ( + "No device list from this host; the system default output is used.", + "Цей пристрій не надає списку пристроїв; використовується стандартний вихід системи.", + ), + ("No layer selected", "Шар не виділено"), + ("No matching effects or presets", "Не знайдено відповідних ефектів або наборів"), + ("No model credits were built into this copy.", "Ця копія не містить відомостей про моделі."), + ("No nested compositions", "Немає вкладених композицій"), + ("No shortcut", "Немає клавіатурного скорочення"), + ("No unlocked layer selected", "Не виділено жодного розблокованого шару"), + ("No video", "Немає відео"), + ("None", "Немає"), + ("Not applied: tick it in the list to add it.", "Не застосовано: позначте в списку, щоб додати."), + ("Not assigned", "Не призначено"), + ("Note: Shadows are only cast from layers with Cast Shadows enabled.", "Примітка: тіні відкидають лише шари з увімкненим параметром «Відкидати тіні»."), + ("Notify", "Сповістити"), + ("OK", "Гаразд"), + ("Offset:", "Зсув:"), + ("On ({}) stop:", "Після зупинки ({}):"), + ("One-Node Camera", "Одновузлова камера"), + ("Opacity:", "Непрозорість:"), + ("Open Composition", "Відкрити композицію"), + ("Open Last Saved Project", "Відкрити останній збережений проєкт"), + ("Open New Composition", "Відкрити нову композицію"), + ("Open Output", "Відкрити результат"), + ("Open a composition to build Essential Graphics", "Відкрийте композицію, щоб створити основну графіку"), + ("Open a composition to see its scopes", "Відкрийте композицію, щоб переглянути колірні діаграми"), + ("Open a composition to track motion.", "Відкрийте композицію, щоб відстежувати рух."), + ("Opus Tuning: {}", "Налаштування Opus: {}"), + ("Original Duration:", "Початкова тривалість:"), + ("Output Cycle: {}", "Цикл виводу: {}"), + ("Output To:", "Вивести до:"), + ("Parameter Name:", "Назва параметра:"), + ("Parent pick whip: drag onto a layer", "Зв’язування батьківського шару: перетягніть на шар"), + ("Pixel Aspect Ratio:", "Пропорції пікселя:"), + ("Playing on Video Preview", "Відтворення на відеоперегляді"), + ("Post-Render Action: {}", "Дія після рендерингу: {}"), + ("Preserve frame rate when nested or in render queue", "Зберігати частоту кадрів у вкладених композиціях або черзі рендерингу"), + ("Preserve resolution when nested", "Зберігати роздільність у вкладених композиціях"), + ("Preset", "Набір"), + ("Preset:", "Набір:"), + ("Presets", "Набори"), + ("Previous", "Назад"), + ("Primary:", "Основний:"), + ("Process Before Match", "Обробити перед зіставленням"), + ("Profile: {}", "Профіль: {}"), + ( + "Project color depth: click to cycle 8/16/32 bpc, Alt-click for Project Settings", + "Глибина кольору проєкту: клацніть, щоб перемикати 8/16/32 bpc, або клацніть з Alt, щоб відкрити налаштування проєкту", + ), + ("Project: {}", "Проєкт: {}"), + ("Projects", "Проєкти"), + ("Property pick whip: drag onto a property", "Зв’язування властивості: перетягніть на властивість"), + ("Protected Region", "Захищена область"), + ("Proxy Use: {}", "Використання проксі: {}"), + ("Push All to Comp", "Застосувати все до композиції"), + ("Radius", "Радіус"), + ("Rate Control: {}", "Керування бітовою швидкістю: {}"), + ("Recent", "Нещодавні"), + ("Recording", "Запис"), + ("Remove", "Вилучити"), + ("Rename", "Перейменувати"), + ("Render: {} ms • UI {} fps{}", "Рендеринг: {} мс • Інтерфейс {} кадр/с{}"), + ("Renderer:", "Рушій рендерингу:"), + ("Request Persistent Storage", "Запросити постійне сховище"), + ("Reset", "Скинути"), + ("Reset Labels", "Скинути мітки"), + ("Reset Preset", "Скинути набір"), + ("Reset to Default", "Відновити стандартні значення"), + ("Reset to Saved Layout", "Відновити збережене компонування"), + ("Resolution/Down Sample Factor Popup", "Меню роздільності та коефіцієнта зменшення"), + ("Restore Panel Size", "Відновити розмір панелі"), + ("Reverse the order", "Обернути порядок"), + ("Revert All", "Відновити все"), + ("Roman Hanging Punctuation", "Висячі латинські розділові знаки"), + ("Roving:", "Автоматичний час:"), + ("Ruler zero point (drag to move, double-click to reset)", "Початок лінійки (перетягніть, щоб перемістити; двічі клацніть, щоб скинути)"), + ("Rulers", "Лінійки"), + ("Run", "Виконати"), + ("Sample Format: {}", "Формат вибірок: {}"), + ("Samples Per Frame:", "Вибірок на кадр:"), + ("Save from #{}", "Зберегти з #{}"), + ("Saved {}", "Збережено {}"), + ("Script… (⌘/Ctrl+Enter runs)", "Скрипт… (⌘/Ctrl+Enter запускає)"), + ("Search commands or shortcuts", "Пошук команд або клавіатурних скорочень"), + ("Select", "Виділити"), + ("Select a command to change its shortcut.", "Виберіть команду, щоб змінити її клавіатурне скорочення."), + ("Select a layer to see its effects", "Виділіть шар, щоб переглянути його ефекти"), + ("Select a layer to see its properties", "Виділіть шар, щоб переглянути його властивості"), + ( + "Select a layer with a mask or shape path (or the path property), or Puppet pins.", + "Виділіть шар із маскою або контуром фігури (або властивість контуру) чи опорні точки деформації.", + ), + ("Select a text layer", "Виділіть текстовий шар"), + ("Select an item in the Project panel", "Виділіть елемент на панелі «Проєкт»"), + ("Select animated properties to show them in the Graph Editor", "Виділіть анімовані властивості, щоб показати їх у редакторі графіків"), + ( + "Select the leader pin, then Shift-click the pins that should trail it (hair, cloth, tails)", + "Виділіть провідну опорну точку, а потім клацніть із Shift точки, які мають рухатися за нею (волосся, тканина, хвости)", + ), + ("Set Ground Plane and Origin", "Задати площину землі та початок координат"), + ("Set Paint Colors to Black and White (D)", "Задати чорний і білий кольори малювання (D)"), + ("Settings", "Налаштування"), + ("Settings Name:", "Назва налаштувань:"), + ("Shadow Darkness", "Темнота тіні"), + ("Shadow Diffusion", "Розсіювання тіні"), + ("Shadows", "Тіні"), + ("Shape…", "Фігура…"), + ("Show", "Показати"), + ("Show Layer Name", "Показати назву шару"), + ("Show Source Name", "Показати назву матеріалу"), + ("Show me", "Показати мені"), + ("Show:", "Показати:"), + ("Shutter Angle:", "Кут затвора:"), + ("Shutter Phase:", "Фаза затвора:"), + ("Size", "Розмір"), + ("Skip", "Пропустити"), + ("Snapping", "Прив’язування"), + ("Snapping options", "Параметри прив’язування"), + ("Snapshot", "Знімок"), + ("Solo Supported Properties", "Лише підтримувані властивості"), + ("Solo Switches: {}", "Перемикачі соло: {}"), + ("Sort", "Сортувати"), + ("Spacing:", "Інтервал:"), + ("Spatial Interpolation:", "Просторова інтерполяція:"), + ("Speed:", "Швидкість:"), + ("Start Timecode:", "Початковий часовий код:"), + ("Start: {} Duration: {}", "Початок: {} Тривалість: {}"), + ("Step {} of {}: {}", "Крок {} з {}: {}"), + ("Stop {} at {}°", "Колірна точка {} на {}°"), + ("Stored in: {}", "Збережено в: {}"), + ("Stretch", "Розтягнення"), + ("Stretch Factor:", "Коефіцієнт розтягнення:"), + ("Stylistic Sets", "Стилістичні набори"), + ("Subpixel Positioning", "Субпіксельне позиціювання"), + ("Swap Paint Colors (X)", "Поміняти місцями кольори малювання (X)"), + ("System Default", "Стандартне системне"), + ("Table", "Таблиця"), + ("Template name", "Назва шаблону"), + ("Template: {}\n{}", "Шаблон: {}\n{}"), + ("Temporal Interpolation:", "Часова інтерполяція:"), + ("The Media Browser is not available here (use File ▸ Import)", "Браузер матеріалів тут недоступний (скористайтеся меню «Файл ▸ Імпорт»)"), + ("The OpenEXR layer shown in red, green, blue and alpha", "Шар OpenEXR, показаний у червоному, зеленому, синьому та альфа-каналі"), + ("The render queue is empty.", "Черга рендерингу порожня."), + ("This ScriptUI panel has closed", "Цю панель ScriptUI закрито"), + ("This change will create a new solid for this layer.", "Ця зміна створить новий матеріал суцільного кольору для цього шару."), + ("Time", "Час"), + ("Time Stretch…", "Розтягнення часу…"), + ("Time:", "Час:"), + ("Time: {}", "Час: {}"), + ("Title/action safe", "Безпечні області титрів і дії"), + ("Toggle Switches / Modes", "Перемкнути перемикачі / режими"), + ("Track Name:", "Назва відстеження:"), + ( + "Trained models are available in the desktop app; the browser uses the classic engine.", + "Навчені моделі доступні в настільній програмі; браузер використовує класичний рушій.", + ), + ("Transform", "Перетворення"), + ("Transparency grid", "Сітка прозорості"), + ("Tutorial complete", "Урок завершено"), + ("Two-Node Camera", "Двовузлова камера"), + ("Type", "Тип"), + ("Type a command or effect…", "Введіть команду або ефект…"), + ("URL:", "URL:"), + ("Undock Panel", "Від’єднати панель"), + ("Unlink {}", "Від’єднати {}"), + ( + "Use this option to place the currently selected layers together into a new intermediate composition.", + "Скористайтеся цим параметром, щоб розмістити виділені шари разом у новій проміжній композиції.", + ), + ("VR Environments", "Середовища VR"), + ("Value:", "Значення:"), + ("Variable Font Axes", "Осі змінного шрифту"), + ("Video Codec: {}", "Відеокодек: {}"), + ("View:", "Вигляд:"), + ("Welcome to EffectCraft", "Ласкаво просимо до EffectCraft"), + ("Width:", "Ширина:"), + ("You can undo this.", "Це можна скасувати."), + ("Your changes will be lost if you don't save them.", "Якщо не зберегти зміни, їх буде втрачено."), + ("Zoom", "Масштаб"), + ("built-in", "вбудований"), + ("frames per second", "кадрів за секунду"), + ("template name", "назва шаблону"), + ("{} contributors · {} commits", "Учасники: {} · Фіксації змін: {}"), + ("{} x {}, {}MB per 8bpc frame", "{} x {}, {} МБ на кадр 8bpc"), + ("{} — coming soon", "{} — незабаром"), + ("{} — double-click to open, {}-click to reveal", "{} — двічі клацніть, щоб відкрити; клацніть із {}, щоб показати розташування"), + ("⌘/Ctrl-drag a pin to record its motion", "Перетягніть опорну точку з ⌘/Ctrl, щоб записати її рух"), + ("▣ {}", "▣ {}"), + ("⚠ {} is used by:", "⚠ {} використовується в:"), +]; + #[cfg(test)] mod tests { use super::*; @@ -991,7 +1424,7 @@ mod tests { const UNITS: &[&str] = &[" px", "%", "·", "s", "ms", "°", "fps", " bpc", " mm", " s", " %", " pixels"]; fn catalogs() -> Vec<(&'static str, &'static str)> { - SIMPLIFIED_CHINESE.iter().copied().chain(TRADITIONAL_CHINESE.iter().copied()).collect() + SIMPLIFIED_CHINESE.iter().copied().chain(TRADITIONAL_CHINESE.iter().copied()).chain(UKRAINIAN.iter().copied()).collect() } /// A row that translates nothing, repeats a source string, or changes the number of values the @@ -1001,6 +1434,8 @@ mod tests { let hans: Vec<&str> = SIMPLIFIED_CHINESE.iter().map(|(en, _)| *en).collect(); let hant: Vec<&str> = TRADITIONAL_CHINESE.iter().map(|(en, _)| *en).collect(); assert_eq!(hans, hant, "the two Chinese catalogs list different source strings"); + let uk: Vec<&str> = UKRAINIAN.iter().map(|(en, _)| *en).collect(); + assert_eq!(hans, uk, "the Ukrainian catalog lists different source strings"); let mut sorted = hans.clone(); sorted.sort_unstable(); let listed = sorted.len(); @@ -1027,6 +1462,21 @@ mod tests { set_current("en"); } + /// The same language preference used for the menus reaches panels immediately, and formatted + /// text keeps the caller's values intact even when they contain non-Ukrainian words. + #[test] + fn ukrainian_panels_translate_and_keep_project_content() { + set_current("uk"); + assert_eq!(tr("Composition Name:"), "Назва композиції:"); + assert_eq!(tr("Cancel"), "Скасувати"); + assert_eq!(tr("my layer.mp4"), "my layer.mp4"); + assert_eq!(tr_args("Proxy Use: {}", &[&"Best"]), "Використання проксі: Best"); + assert_eq!(tr_args("Step {} of {}: {}", &[&1, &3, &"Colour"]), "Крок 1 з 3: Colour"); + assert_eq!(tr_args("Template: {}\n{}", &[&"Title", &"My description"]), "Шаблон: Title\nMy description"); + set_current("en"); + assert_eq!(tr("Composition Name:"), "Composition Name:"); + } + /// Values fill the placeholders in order, and a missing value is dropped instead of panicking. #[test] fn tr_args_fills_values_in_order() { diff --git a/docs/gaps.md b/docs/gaps.md index 18870661..7f9105a9 100644 --- a/docs/gaps.md +++ b/docs/gaps.md @@ -34,7 +34,7 @@ of them have been reconciled. | Behaves like After Effects | largely unmeasured, ≈ 60–80% | One outside contributor found four bugs in features marked done within a day (PRs #6–#10): Hold keyframes eased the motion into them, `keyInSpatialTangent` / `keyOutSpatialTangent` had the wrong names, a zero frame rate crashed, Find and Enter Full Screen shared Ctrl+F off macOS. There are about 10 After Effects reference captures in total. The first live G1 comparison now checks eight scalar Rotation/ease cases (168 samples) against AE 26.3x87 via AEsync 2.0.4: all pass after fixing overlapping temporal influences, with maximum error below 3e-11 degrees ([scores and reproduction](fidelity/README.md)). Other animation behavior and rendered frames remain unmeasured. User reports of 6 October found five more in features marked done (the Render Queue's template menu, angle revolutions, switches wiping the RAM preview, audio playback skipping frames, preview crashing when video memory ran out; all fixed 7 October) | | Opening existing After Effects work | ≈ 0% | `.aep` / `.aepx` projects cannot be opened. Third-party After Effects plug-ins cannot run. Expressions and the scripting object model are strong, so scripts and expressions carry over | | Stability | improving | 23 never-crash PRs landed on 4 October; community PRs on 7 October fixed a hang on projects whose parent chains loop (#142), a panic on non-ASCII label colours (#135), and contained GPU initialization and device failures (#139); [AGENTS.md](../AGENTS.md) "Never crash" now binds every crate. On 5 October `cargo xtask ci` failed on main under Rust 1.99's clippy (a fix is in progress) | -| Real-user experience | ≈ 70%, uneven by platform | Issues #41–#47 (all from the Linux AppImage 0.1.1, 5 October): panning the viewer snaps back, panels can't be resized or rearranged, drag-and-drop and double-click import don't work, layer rename gets stuck, the Layer Settings arrow does nothing, the Project panel clips, the Wayland window icon is generic. Issues #63–#68 (macOS, 5 October): scaling a layer by its handles goes wrong and can stick at 0, two 3D compasses, the viewer lags while a layer is dragged (a frame renders in 20 ms but showed after ≈ 100 ms, behind RAM preview prefetch), the Discord and other links do nothing, Delete doesn't delete in the Project panel, hidden layers can be selected in the viewer. All but the last two were fixed or confirmed fixed with a test on 5–6 October (those two have community PRs); file drops still can't work on Wayland (winit has no support). The panning, rename and arrow bugs had been fixed in v0.2.0 already; nobody had told the reporter. A Windows user (6 October, on Discord) found the font menus listed only the three bundled fonts: installed fonts were read only when a project asked for one, on every platform. Fixed with a regression test on 6 October; the menus now list every installed family and its own styles, and agents get `text.fonts` / `list_fonts`. Seven reports of 6 October (Linux .deb and Windows): Project items and files couldn't be dropped on the viewer (#85), nor effects on a layer there (#88); drops in the Timeline ignored where they landed (#89); an angle's revolutions couldn't be edited (#93); toggling Audio, Lock or Shy threw away the RAM preview and playback with audio skipped frames (#103); running out of video memory panicked every frame thread and left frames stuck (#106); choosing a Render Settings template in the Render Queue did nothing (#117: any popup menu moved up to fit the window closed on the press). All seven were fixed with regression tests on 7 October. Reports of 7 October (Linux, macOS, Windows): the app opened the demo project on every launch, so a new comp landed in it (#204); a comp wider than the GPU's texture limit (11000 × 2200) at Full resolution stopped the window drawing (#201); an Intel HD Graphics 5500 couldn't start the app (#198: most likely the window's device asked for limits that GPU doesn't have, and the failure was silent); Audio Spectrum / Waveform stood still in the viewer (#209) and preview sound waited for the whole work area to cache (#208); a layer couldn't be deselected from the time graph, a shape layer's Fill and Stroke showed only with a shape tool and a drawn shape had no stroke to change, Alt+Shift+P didn't reveal Position (#205); a mask selected in the Timeline wasn't selected in the viewer, Mask Feather had no link and took negative values, the Project panel had no selection box (#203); shape layers had no Add menu for Trim Paths and the other operations, and Classic 3D comps hid Geometry Options (#206); dragging Puppet pins re-rendered the layer under the Puppet at every move (#212). All were fixed with regression tests the same day (#219–#222); the #198 fix couldn't be checked on that GPU. Reports of 8 October (browser, Windows, macOS): an empty project at 1280 × 800 with device pixel ratio 2 crashed on its first frame, because the Project panel's scroll bar clamped its thumb to a track shorter than the thumb (#231, any short panel on every platform); Media Browser ▸ Add Files… dropped a file it couldn't read without a word (#226); after the page's WebGPU device was lost, the browser editor froze on its last frame while edits went on (#225); a press in the viewer always took the topmost layer, so a selected layer behind others couldn't be dragged (#230); the language didn't follow the system's (#229); and a list of missing After Effects behaviour (#227): drawing into, copying, pasting and duplicating shape contents, Tool Creates Shape / Mask and Fill / Stroke Options, Turbulent Displace, Wave Warp and Bulge pinning all but a shape layer's bottom-right quadrant, a Spacebar hold that previewed instead of being the Hand tool, the Effect menu in Effect Controls, dropping items on New Composition and dragging over layer switches. All were fixed with regression tests the same day (#235, #237–#241); multi-monitor support and the Project panel's empty-area menu (community PR #210) remain from #227, and effect points on shape and text layers still sit half a comp off. A list of tweaks from an Intel iMac (#290): the layer bar didn't show where extended footage runs past its source, and Enable Time Remapping then ended at the extended out point; footage items had no Interpret Footage in their menu and the work area bar no menu; Label menus named the labels without their colours; the arrow keys didn't nudge layers; a straight motion path had no Bezier handles; Delete on Time Remap deleted the layer; a mask being drawn with the Pen blacked out its layer until it was closed; a double-click on a mask point didn't select the whole mask. All were fixed with regression tests the same day; the rest of #290 had been fixed already (#221, #238, #255) or came with community PRs #210 (the Timeline's empty-area menu) and #213 (tool flyouts on a long press). A report of 9 October (#397): CC Particle World ignored the comp camera and looked flat. It now sees its particles through an active camera layer, as After Effects does, and through its own Extras ▸ Effect Camera (Distance, Rotation X / Y / Z, FOV, added) otherwise; the layer cache also missed camera and light moves for the effects that read them (Card Dance, Shatter, Card Wipe, Caustics). Fixed with regression tests the same day. Most checking happens on macOS. Localisation covers the menus in Japanese, Simplified and Traditional Chinese, and since 9 October the panels, dialogs and tooltips too (Simplified and Traditional Chinese; the Japanese rows of the panel catalog are still open); no accessibility work | +| Real-user experience | ≈ 70%, uneven by platform | Issues #41–#47 (all from the Linux AppImage 0.1.1, 5 October): panning the viewer snaps back, panels can't be resized or rearranged, drag-and-drop and double-click import don't work, layer rename gets stuck, the Layer Settings arrow does nothing, the Project panel clips, the Wayland window icon is generic. Issues #63–#68 (macOS, 5 October): scaling a layer by its handles goes wrong and can stick at 0, two 3D compasses, the viewer lags while a layer is dragged (a frame renders in 20 ms but showed after ≈ 100 ms, behind RAM preview prefetch), the Discord and other links do nothing, Delete doesn't delete in the Project panel, hidden layers can be selected in the viewer. All but the last two were fixed or confirmed fixed with a test on 5–6 October (those two have community PRs); file drops still can't work on Wayland (winit has no support). The panning, rename and arrow bugs had been fixed in v0.2.0 already; nobody had told the reporter. A Windows user (6 October, on Discord) found the font menus listed only the three bundled fonts: installed fonts were read only when a project asked for one, on every platform. Fixed with a regression test on 6 October; the menus now list every installed family and its own styles, and agents get `text.fonts` / `list_fonts`. Seven reports of 6 October (Linux .deb and Windows): Project items and files couldn't be dropped on the viewer (#85), nor effects on a layer there (#88); drops in the Timeline ignored where they landed (#89); an angle's revolutions couldn't be edited (#93); toggling Audio, Lock or Shy threw away the RAM preview and playback with audio skipped frames (#103); running out of video memory panicked every frame thread and left frames stuck (#106); choosing a Render Settings template in the Render Queue did nothing (#117: any popup menu moved up to fit the window closed on the press). All seven were fixed with regression tests on 7 October. Reports of 7 October (Linux, macOS, Windows): the app opened the demo project on every launch, so a new comp landed in it (#204); a comp wider than the GPU's texture limit (11000 × 2200) at Full resolution stopped the window drawing (#201); an Intel HD Graphics 5500 couldn't start the app (#198: most likely the window's device asked for limits that GPU doesn't have, and the failure was silent); Audio Spectrum / Waveform stood still in the viewer (#209) and preview sound waited for the whole work area to cache (#208); a layer couldn't be deselected from the time graph, a shape layer's Fill and Stroke showed only with a shape tool and a drawn shape had no stroke to change, Alt+Shift+P didn't reveal Position (#205); a mask selected in the Timeline wasn't selected in the viewer, Mask Feather had no link and took negative values, the Project panel had no selection box (#203); shape layers had no Add menu for Trim Paths and the other operations, and Classic 3D comps hid Geometry Options (#206); dragging Puppet pins re-rendered the layer under the Puppet at every move (#212). All were fixed with regression tests the same day (#219–#222); the #198 fix couldn't be checked on that GPU. Reports of 8 October (browser, Windows, macOS): an empty project at 1280 × 800 with device pixel ratio 2 crashed on its first frame, because the Project panel's scroll bar clamped its thumb to a track shorter than the thumb (#231, any short panel on every platform); Media Browser ▸ Add Files… dropped a file it couldn't read without a word (#226); after the page's WebGPU device was lost, the browser editor froze on its last frame while edits went on (#225); a press in the viewer always took the topmost layer, so a selected layer behind others couldn't be dragged (#230); the language didn't follow the system's (#229); and a list of missing After Effects behaviour (#227): drawing into, copying, pasting and duplicating shape contents, Tool Creates Shape / Mask and Fill / Stroke Options, Turbulent Displace, Wave Warp and Bulge pinning all but a shape layer's bottom-right quadrant, a Spacebar hold that previewed instead of being the Hand tool, the Effect menu in Effect Controls, dropping items on New Composition and dragging over layer switches. All were fixed with regression tests the same day (#235, #237–#241); multi-monitor support and the Project panel's empty-area menu (community PR #210) remain from #227, and effect points on shape and text layers still sit half a comp off. A list of tweaks from an Intel iMac (#290): the layer bar didn't show where extended footage runs past its source, and Enable Time Remapping then ended at the extended out point; footage items had no Interpret Footage in their menu and the work area bar no menu; Label menus named the labels without their colours; the arrow keys didn't nudge layers; a straight motion path had no Bezier handles; Delete on Time Remap deleted the layer; a mask being drawn with the Pen blacked out its layer until it was closed; a double-click on a mask point didn't select the whole mask. All were fixed with regression tests the same day; the rest of #290 had been fixed already (#221, #238, #255) or came with community PRs #210 (the Timeline's empty-area menu) and #213 (tool flyouts on a long press). A report of 9 October (#397): CC Particle World ignored the comp camera and looked flat. It now sees its particles through an active camera layer, as After Effects does, and through its own Extras ▸ Effect Camera (Distance, Rotation X / Y / Z, FOV, added) otherwise; the layer cache also missed camera and light moves for the effects that read them (Card Dance, Shatter, Card Wipe, Caustics). Fixed with regression tests the same day. Most checking happens on macOS. Localisation covers the menus in Japanese, Simplified and Traditional Chinese, and Ukrainian, and since 9 October the cataloged panels, dialogs and tooltips too (Simplified and Traditional Chinese, and Ukrainian; the Japanese rows of the panel catalog are still open); no accessibility work | | Performance | unknown against After Effects | Internal numbers only (e.g. Advanced 3D 290 ms/frame at 1080p on the GPU, an M4 Pro under load). Nothing benchmarked against After Effects; no large real projects (4K footage, hundreds of layers) tested | | Media formats | ≈ 80% | H.264, ProRes, HEVC, AV1, image sequences and audio exist. The new HEVC / AV1 encoders have no B-frames, multi-reference or SAO / CDEF, so files are larger than from mature encoders. Camera formats (BRAW, R3D, ProRes RAW, variable-frame-rate phone video) are unverified | | AI-assisted tools | ≈ 50% | Both tools can use trained models (pure-Rust inference, optional downloads), but nothing compares them with After Effects yet. Roto Brush 2.0 / 3.0 with MobileSAM (M13.35) scores IoU 0.989 on the base frame of our synthetic moving-disc test and ≥ 0.980 over 20 propagated frames (classic: 0.973). Face tracking with MediaPipe Face Landmarker (M13.36) matches Google's own pipeline to 0.85 px on average on a test portrait; on our synthetic clip the eyes and chin stay within 4% of the face height | diff --git a/docs/preferences.md b/docs/preferences.md index 9e3d4147..43330cd4 100644 --- a/docs/preferences.md +++ b/docs/preferences.md @@ -29,7 +29,7 @@ Commands (CLI, MCP, control channel): ### Settings that change behaviour -- `general.language`: interface language (`system` / `en` / `ja` / `zh-hans` / `zh-hant` / `uk`), Settings ▸ General ▸ Language; menu labels change immediately. `system` (Match System, the default) follows the operating system's interface language where EffectCraft has a translation and is English otherwise: a Simplified Chinese locale (`zh`, `zh-CN`, `zh_CN.UTF-8`, `zh-Hans-CN`, `zh-SG`) selects `zh-hans`, a Traditional one (`zh-TW`, `zh-HK`, `zh-Hant`, `zh_MO`) selects `zh-hant`, a Ukrainian one (`uk`, `uk-UA`, `uk_UA.UTF-8`) selects `uk`; the browser build stays in English with it. Choose **Українська** for Ukrainian menus; Ukrainian dialog and panel contents stay in English for now, and Ukrainian uses the bundled UI fonts. Native Japanese, Simplified and Traditional Chinese UI uses installed system fonts; the web host must supply a Japanese font. The same setting picks the CJK fallback for the interface *and* the Han preference of the text engine, so the composition's own text is drawn with the same language's glyph forms (the text engine's preference is process-global); swapping the language reinstalls the fonts, so it applies without a restart. Two catalogs back the text: the menus are keyed by `(command, English label)` (`crates/ui-egui/src/i18n.rs`), and the rest of the interface - panels, dialogs, buttons, tooltips - is keyed by its English source string (`crates/ui-egui/src/i18n/ui.rs`), where a panel writes `tr("Rename")`, or `tr_args("Proxy Use: {}", &[&quality])` when the row carries a value. A row that is missing falls back to English, so a new string shows up untranslated rather than empty; the tests in that module fail on an unused row and on a hard-coded string in a converted panel. +- `general.language`: interface language (`system` / `en` / `ja` / `zh-hans` / `zh-hant` / `uk`), Settings ▸ General ▸ Language; menu labels change immediately. `system` (Match System, the default) follows the operating system's interface language where EffectCraft has a translation and is English otherwise: a Simplified Chinese locale (`zh`, `zh-CN`, `zh_CN.UTF-8`, `zh-Hans-CN`, `zh-SG`) selects `zh-hans`, a Traditional one (`zh-TW`, `zh-HK`, `zh-Hant`, `zh_MO`) selects `zh-hant`, a Ukrainian one (`uk`, `uk-UA`, `uk_UA.UTF-8`) selects `uk`; the browser build stays in English with it. Choose **Українська** for Ukrainian menus and cataloged panel, dialog, button and tooltip text; Ukrainian uses the bundled UI fonts. Native Japanese, Simplified and Traditional Chinese UI uses installed system fonts; the web host must supply a Japanese font. The same setting picks the CJK fallback for the interface *and* the Han preference of the text engine, so the composition's own text is drawn with the same language's glyph forms (the text engine's preference is process-global); swapping the language reinstalls the fonts, so it applies without a restart. Two catalogs back the text: the menus are keyed by `(command, English label)` (`crates/ui-egui/src/i18n.rs`), and the rest of the interface - panels, dialogs, buttons, tooltips - is keyed by its English source string (`crates/ui-egui/src/i18n/ui.rs`), where a panel writes `tr("Rename")`, or `tr_args("Proxy Use: {}", &[&quality])` when the row carries a value. A row that is missing falls back to English, so a new string shows up untranslated rather than empty; the tests in that module fail on an unused row and on a hard-coded string in a converted panel. - `general.undoLevels`: Levels of Undo - `general.pathPointSize`: Path Point and Handle Size - `general.recentItems`: Recent Projects Shown