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 <noreply@openai.com>
This commit is contained in:
Dmytro Matviichuk 2026-10-09 20:54:14 +02:00
parent 30aaddf7c2
commit 4823c08d9c
No known key found for this signature in database
6 changed files with 491 additions and 6 deletions

View File

@ -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.

View File

@ -372,7 +372,7 @@ pub fn parse_spimtx(text: &str) -> Option<FileXform> {
}
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;
}

View File

@ -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());

View File

@ -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<Index> = OnceLock::new();
static HANT: OnceLock<Index> = OnceLock::new();
static UK: OnceLock<Index> = 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() {

File diff suppressed because one or more lines are too long

View File

@ -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