Browse / github.com/storytold/craft-launcher / patch
i18n: add a professional Ukrainian (uk) translation
i18n: add a professional Ukrainian (uk) translation ## What Adds a complete Ukrainian (`uk`) UI translation. - `crates/ui-egui/src/i18n/uk.tsv` — the full catalog: 240 plain strings, 5 `@plural` rows, 17 `@msg` rows, 41 `@id` rows, 13 app taglines, 12 month names, 5 table column headers and the filter label, keyed exactly like `ru.tsv` (no key missing, no extra key). - `crates/ui-egui/src/i18n/lang.rs` — registers `uk` («Українська» / Ukrainian) with the East Slavic plural rule (one|few|many) and `complete_menus: true`, plus the `Lang::UK` constant. - `crates/ui-egui/src/i18n/tests.rs` — `ukrainian_locales_and_plurals_work`: `uk`, `uk-U
- Status
- open
- Score
- 0.00
- Adopters
- 0 other operators
- Author
- BrunIF (imported from pull request #640, open)
- Base
7c7271e5f62975326d0add2dcf31f03096be4189- Size
- +564 −15 in 5 files
- Review
- pending
- Shared
- 2026-10-28
- Id
43bbab14608485829ba6900c
How it went
No other operator has reported using it yet.
Diff
raw · README.md crates/ui-egui/src/i18n/lang.rs crates/ui-egui/src/i18n/tests.rs crates/ui-egui/src/i18n/uk.tsv crates/ui-egui/tests/ukrainian.rs
@@ -89,16 +89,17 @@ The web version shows the same catalog in the browser, with the latest download The interface is in English, Brazilian Portuguese (Português (Brasil)), Czech (Čeština), French (Français), German (Deutsch), Hungarian (Magyar), Japanese (日本語), Polish (Polski), Russian-(Русский), Simplified Chinese (简体中文) or Spanish (Español). **Auto**, the default, follows the-system or browser language; **Settings → Language** picks one and switches at once, and the choice-is kept between runs. Czech locales (`cs-CZ`, `cs_CZ.UTF-8`…) select Czech, German ones (`de-DE`,-`de-AT`, `de-CH`, `de_DE.UTF-8`…) German, French ones (`fr-FR`, `fr-CA`…) French, Hungarian ones-(`hu`, `hu-HU`, `hu_HU.UTF-8`…) Hungarian, Japanese ones (`ja`, `ja-JP`, `ja_JP.UTF-8`…) Japanese,-Polish ones (`pl`, `pl-PL`, `pl_PL.UTF-8`) Polish, Spanish ones (`es-ES`, `es-MX`, `es-419`…)-Spanish, and Portuguese ones (`pt-BR`, and `pt-PT` too, for now) Brazilian Portuguese. Simplified-Chinese locales (`zh-CN`, `zh-SG`, `zh-Hans`, and POSIX forms such as `zh_CN.UTF-8`) select-Chinese; Traditional Chinese ones don't. To try a language without changing the setting, start the-desktop app with `ARTCRAFT_LAUNCHER_LOCALE=pt-BR`, or add `?lang=ru` to the web address.+(Русский), Simplified Chinese (简体中文), Spanish (Español) or Ukrainian (Українська).+**Auto**, the default, follows the system or browser language; **Settings → Language** picks one+and switches at once, and the choice is kept between runs. Czech locales (`cs-CZ`, `cs_CZ.UTF-8`…)+select Czech, German ones (`de-DE`, `de-AT`, `de-CH`, `de_DE.UTF-8`…) German, French ones (`fr-FR`,+`fr-CA`…) French, Hungarian ones (`hu`, `hu-HU`, `hu_HU.UTF-8`…) Hungarian, Japanese ones (`ja`,+`ja-JP`, `ja_JP.UTF-8`…) Japanese, Polish ones (`pl`, `pl-PL`, `pl_PL.UTF-8`) Polish, Ukrainian ones+(`uk`, `uk-UA`, `uk_UA.UTF-8`…) Ukrainian, Spanish ones (`es-ES`, `es-MX`, `es-419`…) Spanish, and+Portuguese ones (`pt-BR`, and `pt-PT` too, for now) Brazilian Portuguese. Simplified Chinese locales+(`zh-CN`, `zh-SG`, `zh-Hans`, and POSIX forms such as `zh_CN.UTF-8`) select Chinese; Traditional+Chinese ones don't. To try a language without changing the setting, start the desktop app with+`ARTCRAFT_LAUNCHER_LOCALE=pt-BR`, or add `?lang=ru` to the web address. The CJK languages are drawn with the system's own CJK font (Hiragino Sans or PingFang on macOS, Yu Gothic, Meiryo or Microsoft YaHei on Windows, Noto Sans CJK, IPA, Takao or WenQuanYi on Linux and@@ -22,7 +22,8 @@ impl Script { /// One supported UI language. pub struct LangInfo {- /// Canonical BCP 47 code (`en`, `ru`, `zh-CN`, `de`, `fr`, `pt-BR`, `es`, `cs`, `pl`, `hu`, `ja`).+ /// Canonical BCP 47 code (`en`, `ru`, `uk`, `zh-CN`, `de`, `fr`, `pt-BR`, `es`, `cs`, `pl`,+ /// `hu`, `ja`). /// Also the `interfaceLanguage` value. pub code: &'static str, /// The language's name in itself, shown in the Preferences dropdown.@@ -34,9 +35,9 @@ pub struct LangInfo { /// Catalog file contents (empty for the built-in English). pub source: &'static str, /// Plural form index for a count (English, German, Spanish and Hungarian: 0 = one, 1 = other;- /// Russian: 0 = one, 1 = few, 2 = many; Czech: 0 = one, 1 = few, 2 = other; Polish: 0 = one only- /// for 1, 1 = few, 2 = many; French: 0 = one for 0 and 1; a language whose nouns don't inflect- /// for number: always 0).+ /// Russian and Ukrainian: 0 = one, 1 = few, 2 = many; Czech: 0 = one, 1 = few, 2 = other;+ /// Polish: 0 = one only for 1, 1 = few, 2 = many; French: 0 = one for 0 and 1; a language whose+ /// nouns don't inflect for number: always 0). pub plural: fn(u64) -> usize, /// Number of plural forms the catalog's `@plural` rows carry (checked by tests). pub plural_forms: usize,@@ -94,7 +95,7 @@ pub(super) fn plural_polish(n: u64) -> usize { } /// The registry. English first: it is the fallback and the source language.-pub static LANGUAGES: [LangInfo; 11] = [+pub static LANGUAGES: [LangInfo; 12] = [ LangInfo { code: "en", name: "English",@@ -216,6 +217,17 @@ pub static LANGUAGES: [LangInfo; 11] = [ complete_menus: true, catalog: OnceLock::new(), },+ LangInfo {+ code: "uk",+ name: "Українська",+ english_name: "Ukrainian",+ script: None,+ source: include_str!("uk.tsv"),+ plural: plural_east_slavic,+ plural_forms: 3,+ complete_menus: true,+ catalog: OnceLock::new(),+ }, ]; impl LangInfo {@@ -254,6 +266,8 @@ impl Lang { pub const PL: Lang = Lang(&LANGUAGES[8]); pub const HU: Lang = Lang(&LANGUAGES[9]); pub const JA: Lang = Lang(&LANGUAGES[10]);+ /// The canonical `uk` code, so call sites can name Ukrainian without a lookup.+ pub const UK: Lang = Lang(&LANGUAGES[11]); pub fn code(self) -> &'static str { self.0.code@@ -25,10 +25,12 @@ fn rows(lang: &LangInfo) -> Vec<(usize, &str, &str, &str)> { out } +/// Every catalog that carries rows (English is the built-in source, with no file of its own). fn catalogs() -> impl Iterator<Item = &'static LangInfo> { LANGUAGES.iter().filter(|l| !l.source.is_empty()) } +/// The `{name}` placeholders a source or translation line carries, for comparison. fn placeholders(template: &str) -> BTreeSet<String> { let mut out = BTreeSet::new(); let mut rest = template;@@ -211,6 +213,28 @@ fn russian_plural_rule_works() { assert_eq!(trn(Lang::RU, 3, "{n} day ago", "{n} days ago"), "3 дня назад"); } +#[test]+fn ukrainian_locales_and_plurals_work() {+ for tag in ["uk", "uk-UA", "uk_UA.UTF-8", "UK_ua"] {+ assert_eq!(lang_from_tag(tag), Some(Lang::UK), "{tag}");+ }+ assert_eq!(super::detect::first_supported(["xx-XX", "uk-UA", "en-US"]), Some(Lang::UK));+ // Ukrainian counts share Russian's rule: 1 (but not 11) is "one", 2–4 (but not 12–14) "few",+ // everything else "many".+ assert_eq!(trn(Lang::UK, 1, "{n} app", "{n} apps"), "1 застосунок");+ assert_eq!(trn(Lang::UK, 3, "{n} app", "{n} apps"), "3 застосунки");+ assert_eq!(trn(Lang::UK, 5, "{n} app", "{n} apps"), "5 застосунків");+ assert_eq!(trn(Lang::UK, 21, "{n} day ago", "{n} days ago"), "21 день тому");+ assert_eq!(trn(Lang::UK, 22, "{n} day ago", "{n} days ago"), "22 дні тому");+ assert_eq!(trn(Lang::UK, 12, "{n} day ago", "{n} days ago"), "12 днів тому");+ // A key the Ukrainian catalog doesn't carry falls back to the English forms.+ assert_eq!(trn(Lang::UK, 3, "{n} unknown item", "{n} unknown items"), "3 unknown items");+ assert_eq!(super::tr_ctx(Lang::UK, "month", "Mar"), "бер.");+ assert_eq!(tr(Lang::UK, "{month} {day}, {year}"), "{day} {month} {year}");+ assert_eq!(message(Lang::UK, "PhotoCraft updated to 0.5.0"), "Застосунок PhotoCraft оновлено до 0.5.0");+ assert_eq!(tr(Lang::UK, "Untranslated diagnostic"), "Untranslated diagnostic");+}+ #[test] fn czech_plural_rule_works() { for n in [0, 5, 11, 21, 22, 25] {new file mode 100644@@ -0,0 +1,374 @@+# Ukrainian (uk) catalog for ArtCraft Launcher.+# Format: context<TAB>source<TAB>translation+# Empty context = plain UI string (tl!, t).+# @id = command-id keyed (tr_id).+# @plural = plural forms: one|other (English) or one|few|many (Ukrainian).+# @msg = status/error message template (message, msg).+# Other context = disambiguation for tr_ctx.+#+# Clean-room: written from the meaning of ArtCraft Launcher's English labels in clear,+# idiomatic Ukrainian for users of creative software.+#+# Coverage: the whole interface — every menu string and tl! literal (complete_menus = true).+# Left in English on purpose: product name "ArtCraft Launcher" (and its "Launcher" tag), suite+# name "Craft App Suite",+# app names (PhotoCraft, LightCraft, FilmCraft, DesignCraft, VectorCraft, PdfCraft, EffectCraft,+# WordCraft, DeckCraft, GridCraft, CADCraft, SoundCraft, FigCraft), "GitHub", "Discord", "Website".+# Shortcut keys keep their key-cap names (Shift, Alt, Ctrl, Cmd, Esc).+# Trailing "…" (U+2026) must be preserved. Placeholders {name}, {n}, {version}, {count}, {v},+# {what}, {unit}, {real}, {e} must appear in translation.++ Launcher Launcher+ Craft App Suite Craft App Suite+ Open Source Tools for Creatives, Makers, and Builders Відкриті інструменти для творців, мейкерів і розробників+ Settings Налаштування+ Check for updates Перевірити оновлення+ Grid Сітка+ Table Таблиця+ Show apps as cards Показувати застосунки картками+ Show apps as a table Показувати застосунки таблицею+ All Усі+ Updates Оновлення+ Not installed Не встановлені+ Search apps Пошук застосунків+ Sort Сортування+ Suite order За порядком у наборі+ Name Назва+ Status Статус+ Installed version Встановлена версія+ Latest version Остання версія+ Newest release Найновіший випуск+ Discord Discord+ GitHub GitHub+ Website Вебсайт+ Classic apps Класичні застосунки+ New apps Нові застосунки+ NEW НОВЕ+ Install Встановити+ Update to {v} Оновити до {v}+ Open Відкрити+ Download for {os} Завантажити для {os}+ Download Завантажити+ Coming soon Незабаром+ Checking… Перевірка…+ Not available Недоступно+ Offline Не в мережі+ Downloading {done} of {total} Завантаження {done} із {total}+ Downloading… Завантаження…+ Reconnecting… Повторне з’єднання…+ Verifying… Перевірка цілісності…+ Installing, then restarting… Встановлення, потім перезапуск…+ Installing… Встановлення…+ Removing… Видалення…+ Pause download Призупинити завантаження+ Cancel download Скасувати завантаження+ Resume download Продовжити завантаження+ Discard download Видалити завантажене+ Release notes Примітки до випуску+ Show in Finder Показати в Finder+ Show in Explorer Показати в Провіднику+ Show in Files Показати у файловому менеджері+ Reinstall Перевстановити+ Uninstall… Видалити…+ All releases Усі випуски+ ArtCraft Launcher {version} is available. Доступний ArtCraft Launcher {version}.+ Dismiss Закрити+ Update and restart Оновити й перезапустити+ A new version is available. Would you like to relaunch with the updated version? Доступна нова версія. Перезапустити з оновленою версією?+ Later Пізніше+ What's new Що нового+ No notes were published with this release. До цього випуску примітки не опубліковано.+ Released {date} Випущено {date}+ Open on GitHub Відкрити на GitHub+ Close Закрити+ ArtCraft Launcher {version} ArtCraft Launcher {version}+ Only when I ask Лише за моїм запитом+ Automatically download and install updates Автоматично завантажувати й встановлювати оновлення+ Notify me when an installed app has an update Повідомляти, коли для встановленого застосунку з’являється оновлення+ Include pre-releases (betas and release candidates) Включати передвипуски (бета-версії та реліз-кандидати)+ Installing on Linux Встановлення в Linux+ Installing on Windows Встановлення у Windows+ Install new apps as Встановлювати нові застосунки як+ System package (recommended) Системний пакет (рекомендовано)+ Updates always use the format an app was installed with. Оновлення завжди використовують формат, у якому застосунок було встановлено.+ Running in the background Робота у фоновому режимі+ Keep running in the tray when the window is closed Продовжувати роботу в треї після закриття вікна+ Show the window on the current desktop Показувати вікно на поточному робочому столі+ Start ArtCraft Launcher when I log in Запускати ArtCraft Launcher під час входу в систему+ Community Спільнота+ Questions, ideas and news from the ArtCraft team Запитання, ідеї та новини від команди ArtCraft+ Appearance Зовнішній вигляд+ Quit ArtCraft Launcher Вийти з ArtCraft Launcher+ Done Готово+ Uninstall {name}? Видалити {name}?+ The app will be moved to the Trash. Your documents and settings stay. Застосунок буде переміщено в Кошик. Ваші документи та налаштування залишаться.+ Windows will remove it like any installed program. Your documents and settings stay. Windows видалить його так само, як будь-яку іншу програму. Ваші документи та налаштування залишаться.+ Your system's package manager will remove it, and may ask for your password. Your documents and settings stay. Системний менеджер пакетів видалить його й може запитати ваш пароль. Ваші документи та налаштування залишаться.+ The app will be removed. Your documents and settings stay. Застосунок буде видалено. Ваші документи та налаштування залишаться.+ Uninstall Видалити+ Cancel Скасувати+ Checked {ago} Перевірено {ago}+ just now щойно+ yesterday вчора+ {month} {day}, {year} {day} {month} {year}++ Automatically Автоматично+ Every Craft app Усі застосунки Craft+ Apps on this computer Застосунки на цьому комп’ютері+ Installed apps with a newer version Встановлені застосунки з новішою версією+ Apps you can install Застосунки, які можна встановити+ Hide App Сховати застосунок+ Unhide App Показати застосунок+ Hidden Apps Сховані застосунки+ Apps you hid Застосунки, які ви сховали+ Checked {ago} · using {sources} Перевірено {ago} · джерела: {sources}+ {a} and {b} {a} і {b}+ Join the Discord Приєднатися до Discord+ Learn more Докладніше+ Minimize Згорнути+ Maximize Розгорнути+ Restore Відновити+ New Нове+ OK Гаразд+ Open link Відкрити посилання+ Update Оновити+ {name} icon Значок {name}+ Loading ArtCraft Launcher… Завантаження ArtCraft Launcher…+ Cancel update Скасувати оновлення+ Stop waiting; the update can be started again later Припинити чекати; оновлення можна буде запустити знову пізніше+ Download again Завантажити ще раз+ Install cancelled Встановлення скасовано+ Install cancelled. The download is kept for the next try. Встановлення скасовано. Завантажений файл збережено для наступної спроби.+ Update cancelled Оновлення скасовано+ Uninstall cancelled Видалення скасовано+ Retry install Повторити встановлення+ Retry update to {version} Повторити оновлення до {version}+ The download is still here. It's checked again, then installed. Завантажений файл усе ще на місці. Його перевірять ще раз, а потім встановлять.+ Waiting for another app… Очікування іншого застосунку…+ Waiting for other installs… Очікування інших встановлень…++# Package formats offered for download (PackageKind::label)+ Installer (.msi) Програма встановлення (.msi)+ Portable (.zip) Портативна версія (.zip)+ Disk image (.dmg) Образ диска (.dmg)+ Debian package (.deb) Пакет Debian (.deb)+ RPM package (.rpm) Пакет RPM (.rpm)+ Arch package (.pkg.tar.zst) Пакет Arch (.pkg.tar.zst)+ Flatpak bundle Пакет Flatpak+ Tarball (.tar.gz) Архів (.tar.gz)++# Contextual: each app's tagline, keyed by app id+photocraft Image editing Редагування зображень+vectorcraft Vector illustration Векторна іллюстрація+filmcraft Video editing, color and sound Монтаж відео, корекція кольору та звук+lightcraft Photo library and raw development Фототека та проявка RAW+pdfcraft Reading, organizing and protecting PDFs Читання, впорядкування та захист PDF+effectcraft Motion graphics and visual effects Моушн-графіка та візуальні ефекти+designcraft Page layout and publishing Верстка та публікації+wordcraft Writing and document design Написання текстів та оформлення документів+deckcraft Presentations and slide shows Презентації та слайд-шоу+gridcraft Spreadsheets and data Електронні таблиці та дані+cadcraft Computer-aided design and drafting Комп’ютерне проєктування та креслення+soundcraft Recording, editing and mixing audio Запис, редагування та зведення звуку+figcraft Interface design and prototyping Дизайн інтерфейсів та прототипування++# Contextual: month abbreviations for date rendering+month Jan січ.+month Feb лют.+month Mar бер.+month Apr квіт.+month May трав.+month Jun черв.+month Jul лип.+month Aug серп.+month Sep вер.+month Oct жовт.+month Nov лист.+month Dec груд.+ Not checked yet Ще не перевірено+ GitHub's hourly limit was reached; full details return in {min} min Вичерпано годинний ліміт GitHub; повні подробиці повернуться через {min} хв+ GitHub limit hit; back in {min} min Ліміт GitHub вичерпано; доступ відновиться за {min} хв+ Installing for {platform} Встановлення для {platform}+ Downloads for {platform} Завантаження для {platform}+ No downloads for this system Немає завантажень для цієї системи+ Join the ArtCraft community on Discord Приєднатися до спільноти ArtCraft у Discord+ ArtCraft on {name} ArtCraft на {name}+ Couldn't open {name}: {e} Не вдалося відкрити {name}: {e}+ Download cancelled Завантаження скасовано+ Available to install Доступно для встановлення+ Available Доступно+ Installed · up to date Встановлено · актуально+ Update available Доступне оновлення+ Installed Встановлено+ installed встановлено+ Not available here Недоступно тут+ Updating… Оновлення…+ Update paused Оновлення призупинено+ Download paused Завантаження призупинено+ {version} installed → {latest} released {version} встановлено → {latest} випущено+ Version {version} Версія {version}+ {installed} installed · latest {latest} {installed} встановлено · остання {latest}+ Installed · latest {latest} Встановлено · остання {latest}+ Version {version} installed Версія {version} встановлена+ Version {latest} Версія {latest}+ No release published yet Випуск ще не опубліковано+ ArtCraft Launcher ArtCraft Launcher+ App Застосунок+ Latest Остання+ Released Випущено+ Auto Авто+ Language Мова+ Ascending За зростанням+ Descending За спаданням+ Click to reverse the order Натисніть, щоб змінити порядок+ Click to sort by this column Натисніть, щоб сортувати за цією колонкою+ {done} of {total} {done} із {total}+ Downloading Завантаження+ Verifying checksum… Перевірка контрольної суми…+ Resume Продовжити+ {done} of {total} downloaded Завантажено {done} із {total}+ {done} downloaded Завантажено {done}+ Checking for updates… Перевірка оновлень…+ Downloads the new version, replaces this one and opens it again Завантажує нову версію, замінює цю і відкриває її знову+ Couldn't check for updates: {f} Не вдалося перевірити оновлення: {f}+ GitHub repository Репозиторій GitHub+ ArtCraft ArtCraft+ Update 1 app Оновити 1 застосунок+ Update all ({count}) Оновити всі ({count})+ ⊞ Grid ⊞ Сітка+ ☰ Table ☰ Таблиця+ 🔍 Search apps 🔍 Пошук застосунків+ {name} {version} {name} {version}+ Match system Як у системі+ Dark Темна+ Light Світла+ Full changelog Повний список змін+ link посилання+ No apps match Немає відповідних застосунків+ Open ArtCraft Launcher to update. Відкрийте ArtCraft Launcher, щоб оновити.+ {name} {v} is available Доступне оновлення {name} {v}+ {count} updates are available Доступні оновлення ({count})+ These notes weren't fetched (GitHub's request limit was reached). They're on the release page. Ці примітки не вдалося отримати (вичерпано ліміт запитів GitHub). Вони є на сторінці випуску.+ When on, opening the launcher from the menu bar brings its window to the desktop you're on. When off, macOS switches you to the desktop the window was on. Якщо ввімкнено, відкриття лаунчера з рядка меню переносить його вікно на поточний робочий стіл. Якщо вимкнено, macOS перемикає вас на робочий стіл, де було вікно.+ Couldn't change the login item: {e} Не вдалося змінити елемент автозапуску: {e}++# Contextual: table column headers+column App Застосунок+column Installed Встановлена+column Latest Остання+column Released Дата випуску+column Version Версія++# Contextual: the filter tab names a group (the status pill says "Installed")+filter Installed Встановлені+ {name} details {name}: подробиці++# Plural forms for Ukrainian (one|few|many)+@plural {n} app|{n} apps {n} застосунок|{n} застосунки|{n} застосунків+@plural {n} min ago|{n} min ago {n} хвилину тому|{n} хвилини тому|{n} хвилин тому+@plural {n} h ago|{n} h ago {n} годину тому|{n} години тому|{n} годин тому+@plural {n} day ago|{n} days ago {n} день тому|{n} дні тому|{n} днів тому++# Message templates (@msg): toasts from the backend. {placeholders} match any text.+@msg Couldn't open {name}: {e} Не вдалося відкрити {name}: {e}+@msg Download cancelled Завантаження скасовано+@msg {name} updated to {version} Застосунок {name} оновлено до {version}+@msg {name} installed: {version} Застосунок {name} встановлено: {version}+@msg {name} updated to {version} (this release publishes no checksum, so only its size was checked) Застосунок {name} оновлено до {version} (у цьому випуску немає контрольних сум, тому перевірено лише розмір)+@msg {name} installed: {version} (this release publishes no checksum, so only its size was checked) Застосунок {name} встановлено: {version} (у цьому випуску немає контрольних сум, тому перевірено лише розмір)+@msg {name} was removed Застосунок {name} видалено+@msg {name} is running. Quit it, then try again. Застосунок {name} запущено. Закрийте його та спробуйте ще раз.+@msg {name} was installed from the {repo} repository or the AUR ({pkg}). Update it with {cmd} or sudo pacman -Syu Застосунок {name} встановлено з репозиторію {repo} або з AUR ({pkg}). Оновіть його командою {cmd} або sudo pacman -Syu+@msg {name} was installed from the {repo} repository or the AUR ({pkg}). Update it with your AUR helper (for example: yay -S {pkg}) or sudo pacman -Syu Застосунок {name} встановлено з репозиторію {repo} або з AUR ({pkg}). Оновіть його за допомогою вашого помічника AUR (наприклад: yay -S {pkg}) або командою sudo pacman -Syu+@msg {name} was installed from the AUR ({pkg}). Update it with {cmd} Застосунок {name} встановлено з AUR ({pkg}). Оновіть його командою {cmd}+@msg {name} was installed from the AUR ({pkg}). Update it with your AUR helper (for example: yay -S {pkg}) Застосунок {name} встановлено з AUR ({pkg}). Оновіть його за допомогою вашого помічника AUR (наприклад: yay -S {pkg})++# Command IDs for menu items (@id)+# Format: @id<TAB>command_id<TAB>translation+@id app.install Встановити+@id app.update Оновити до {v}+@id app.open Відкрити+@id app.download Завантажити для {os}+@id app.reveal Показати в Finder+@id app.reinstall Перевстановити+@id app.uninstall Видалити…+@id app.notes Примітки до випуску+@id app.website Вебсайт+@id app.github Репозиторій на GitHub+@id app.releases Усі випуски+@id launcher.update Оновити й перезапустити+@id launcher.download Завантажити+@id launcher.notes Що нового+@id launcher.dismiss Закрити+@id settings.open Налаштування+@id settings.quit Вийти з ArtCraft Launcher+@id view.grid Сітка+@id view.table Таблиця+@id filter.all Усі+@id filter.installed Встановлені+@id filter.updates Оновлення+@id filter.notinstalled Не встановлені+@id sort.suite За порядком у наборі+@id sort.name Назва+@id sort.status Статус+@id sort.installed Встановлена версія+@id sort.latest Остання версія+@id sort.released Найновіший випуск+@id help.discord Discord+@id help.github GitHub+@id help.website Вебсайт+@id update.check Перевірити оновлення+@id update.check_auto Перевіряти оновлення автоматично+@id update.notify Повідомляти, коли для встановленого застосунку з’являється оновлення+@id update.prereleases Включати передвипуски (бета-версії та реліз-кандидати)+@id linux.preferred_kind Встановлювати нові застосунки як+@id tray.close_to_tray Продовжувати роботу в треї після закриття вікна+@id tray.show_on_current_desktop Показувати вікно на поточному робочому столі+@id tray.login_item Запускати ArtCraft Launcher під час входу в систему+@id appearance.theme Зовнішній вигляд++# Tray (menu bar) menu and tooltip+ Show ArtCraft Launcher Показати ArtCraft Launcher+ Check for Updates Перевірити оновлення+ Visit getartcraft.com Відкрити getartcraft.com+ ArtCraft on GitHub ArtCraft на GitHub+ Join the ArtCraft Discord Приєднатися до Discord ArtCraft+ App details Про застосунок+ Close (Esc) Закрити (Esc)+ Features Можливості+ Latest release Останній випуск+ Version Версія+ Platforms Платформи+ Releases Випуски+ Enlarge picture Збільшити зображення+ Click to enlarge Натисніть, щоб збільшити+ Previous picture (←) Попереднє зображення (←)+ Next picture (→) Наступне зображення (→)+ Loading pictures… Завантаження зображень…+ Pictures couldn't be loaded. Reopen this page to try again. Не вдалося завантажити зображення. Відкрийте цю сторінку ще раз.+@plural ArtCraft Launcher · {n} update available|ArtCraft Launcher · {n} updates available ArtCraft Launcher · {n} оновлення|ArtCraft Launcher · {n} оновлення|ArtCraft Launcher · {n} оновлень+ Install folder Папка встановлення+ Default install folder Папка встановлення за замовчуванням+ System default Системне значення за замовчуванням+ Browse… Огляд…+ Use default Скористатися значенням за замовчуванням+ New installs use this folder. Updates stay in their current location. Нові застосунки встановлюються в цю папку. Під час оновлення поточне розташування зберігається.+ Custom folders use AppImage on Linux. Для власних папок у Linux використовується AppImage.+ The system manages install locations here. Тут розташуванням застосунків керує система.+@msg couldn't use install folder {path}: {reason} не вдалося використати папку встановлення {path}: {reason}+@msg this release has no AppImage for a custom install folder у цьому випуску немає AppImage для власної папки встановлення+@msg custom install folders aren't supported on this system ця система не підтримує власні папки встановлення+@msg {path} already exists and wasn't installed by ArtCraft Launcher {path} уже існує і не був встановлений через ArtCraft Launcher+@msg couldn't move {path} to Trash ({reason}). Remove it in Finder instead. не вдалося перемістити {path} у Кошик ({reason}). Натомість видаліть його у Finder.+ Apps are placed inside this folder. Застосунки розміщуються всередині цієї папки.+ Replace what's there? Замінити наявне?+ {path} already exists. Installing {name} will overwrite it. {path} уже існує. Встановлення {name} перезапише його.+ Replace Замінити+ Recently updated Нещодавно оновлені+ Name (A–Z) Назва (A–Z)+ Name (Z–A) Назва (Z–A)+ Frequently used Часто використовувані+ Next app Наступний застосунок+ Previous app Попередній застосунок+ Show {n} more changes Показати ще {n} змін+ Last updated: {date} Востаннє оновлено: {date}new file mode 100644@@ -0,0 +1,136 @@+//! The Ukrainian UI, in its own test binary: the UI language is process-wide, so it can't share one+//! with the English UI tests. `ARTCRAFT_LAUNCHER_SNAPSHOTS=<dir>` also saves screenshots (run with+//! `-- --test-threads=1`, see `ui.rs`).++use std::cell::Cell;+use std::rc::Rc;++use artcraft_launcher_core::protocol::{Backend, Capabilities, Command, Event, JobPhase, LatestRelease, Origin, Source};+use artcraft_launcher_core::release::{Asset, Release};+use artcraft_launcher_core::status::InstallSource;+use artcraft_launcher_core::{Arch, Installed, Os, Platform, SUITE, version};+use artcraft_launcher_ui_egui::settings::ViewMode;+use artcraft_launcher_ui_egui::{Host, LauncherUi, Settings};+use egui_kittest::Harness;+use egui_kittest::kittest::Queryable;++const NOW: i64 = 1_791_500_000;++struct Fake {+ events: Vec<Event>,+ clock: Rc<Cell<i64>>,+}++impl Backend for Fake {+ fn send(&mut self, _command: Command) {}+ fn poll(&mut self) -> Vec<Event> {+ std::mem::take(&mut self.events)+ }+ fn capabilities(&self) -> Capabilities {+ Capabilities { manage_apps: true, reveal_label: "Show in Finder", self_update: true }+ }+ fn platform(&self) -> Option<Platform> {+ Some(Platform { os: Os::MacOs, arch: Arch::Arm64 })+ }+ fn now(&self) -> i64 {+ self.clock.get()+ }+}++struct NoHost;+impl Host for NoHost {}++/// A macOS release of the app `id` at version `v`, published at `at`.+fn release(id: &str, v: &str, at: i64) -> Release {+ let name = format!("{id}-{v}-macos-universal.dmg");+ Release {+ tag: format!("v{v}"),+ version: version::parse(v),+ name: format!("v{v}"),+ published_at: Some(at),+ html_url: format!("https://github.com/storytold/{id}/releases/tag/v{v}"),+ notes: String::new(),+ prerelease: false,+ assets: vec![Asset {+ name: name.clone(),+ size: 150 << 20,+ url: format!("https://github.com/storytold/{id}/releases/download/v{v}/{name}"),+ sha256: None,+ }],+ }+}++/// The startup events: a latest release and a detection for every app, one job at the verifying+/// phase, a finished check three minutes ago, and a backend message shown through its `@msg`+/// template.+fn events() -> Vec<Event> {+ let mut ev = Vec::new();+ for (i, app) in SUITE.iter().enumerate() {+ let latest = LatestRelease::Found(release(app.id, "0.5.0", NOW - 40 * 86_400 - i as i64 * 600));+ ev.push(Event::Latest { app: app.id.into(), latest, from: Origin::ArtCraftApi });+ let installed = matches!(app.id, "photocraft" | "lightcraft").then(|| Installed {+ version: version::parse(if app.id == "photocraft" { "0.4.1" } else { "0.5.0" }),+ source: InstallSource::MacApp,+ location: format!("/Applications/{}.app", app.name),+ package: None,+ });+ ev.push(Event::Detected { app: app.id.into(), installed });+ }+ ev.push(Event::Progress { app: "filmcraft".into(), phase: JobPhase::Verifying });+ ev.push(Event::CheckFinished { at: NOW - 180, source: Source::Feed, rate_limited_until: None });+ // A backend message, translated through its `@msg` template where it's shown.+ ev.push(Event::JobFinished { app: "lightcraft".into(), result: Ok("LightCraft updated to 0.5.0".into()) });+ ev+}++/// The launcher UI in Ukrainian, in the given view, over the fake backend from [`events`]. The UI+/// language is process-wide, so this binary holds a single test.+fn harness(view: ViewMode) -> Harness<'static, Option<LauncherUi>> {+ artcraft_launcher_ui_egui::i18n::set_test_lang(artcraft_launcher_ui_egui::i18n::Lang::EN);+ let mut backend = Some(Fake { events: events(), clock: Rc::new(Cell::new(NOW)) });+ let settings =+ Settings { view, notify: false, language: "uk".into(), theme: artcraft_launcher_ui_egui::theme::ThemeChoice::Dark, ..Settings::default() };+ Harness::builder().with_size(egui::vec2(1180.0, 900.0)).with_theme(egui::Theme::Dark).build_ui_state(+ move |ui, state: &mut Option<LauncherUi>| {+ if state.is_none()+ && let Some(b) = backend.take()+ {+ *state = Some(LauncherUi::new(ui.ctx(), Box::new(b), settings.clone()));+ }+ if let Some(l) = state.as_mut() {+ l.logic(&ui.ctx().clone(), &mut NoHost);+ l.ui(ui, &mut NoHost);+ }+ },+ None,+ )+}++/// Save a PNG of `h` under `ARTCRAFT_LAUNCHER_SNAPSHOTS`, if that directory is set.+fn snapshot(h: &mut Harness<'_, Option<LauncherUi>>, name: &str) {+ let Some(dir) = std::env::var_os("ARTCRAFT_LAUNCHER_SNAPSHOTS") else { return };+ let img = h.render().unwrap();+ img.save(std::path::Path::new(&dir).join(format!("{name}.png"))).unwrap();+}++// One test: the UI language is process-wide.+#[test]+fn ukrainian_ui_translates_labels_dates_and_messages() {+ let mut h = harness(ViewMode::Grid);+ h.run_steps(8);+ snapshot(&mut h, "grid-uk-dark");+ // Product names stay in English.+ assert!(h.query_by_label("Launcher").is_none());+ h.get_by_label("Craft App Suite");+ h.get_by_label("Редагування зображень");+ h.get_by_label("Оновити до 0.5.0");+ h.get_by_label_contains("Перевірка контрольної суми…");+ h.get_by_label_contains("Перевірено 3 хвилини тому · джерела: ArtCraft API");+ h.get_by_label("Застосунок LightCraft оновлено до 0.5.0");++ let mut table = harness(ViewMode::Table);+ table.run_steps(8);+ snapshot(&mut table, "table-uk-dark");+ table.get_by_label("Версія");+ assert!(table.query_all_by_label_contains("серп. 2026").count() >= 1);+}