diff --git a/ATTRIBUTION.md b/ATTRIBUTION.md index 3e94fa45..ce7bd609 100644 --- a/ATTRIBUTION.md +++ b/ATTRIBUTION.md @@ -9,6 +9,7 @@ iconography, images or other Adobe assets; open licences only; every asset attri | Asset | Author | Source | Licence | |---|---|---|---| +| `crates/ui-egui/src/i18n/ru.tsv` | FilmCraft contributors | Original work: clean-room Russian interface translations | MIT OR Apache-2.0 | | `crates/ui-egui/src/i18n/uk.tsv` | FilmCraft contributors | Original work: clean-room Ukrainian interface translations | MIT OR Apache-2.0 | | `assets/fonts/Inter-Regular.ttf` | The Inter Project Authors | https://github.com/rsms/inter | OFL-1.1 (`assets/fonts/OFL-Inter.txt`) | | `assets/fonts/Inter-Medium.ttf` | The Inter Project Authors | https://github.com/rsms/inter | OFL-1.1 (`assets/fonts/OFL-Inter.txt`) | diff --git a/ROADMAP.md b/ROADMAP.md index 7a32f549..e8e5f928 100644 --- a/ROADMAP.md +++ b/ROADMAP.md @@ -211,4 +211,4 @@ Status: ✅ done · 🟡 in progress · ⬜ not started. Estimates are remaining ### Interface localization -Edit > Language switches English/Japanese/Spanish/Portuguese (Brazilian)/Ukrainian and persists across restarts through `general.interfaceLanguage` (also available in Settings > General). Until a language is chosen, System Language (the default) follows the operating system's or browser's preferred languages and falls back to English. Commands `app.language.english`, `app.language.japanese`, `app.language.spanish`, `app.language.portuguese` and `app.language.ukrainian` are reachable through the control channel. Translations live in one catalog per language (`crates/ui-egui/src/i18n/.tsv`); UI code wraps its strings in `tl!("…")` / `tlf!("…{name}", name)`, and names that come from registries (effects and their parameters, settings, commands, panels, workspaces, presets) are translated where they are drawn. **Spanish and Japanese cover the whole interface**: menus, panels, dialogs, the Settings pages, the Effects/Effect Controls registry, the Keyboard Shortcuts dialog and status messages; tests fail when a `tl!` literal, a menu label or a registry name has no Spanish entry (`ja.tsv` has the same entries, but the tests do not require them yet). Searches match the displayed labels as well as English sources. Brazilian Portuguese and Ukrainian cover the core menus; untranslated labels use English. Ukrainian uses the bundled Cyrillic interface fonts. Engine error messages, the control channel, the CLI and MCP stay in English. Japanese text uses the Japanese fonts from [craft-fonts](https://github.com/storytold/craft-fonts) when FilmCraft is built with them (`CRAFT_FONTS_DIR`; all official releases, including the web build), otherwise a Japanese font already installed on the system; with neither, switching to Japanese is refused with a message. Existing vertical text support is preserved. +Edit > Language switches English/Japanese/Spanish/Portuguese (Brazilian)/Ukrainian/Russian and persists across restarts through `general.interfaceLanguage` (also available in Settings > General). Until a language is chosen, System Language (the default) follows the operating system's or browser's preferred languages and falls back to English. Commands `app.language.english`, `app.language.japanese`, `app.language.spanish`, `app.language.portuguese`, `app.language.ukrainian` and `app.language.russian` are reachable through the control channel. Translations live in one catalog per language (`crates/ui-egui/src/i18n/.tsv`); UI code wraps its strings in `tl!("…")` / `tlf!("…{name}", name)`, and names that come from registries (effects and their parameters, settings, commands, panels, workspaces, presets) are translated where they are drawn. **Spanish and Japanese cover the whole interface**: menus, panels, dialogs, the Settings pages, the Effects/Effect Controls registry, the Keyboard Shortcuts dialog and status messages; tests fail when a `tl!` literal, a menu label or a registry name has no Spanish entry (`ja.tsv` has the same entries, but the tests do not require them yet). Searches match the displayed labels as well as English sources. Brazilian Portuguese, Ukrainian and Russian cover the core menus; untranslated labels use English. Ukrainian and Russian use the bundled Cyrillic interface fonts. Engine error messages, the control channel, the CLI and MCP stay in English. Japanese text uses the Japanese fonts from [craft-fonts](https://github.com/storytold/craft-fonts) when FilmCraft is built with them (`CRAFT_FONTS_DIR`; all official releases, including the web build), otherwise a Japanese font already installed on the system; with neither, switching to Japanese is refused with a message. Existing vertical text support is preserved. diff --git a/apps/filmcraft/src/native_menu.rs b/apps/filmcraft/src/native_menu.rs index c63f8295..65aa26b5 100644 --- a/apps/filmcraft/src/native_menu.rs +++ b/apps/filmcraft/src/native_menu.rs @@ -101,6 +101,8 @@ pub fn install(app: &FilmcraftApp, ctx: egui::Context) -> (Receiver, Sho filmcraft_ui_egui::i18n::Language::PtBr } else if checked("app.language.ukrainian") { filmcraft_ui_egui::i18n::Language::Uk + } else if checked("app.language.russian") { + filmcraft_ui_egui::i18n::Language::Ru } else { filmcraft_ui_egui::i18n::Language::En }; diff --git a/crates/engine/src/settings.rs b/crates/engine/src/settings.rs index 9243483f..e2deb5b8 100644 --- a/crates/engine/src/settings.rs +++ b/crates/engine/src/settings.rs @@ -687,6 +687,7 @@ static CATEGORIES: &[Category] = &[ ("es", "Español"), ("pt-br", "Português (Brasil)"), ("uk", "Українська"), + ("ru", "Русский"), ]), true, ), diff --git a/crates/ui-egui/src/i18n/mod.rs b/crates/ui-egui/src/i18n/mod.rs index 3f4b845e..71c58151 100644 --- a/crates/ui-egui/src/i18n/mod.rs +++ b/crates/ui-egui/src/i18n/mod.rs @@ -35,15 +35,17 @@ pub enum Language { #[serde(rename = "pt-br")] PtBr, Uk, + Ru, } static JAPANESE: OnceLock = OnceLock::new(); static SPANISH: OnceLock = OnceLock::new(); static PORTUGUESE: OnceLock = OnceLock::new(); static UKRAINIAN: OnceLock = OnceLock::new(); +static RUSSIAN: OnceLock = OnceLock::new(); impl Language { - pub const ALL: [Self; 5] = [Self::En, Self::Ja, Self::Es, Self::PtBr, Self::Uk]; + pub const ALL: [Self; 6] = [Self::En, Self::Ja, Self::Es, Self::PtBr, Self::Uk, Self::Ru]; pub fn name(self) -> &'static str { match self { @@ -52,6 +54,7 @@ impl Language { Self::Es => "Español", Self::PtBr => "Português (Brasil)", Self::Uk => "Українська", + Self::Ru => "Русский", } } @@ -62,6 +65,7 @@ impl Language { "es" => Some(Self::Es), "pt-br" => Some(Self::PtBr), "uk" => Some(Self::Uk), + "ru" => Some(Self::Ru), _ => None, } } @@ -70,7 +74,7 @@ impl Language { /// (BCP 47 or POSIX locale tags such as `es-419`, `pt_BR.UTF-8`, most preferred first) that the /// interface has, else English. Any Portuguese gets the Brazilian catalog, the only one there is. pub fn from_locales(tags: &[String]) -> Self { - const PRIMARY: [(&str, Language); 5] = [("en", Language::En), ("ja", Language::Ja), ("es", Language::Es), ("pt", Language::PtBr), ("uk", Language::Uk)]; + const PRIMARY: [(&str, Language); 6] = [("en", Language::En), ("ja", Language::Ja), ("es", Language::Es), ("pt", Language::PtBr), ("uk", Language::Uk), ("ru", Language::Ru)]; tags.iter() .find_map(|tag| { let primary = tag.split(['-', '_', '.', '@']).next().unwrap_or_default(); @@ -87,6 +91,7 @@ impl Language { Self::Es => "es", Self::PtBr => "pt-br", Self::Uk => "uk", + Self::Ru => "ru", } } @@ -98,6 +103,7 @@ impl Language { Self::Es => Some(SPANISH.get_or_init(|| Catalog::parse(include_str!("es.tsv")))), Self::PtBr => Some(PORTUGUESE.get_or_init(|| Catalog::parse(include_str!("pt-br.tsv")))), Self::Uk => Some(UKRAINIAN.get_or_init(|| Catalog::parse(include_str!("uk.tsv")))), + Self::Ru => Some(RUSSIAN.get_or_init(|| Catalog::parse(include_str!("ru.tsv")))), } } @@ -253,7 +259,7 @@ mod tests { #[test] fn catalogs_are_well_formed() { for (code, text) in - [("es", include_str!("es.tsv")), ("ja", include_str!("ja.tsv")), ("pt-br", include_str!("pt-br.tsv")), ("uk", include_str!("uk.tsv"))] + [("es", include_str!("es.tsv")), ("ja", include_str!("ja.tsv")), ("pt-br", include_str!("pt-br.tsv")), ("uk", include_str!("uk.tsv")), ("ru", include_str!("ru.tsv"))] { let (entries, errors) = catalog::parse_entries(text); assert!(errors.is_empty(), "{code}: {errors:?}"); @@ -280,6 +286,10 @@ mod tests { assert_eq!(Language::Uk.tr("File"), "Файл"); assert_eq!(Language::Uk.tr("мій кліп.mp4"), "мій кліп.mp4"); assert_eq!(Language::Uk.tr("An untranslated label"), "An untranslated label"); + assert_eq!(Language::Ru.name(), "Русский"); + assert_eq!(Language::Ru.tr("File"), "Файл"); + assert_eq!(Language::Ru.tr("мой клип.mp4"), "мой клип.mp4"); + assert_eq!(Language::Ru.tr("An untranslated label"), "An untranslated label"); for l in Language::ALL { assert_eq!(Language::parse(l.code()), Some(l)); let json = serde_json::to_string(&l).unwrap(); @@ -732,6 +742,24 @@ mod tests { } } + #[test] + fn russian_entries_cover_the_original_menu_catalog() { + let app = crate::FilmcraftApp::new(filmcraft_engine::Session::default()); + let items = crate::menus::menu_items(&app); + let known = |text: &str| crate::menus::MENUS.contains(&text) || items.iter().any(|it| it.label == text || it.path.iter().any(|p| p == text)); + let (entries, _) = catalog::parse_entries(include_str!("ru.tsv")); + for (_, en, _) in entries { + assert!(known(&en) || en == "Settings", "not a menu label: {en}"); + } + let (portuguese, _) = catalog::parse_entries(include_str!("pt-br.tsv")); + for (_, en, _) in portuguese { + assert!(Language::Ru.has(&en), "missing Russian menu label: {en}"); + } + for en in crate::menus::MENUS.into_iter().chain(["Audio", "Settings"]) { + assert_ne!(Language::Ru.tr(en), en, "untranslated Russian menu: {en}"); + } + } + #[test] fn ukrainian_renders_with_bundled_fonts() { let ctx = egui::Context::default(); diff --git a/crates/ui-egui/src/i18n/ru.tsv b/crates/ui-egui/src/i18n/ru.tsv new file mode 100644 index 00000000..b8daace5 --- /dev/null +++ b/crates/ui-egui/src/i18n/ru.tsv @@ -0,0 +1,356 @@ +# Russian (ru) catalog. Columns separated by TAB: context source translation +# Empty context is a plain UI string. Escapes: \n, \t, \\; preserve placeholders and trailing … +# Original clean-room translations from the meaning of FilmCraft's English menu labels, +# using Russian video-editing vocabulary (Premiere-style). No proprietary localization resources were used. +# Core menu coverage; other interface strings fall back to English. + + About FilmCraft О FilmCraft + Add Audio Submix Track Добавить дорожку подмикширования звука + Add Caption at Playhead Добавить субтитр в позиции курсора воспроизведения + Add Chapter Marker… Добавить маркер главы… + Add Edit Добавить склейку + Add Edit to All Tracks Добавить склейку на все дорожки + Add Flash Cue Marker… Добавить маркер Flash Cue… + Add Frame Hold Добавить стоп-кадр + Add Guide… Добавить направляющую… + Add Marker Добавить маркер + Add New Caption Track… Добавить новую дорожку субтитров… + Add Range Marker Добавить маркер диапазона + Add Range Marker to In and Out Добавить маркер диапазона между точками входа и выхода + Add Tracks… Добавить дорожки… + Adjustment Layer… Корректирующий слой… + Align to Selection Выровнять по выделению + Align to Video Frame Выровнять по видеокадру + Align to Video Frame as Group Выровнять как группу по видеокадру + All Panels Все панели + Alpha Альфа + Appearance Оформление + Appearance… Оформление… + Apply Audio Transition Применить аудиопереход + Apply Default Transitions to Selection Применить переходы по умолчанию к выделению + Apply Video Transition Применить видеопереход + Arrange Упорядочить + ArtCraft Website Веб-сайт ArtCraft + Assembly Сборка + Attach Proxies… Прикрепить прокси… + Audio Аудио + Audio Channels… Аудиоканалы… + Audio Clip Mixer Микшер аудиоклипов + Audio Gain… Усиление звука… + Audio Hardware… Аудиооборудование… + Audio In Вход аудио + Audio Meters Индикаторы звука + Audio Options Параметры аудио + Audio Out Выход аудио + Audio Track Mixer Микшер аудиодорожек + Audio Waveform Форма звуковой волны + Auto Save… Автосохранение… + Automate to Sequence… Автоматизировать в эпизод… + Bars and Tone… Цветные полосы и тон… + Bin Подборка + Bin From Selection Подборка из выделения + Black Video… Черное видео… + Blue Синий + Bottom По нижнему краю + Breakout to Mono Разбить на моно + Bring Forward Переместить вперед + Bring to Front Переместить на передний план + Brown Коричневый + Captions Субтитры + Captions and Graphics Субтитры и графика + Captions… Субтитры… + Caribbean Карибский + Center Horizontally По центру по горизонтали + Center Vertically По центру по вертикали + Cerulean Лазурный + Clear Очистить + Clear Guides Очистить направляющие + Clear In Снять точку входа + Clear In and Out Снять точки входа и выхода + Clear Markers Удалить маркеры + Clear Out Снять точку выхода + Clear Selected Marker Удалить выбранный маркер + Clip Клип + Close Закрыть + Close All Other Projects Закрыть все остальные проекты + Close All Projects Закрыть все проекты + Close Gap Удалить пробел + Close Project Закрыть проект + Color Management… Управление цветом… + Color Matte… Цветная подложка… + Comparison View Режим сравнения + Composite Video Составное видео + Consolidate Duplicates Объединить дубликаты + Copy Копировать + Copy Paste Includes Sequence Markers Копирование и вставка включают маркеры эпизода + Create Captions from Transcript… Создать субтитры из расшифровки… + Create Multi-Camera Source Sequence… Создать многокамерный исходный эпизод… + Create Proxies… Создать прокси… + Cut Вырезать + Dark Темный + Darkest Самый темный + Delete Render Files Удалить файлы рендеринга + Delete Render Files In to Out Удалить файлы рендеринга между точками входа и выхода + Delete Tracks… Удалить дорожки… + Delete Transcript Удалить расшифровку + Demo Footage Демонстрационные материалы + Demo Project Демо-проект + Deselect All Отменить выделение + Detach Proxies Отсоединить прокси + Display Mode Режим отображения + Distribute Распределить + Distribute Horizontally Распределить по горизонтали + Distribute Space Horizontally Распределить интервалы по горизонтали + Distribute Space Vertically Распределить интервалы по вертикали + Distribute Vertically Распределить по вертикали + Duplicate Дублировать + Dynamic Audio Waveforms Динамические формы звуковой волны + Edit Правка + Edit Offline… Редактировать автономный клип… + Edit Original Редактировать оригинал + Edit Subclip… Редактировать субклип… + Editing Монтаж + Effect Controls Элементы управления эффектами + Effects Эффекты + Ellipse Эллипс + Enable Включить + Enable Remix Включить ремикс + Essential Graphics Основные графические элементы + Essential Sound Основной звук + Events События + Export Экспорт + Export As Motion Graphics Template… Экспортировать как шаблон анимированной графики… + Extract Извлечь + Extract Audio Извлечь аудио + Field Options… Параметры полей… + File Файл + File… Файл… + Fill frame Заполнить кадр + FilmCraft Help… Справка FilmCraft… + FilmCraft on GitHub FilmCraft на GitHub + FilmCraft on getartcraft.com FilmCraft на getartcraft.com + Find Next Найти далее + Find… Найти… + Fit По размеру + Fit to frame Вписать в кадр + Flatten Свести + Forest Лесной + Frame Blending Смешение кадров + Frame Hold Options… Параметры стоп-кадра… + Frame Sampling Выборка кадров + From Bin Из подборки + From Source Monitor Из монитора источника + From Source Monitor, Match Frame Из монитора источника, сопоставление кадров + From file… Из файла… + Full Полное + Generate Audio Waveform Сгенерировать форму звуковой волны + Get Media File Properties for Свойства медиафайла для + Go to Gap Перейти к промежутку + Go to In Перейти к точке входа + Go to Next Caption Segment Перейти к следующему сегменту субтитров + Go to Next Marker Перейти к следующему маркеру + Go to Out Перейти к точке выхода + Go to Previous Caption Segment Перейти к предыдущему сегменту субтитров + Go to Previous Marker Перейти к предыдущему маркеру + Go to Split Перейти к раздельной точке + Graphics and Titles Графика и титры + Graphics… Графика… + Green Зеленый + Group Сгруппировать + Guide Templates Шаблоны направляющих + Help Справка + Hide All Caption Tracks Скрыть все дорожки субтитров + High Quality Playback Высокое качество воспроизведения + History История + Import From Импортировать из + Import Image Sequence… Импортировать последовательность изображений… + Import from Media Browser Импортировать из медиабраузера + Import… Импорт… + Info Инфо + Ingest Settings… Параметры инжеста… + Insert Вставить + Insert Frame Hold Segment Вставить сегмент стоп-кадра + Install Motion Graphics Template… Установить шаблон анимированной графики… + Interpret Footage… Интерпретировать материал… + Join the ArtCraft Discord… Присоединиться к ArtCraft Discord… + Keyboard Shortcuts… Сочетания клавиш… + Label Метка + Labels… Метки… + Language Язык + Lavender Лавандовый + Learning Обучение + Left По левому краю + Libraries Библиотеки + Lift Поднять + Light Светлый + Link Связать + Link Media… Привязать медиаданные… + Linked Selection Связанное выделение + Lock Guides Закрепить направляющие + Lumetri Color Цвет Lumetri + Lumetri Scopes Видеографика Lumetri + Magnification Масштаб + Make Offline… Сделать автономным… + Make Subclip… Создать субклип… + Make Subsequence Создать вложенный эпизод + Manage Guides… Управление направляющими… + Mark Clip Пометить клип + Mark In Отметить точку входа + Mark Out Отметить точку выхода + Mark Selection Пометить выделение + Mark Split Отметить раздельную точку + Markers Маркеры + Match Frame Сопоставить кадр + Media Analysis & Transcription… Анализ медиа и расшифровка… + Media Browser Медиабраузер + Media Cache… Кэш медиаданных… + Media… Медиа… + Memory… Память… + Merge Clips… Объединить клипы… + Metadata Метаданные + Modify Изменить + Motion Graphics Template… Шаблон анимированной графики… + Multi-Camera Мультикамера + Multi-Camera View Мультикамерный режим + Nest… Вложить… + New Создать + New Layer Новый слой + New Project Panel Новая панель проекта + Next in Sequence Следующий в эпизоде + Next in Track Следующий на дорожке + Normalize Mix Track… Нормализовать мастер-дорожку… + Offline File… Автономный файл… + Open Project… Открыть проект… + Optical Flow Оптический поток + Overwrite Перезаписать + Paste Вставить + Paste Attributes… Вставить атрибуты… + Paste Insert Вставить со сдвигом + Paused Resolution Разрешение на паузе + Playback Resolution Разрешение воспроизведения + Playback… Воспроизведение… + Plugins… Плагины… + Polygon Многоугольник + Preferences Настройки + Previous in Sequence Предыдущий в эпизоде + Previous in Track Предыдущий на дорожке + Program Программа + Progress Прогресс + Project Проект + Project Manager… Диспетчер проектов… + Project Settings Настройки проекта + Project… Проект… + Properties Свойства + Purple Пурпурный + Reconnect Full Resolution Media… Переподключить медиаданные полного разрешения… + Recover Unsaved Changes… Восстановить несохраненные изменения… + Rectangle Прямоугольник + Red Красный + Redo Повторить + Reference Monitor Контрольный монитор + Remix Ремикс + Remix Properties… Свойства ремикса… + Remove Attributes… Удалить атрибуты… + Remove Filler Words Удалить слова-паразиты + Remove Pauses Удалить паузы + Remove Unused Удалить неиспользуемые + Rename… Переименовать… + Render Audio Рендеринг аудио + Render Effects In to Out Рендеринг эффектов от точки входа до точки выхода + Render In to Out Рендеринг от точки входа до точки выхода + Render Selection Рендеринг выделения + Replace Fonts in Projects… Заменить шрифты в проектах… + Replace With Clip Заменить клипом + Report an Issue… Сообщить о проблеме… + Reset All Parameters Сбросить все параметры + Reset Duration Сбросить длительность + Reset to Saved Layout Сбросить к сохраненному макету + Restore Captions from Source Clip Восстановить субтитры из исходного клипа + Reveal Log Files… Показать файлы журнала… + Reverse Match Frame Обратное сопоставление кадра + Revert Вернуть к сохраненному + Revert Remix Отменить ремикс + Review Рецензирование + Right По правому краю + Ripple Delete Удаление со сдвигом + Ripple Sequence Markers Сдвигать маркеры эпизода + Rose Розовый + Safe Margins Безопасные поля + Save Сохранить + Save All Сохранить все + Save As… Сохранить как… + Save Guides as Template… Сохранить направляющие как шаблон… + Save a Copy… Сохранить копию… + Save as Template… Сохранить как шаблон… + Scale to Frame Size Масштабировать по размеру кадра + Scene Edit Detection… Обнаружение склеек сцен… + Scratch Disks… Рабочие диски… + Search Bin Поисковая подборка + Select Выделение + Select All Выделить все + Select All Matching Выделить все совпадающие + Select Label Group Выделить группу по метке + Select Next Graphic Выбрать следующий графический элемент + Select Next Layer Выбрать следующий слой + Select Previous Graphic Выбрать предыдущий графический элемент + Select Previous Layer Выбрать предыдущий слой + Selection Follows Playhead Выделение следует за курсором воспроизведения + Selection as FilmCraft Project… Выделение как проект FilmCraft… + Selection… Выделение… + Send Backward Переместить назад + Send to Back Переместить на задний план + Sequence Эпизод + Sequence From Clip Эпизод из клипа + Sequence Settings… Настройки эпизода… + Sequence… Эпизод… + Settings Параметры + Show Active Caption Tracks Only Показать только активные дорожки субтитров + Show All Caption Tracks Показать все дорожки субтитров + Show All Marker Colors Показать все цвета маркеров + Show Guides Показать направляющие + Show Rulers Показать линейки + Show Through Edits Показать сквозные склейки + Simplify Sequence… Упростить эпизод… + Snap in Program Monitor Привязка в программном мониторе + Snap in Timeline Привязка на таймлайне + Source Источник + Source Settings… Настройки источника… + Speed/Duration… Скорость/Длительность… + Synchronize… Синхронизировать… + System Compatibility Report… Отчет о совместимости системы… + Tan Рыжевато-коричневый + Teal Бирюзовый + Text Текст + Time Interpolation Интерполяция времени + Timecode Тайм-код + Timecode… Тайм-код… + Timeline Таймлайн + Timeline… Таймлайн… + Toggle Proxies Переключить прокси + Tools Инструменты + Top По верхнему краю + Transcribe Sequence… Транскрибировать эпизод… + Transcribe… Транскрибировать… + Transcript Расшифровка + Transparent Video… Прозрачное видео… + Trim Edit Режим подрезки + Trim… Подрезка… + Undo Отменить + Ungroup Разгруппировать + Universal Counting Leader… Универсальный ракорд… + Update Metadata… Обновить метаданные… + Upgrade Caption to Graphic Преобразовать субтитр в графику + Upgrade to Source Graphic Преобразовать в исходную графику + Vertical Text Вертикальный текст + Video In Вход видео + Video Options Параметры видео + Video Out Выход видео + Video and Audio Waveform Split Раздельное отображение видео и аудиоволны + View Просмотр + Violet Фиолетовый + Window Окно + Workspaces Рабочие среды + Yellow Желтый + Zoom In Увеличить + Zoom Out Уменьшить + Zoom to Sequence Масштабировать по размеру эпизода diff --git a/crates/ui-egui/src/i18n/ru.tsv.attribution b/crates/ui-egui/src/i18n/ru.tsv.attribution new file mode 100644 index 00000000..23c128ac --- /dev/null +++ b/crates/ui-egui/src/i18n/ru.tsv.attribution @@ -0,0 +1,8 @@ +asset: ru.tsv +title: Russian interface translation catalog +author: FilmCraft contributors +source: original work +license: MIT OR Apache-2.0 +license-file: LICENSE-MIT and LICENSE-APACHE +added: 2026-10-10 by grib_job +notes: Original English-to-Russian menu translations using clean-room NLE vocabulary. diff --git a/crates/ui-egui/src/menus.rs b/crates/ui-egui/src/menus.rs index 5cac050e..581af9a3 100644 --- a/crates/ui-egui/src/menus.rs +++ b/crates/ui-egui/src/menus.rs @@ -27,6 +27,7 @@ pub const UI_COMMANDS: &[UiCommand] = &[ uic!("app.language.spanish", "Español", ["Edit", "Language"], None), uic!("app.language.portuguese", "Português (Brasil)", ["Edit", "Language"], None), uic!("app.language.ukrainian", "Українська", ["Edit", "Language"], None), + uic!("app.language.russian", "Русский", ["Edit", "Language"], None), uic!("source.playback.toggle", "Source Play/Stop", [], None), uic!("source.playback.play", "Play Source", [], None), uic!("source.playback.stop", "Stop Source", [], None), @@ -170,7 +171,7 @@ pub fn invoke(app: &mut FilmcraftApp, ctx: &egui::Context, id: &str, mut params: let object = params.as_object_mut().ok_or("command parameters must be an object")?; object.insert("target".into(), json!("source")); } - if matches!(id, "app.language.english" | "app.language.japanese" | "app.language.spanish" | "app.language.portuguese" | "app.language.ukrainian") { + if matches!(id, "app.language.english" | "app.language.japanese" | "app.language.spanish" | "app.language.portuguese" | "app.language.ukrainian" | "app.language.russian") { // Japanese needs the craft-fonts (built with CRAFT_FONTS_DIR) or a font installed on the system if id == "app.language.japanese" && !crate::i18n::install_japanese_font(ctx) { return Err("no Japanese font is installed on this system (for example Noto Sans CJK JP); the interface stays in English".into()); @@ -180,6 +181,7 @@ pub fn invoke(app: &mut FilmcraftApp, ctx: &egui::Context, id: &str, mut params: "app.language.spanish" => crate::i18n::Language::Es, "app.language.portuguese" => crate::i18n::Language::PtBr, "app.language.ukrainian" => crate::i18n::Language::Uk, + "app.language.russian" => crate::i18n::Language::Ru, _ => crate::i18n::Language::En, }; // The preference is updated in memory before it is written, so a failed write (read-only @@ -551,6 +553,7 @@ pub fn menu_items(app: &FilmcraftApp) -> Vec { "app.language.spanish" => it.checked = Some(app.ui.language == crate::i18n::Language::Es), "app.language.portuguese" => it.checked = Some(app.ui.language == crate::i18n::Language::PtBr), "app.language.ukrainian" => it.checked = Some(app.ui.language == crate::i18n::Language::Uk), + "app.language.russian" => it.checked = Some(app.ui.language == crate::i18n::Language::Ru), _ => {} } if it.id.starts_with("view.") {