Add Polish (Polski) as an interface language. (#138)

* Add a Polish interface

- pl.tsv: the full catalog (287 rows), written clean-room from the English
  labels; plurals use the same one|few|many rule as Russian.
- Registered as the seventh language: "Polski" in the picker, locales pl,
  pl-PL and pl_PL.UTF-8, complete_menus = true so the completeness tests
  cover it.
- tests/polish.rs: the Polish UI test, modeled on the French one.
- README and AGENTS updated for the new language.

* i18n: add Czech (cs) interface language

Czech catalog with every key of the most complete catalog (287 rows), the
cs language entry with locale detection (cs-CZ, cs_CZ.UTF-8, ?lang=cs),
a Czech plural rule (1 / 2-4 / other) with a unit test, and the README
language list.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>

* i18n(pl): use Polish plural rule, so 21 is "21 aplikacji"

plural_east_slavic treats 21, 31 and 101 as "one", which is right for
Russian but not Polish, where only 1 itself takes the singular.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>

* i18n(cs): translate "Installed · latest {latest}" (added by #195)

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>

* i18n(pl): translate "Installed · latest {latest}" (added by #195)

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>

---------

Co-authored-by: Losminek <301412321+Losminek@users.noreply.github.com>
Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
Co-authored-by: Brandon Thomas <echelon@gmail.com>
This commit is contained in:
authored and GitHub committed 2026-10-12 02:48:08 -04:00
1 parent 6d59370024
commit 94d6480a11
9 files changed
+535 -19

No files matched your search

+4 -2
View File
@@ -76,8 +76,10 @@ colour tokens by name (`theme::Tokens`). Build new UI from `widgets.rs`; never h
Every user-facing string goes through `crates/ui-egui/src/i18n` (`tl!("…")` for literals, `fmt`
for `{placeholders}`, `tn` for counts). English in the code is the lookup key; adding a string
means adding its row to every catalog marked `complete_menus` (`ru.tsv`, `zh-CN.tsv`, `de.tsv`),
which the i18n tests check. Product and app names (ArtCraft Launcher, Craft App Suite, PhotoCraft…) stay in English.
means adding its row to every catalog marked `complete_menus` (`ru.tsv`, `zh-CN.tsv`, `de.tsv`,
`fr.tsv`, `pt-BR.tsv`, `pl.tsv`), which the i18n tests check. Translate clean-room from the
meaning of the English text, never through another catalog. Product and app names (ArtCraft
Launcher, Craft App Suite, PhotoCraft…) stay in English.
Chinese is drawn with the system's own CJK font (`crates/ui-egui/src/cjk_font.rs`, desktop only;
English where there is none): never bundle a CJK font, they are 10 MB and more.
+10 -9
View File
@@ -85,15 +85,16 @@ The web version shows the same catalog in the browser, with the latest download
## Interface languages
The interface is in English, Brazilian Portuguese (Português (Brasil)), Czech (Čeština), French
(Français), German (Deutsch), 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, 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.
(Français), German (Deutsch), 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, 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.
Chinese is drawn with the system's own Chinese font (PingFang on macOS, Microsoft YaHei on
Windows, Noto Sans CJK or WenQuanYi on Linux and FreeBSD). Where there is none, and in the web
+1 -1
View File
@@ -6,7 +6,7 @@
//! - `@id` context = keyed by command id (`tr_id`).
//! - `@plural` context = plural forms: the English `one|other` key, and the language's forms
//! separated by `|` (one form for a language whose nouns don't inflect for number, up to
//! three: `one|few|many` for Russian).
//! three: `one|few|many` for Russian, Ukrainian and Polish).
//! - `@msg` context = a status/error message, or a template whose `{placeholders}` match any text
//! (`message`, `msg`).
//! - Any other context = disambiguation for `tr_ctx`.
+29 -4
View File
@@ -7,7 +7,8 @@ use super::detect::system_lang;
/// One supported UI language.
pub struct LangInfo {
/// Canonical BCP 47 code (`en`, `ru`, `zh-CN`, `de`, `fr`, `cs`). Also the `interfaceLanguage` value.
/// Canonical BCP 47 code (`en`, `ru`, `zh-CN`, `de`, `fr`, `pt-BR`, `es`, `cs`, `pl`). Also the
/// `interfaceLanguage` value.
pub code: &'static str,
/// The language's name in itself, shown in the Preferences dropdown.
pub name: &'static str,
@@ -19,8 +20,8 @@ pub struct LangInfo {
/// Catalog file contents (empty for the built-in English).
pub source: &'static str,
/// Plural form index for a count (English, German and Spanish: 0 = one, 1 = other; Russian:
/// 0 = one, 1 = few, 2 = many; Czech: 0 = one, 1 = few, 2 = other; French: 0 = one for 0 and 1;
/// Chinese: always 0).
/// 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; Chinese: always 0).
pub plural: fn(u64) -> usize,
/// Number of plural forms the catalog's `@plural` rows carry (checked by tests).
pub plural_forms: usize,
@@ -65,8 +66,20 @@ pub(super) fn plural_czech(n: u64) -> usize {
}
}
/// Polish integer counts: only 1 itself is `one` (21 is "21 aplikacji"), 2–4 in the last digit
/// (but not 12–14) `few`, everything else `many`.
pub(super) fn plural_polish(n: u64) -> usize {
if n == 1 {
0
} else if matches!(n % 10, 2..=4) && !matches!(n % 100, 12..=14) {
1
} else {
2
}
}
/// The registry. English first: it is the fallback and the source language.
pub static LANGUAGES: [LangInfo; 8] = [
pub static LANGUAGES: [LangInfo; 9] = [
LangInfo {
code: "en",
name: "English",
@@ -155,6 +168,17 @@ pub static LANGUAGES: [LangInfo; 8] = [
complete_menus: true,
catalog: OnceLock::new(),
},
LangInfo {
code: "pl",
name: "Polski",
english_name: "Polish",
cjk: false,
source: include_str!("pl.tsv"),
plural: plural_polish,
plural_forms: 3,
complete_menus: true,
catalog: OnceLock::new(),
},
];
impl LangInfo {
@@ -190,6 +214,7 @@ impl Lang {
pub const PT_BR: Lang = Lang(&LANGUAGES[5]);
pub const ES: Lang = Lang(&LANGUAGES[6]);
pub const CS: Lang = Lang(&LANGUAGES[7]);
pub const PL: Lang = Lang(&LANGUAGES[8]);
pub fn code(self) -> &'static str {
self.0.code
+4 -1
View File
@@ -5,7 +5,10 @@
//! 1. Add `xx.tsv` next to `ru.tsv` (copy its header; translate from the *meaning* of the
//! English text, clean-room).
//! 2. Add one row to [`LANGUAGES`] (code, native name, catalog, plural rule). For a complete
//! catalog, cover every UI literal and plural key; the tests enforce that claim.
//! catalog, cover every UI literal and plural key; the tests enforce that claim. A translation
//! that isn't finished yet starts with `complete_menus: false`: it still shows in
//! Preferences, strings without a row fall back to English, and the completeness tests
//! skip it until every row is done.
//!
//! That is all: the Preferences dropdown, the system-locale match and the catalog tests
//! (parse, placeholders, plural forms, coverage of every `tl!` literal) pick it up from the
+329
View File
@@ -0,0 +1,329 @@
# Polish (pl) 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 (Polish).
# @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 neutral Polish,
# using the terms Polish users of creative and system software know. The user is addressed
# informally ("twoje", "cię"), as in Polish system interfaces.
#
# 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), "GitHub",
# "Discord".
# 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 Narzędzia open source dla artystów, twórców i konstruktorów
Settings Ustawienia
Check for updates Sprawdź dostępność aktualizacji
Grid Siatka
Table Tabela
Show apps as cards Wyświetlaj listę jako karty
Show apps as a table Wyświetlaj listę jako tabelę
All Wszystkie
Updates Aktualizacje
Not installed Niezainstalowane
Search apps Wyszukaj aplikacje
Sort Sortowanie
Suite order Domyślna kolejność
Name Nazwa
Inspired by Wzorowane na
Status Status
Installed version Zainstalowana wersja
Latest version Najnowsza wersja
Newest release Najnowsze wydanie
Discord Discord
GitHub GitHub
Website Strona
Classic apps Klasyczne aplikacje
New apps Nowe aplikacje
Classic Klasyk
NEW NOWE
Install Instaluj
Update to {v} Zaktualizuj do {v}
Open Otwórz
Download for {os} Pobierz dla {os}
Download Pobierz
Coming soon Wkrótce
Checking… Sprawdzanie…
Not available Niedostępna
Offline Offline
Downloading {done} of {total} Pobrano {done} z {total}
Downloading… Pobieranie…
Reconnecting… Wznawianie połączenia…
Verifying… Weryfikowanie…
Installing, then restarting… Instalowanie a następnie ponowne uruchomienie…
Installing… Instalowanie…
Removing… Usuwanie…
Pause download Wstrzymaj pobieranie
Cancel download Anuluj pobieranie
Resume download Wznów pobieranie
Discard download Usuń pobrane
Release notes Informacje o wydaniu
Show in Finder Pokaż w Finderze
Show in Explorer Pokaż w Eksploratorze plików
Show in Files Pokaż w menedżerze plików
Reinstall Przeinstaluj
Uninstall… Odinstaluj…
All releases Wszystkie wydania
ArtCraft Launcher {version} is available. Dostępna jest nowa wersja ArtCraft Launcher ({version}).
Dismiss Zamknij
Update and restart Zaktualizuj i zrestartuj
A new version is available. Would you like to relaunch with the updated version? Nowa wersja jest dostępna. Czy chcesz uruchomić ponownie ze zaktualizowaną wersją
Later Później
What's new Co nowego
No notes were published with this release. Dla tego wydania nie opublikowano żadnych notatek.
Released {date} Wydano {date}
Open on GitHub Otwórz na GitHubie
Close Zamknij
ArtCraft Launcher {version} ArtCraft Launcher {version}
Only when I ask Tylko kiedy zapytam
Automatically download and install updates Automatycznie pobieraj i instaluj aktualizacje
Notify me when an installed app has an update Powiadom mnie o aktualizacjach dla zainstalowanych aplikacji
Include pre-releases (betas and release candidates) Uwzględnij wersje przedpremierowe (beta i wersje kandydujące)
Installing on Linux Instalowanie w systemie Linux
Install new apps as Instaluj nowe aplikacje jako
System package (recommended) Pakiet systemowy (zalecany)
Updates always use the format an app was installed with. Aktualizacje zawsze używają formatu, w jakim pierwotnie zainstalowano aplikację.
Running in the background Działanie w tle
Keep running in the tray when the window is closed Po zamknięciu okna działaj w zasobniku systemowym
Show the window on the current desktop Pokaż okno na bieżącym pulpicie
Start ArtCraft Launcher when I log in Uruchom ArtCraft Launcher po zalogowaniu
Community Społeczność
Questions, ideas and news from the ArtCraft team Pytania, pomysły i nowości od zespołu ArtCraft
Appearance Motyw
Quit ArtCraft Launcher Wyjdź z ArtCraft Launcher
Done Gotowe
Uninstall {name}? Odinstalować {name}?
The app will be moved to the Trash. Your documents and settings stay. Aplikacja zostanie przeniesiona do kosza. Twoje dokumenty i ustawienia pozostaną.
Windows will remove it like any installed program. Your documents and settings stay. Windows usunie to jak normalny zainstalowany program. Twoje dokumenty i ustawienia pozostaną.
Your system's package manager will remove it, and may ask for your password. Your documents and settings stay. Twój systemowy menedżer pakietów usunie aplikację, może też zapytać cię o hasło. Twoje dokumenty i ustawienia pozostaną.
The app will be removed. Your documents and settings stay. Ta aplikacja zostanie usunięta. Twoje dokumenty i ustawienia pozostaną.
Uninstall Odinstaluj
Cancel Anuluj
Checked {ago} Ostatnio sprawdzono {ago}
just now przed chwilą
yesterday wczoraj
{month} {day}, {year} {day} {month} {year}
Automatically Automatycznie
Every Craft app Wszystkie aplikacje Craft
Apps on this computer Aplikacje na tym komputerze
Installed apps with a newer version Zainstalowane aplikacje z nowszą wersją
Apps you can install Aplikacje, które możesz zainstalować
Checked {ago} · using {sources} Ostatnio sprawdzono {ago} · przy użyciu {sources}
{a} and {b} {a} i {b}
Join the Discord Dołącz na Discorda
Learn more Więcej
Minimize Minimalizuj
Maximize Maksymalizuj
Restore Przywróć
New Nowe
OK OK
Open link Otwórz link
Update Aktualizacja
{name} icon Ikona {name}
Loading ArtCraft Launcher… Ładowanie ArtCraft Launcher…
Cancel update Anuluj aktualizację
Stop waiting; the update can be started again later Przerwij oczekiwanie; aktualizację można uruchomić ponownie później
Download again Pobierz ponownie
Install cancelled Anulowano instalację
Install cancelled. The download is kept for the next try. Anulowano instalację. Pobrany plik zostanie zachowany na następną próbę.
Update cancelled Anulowano aktualizację
Uninstall cancelled Anulowano odinstalowanie
Retry install Ponów instalację
Retry update to {version} Ponów aktualizację do {version}
The download is still here. It's checked again, then installed. Pobrany plik wciąż tu jest. Zostanie sprawdzony ponownie, a następnie zainstalowany.
Waiting for another app… Oczekiwanie na inną aplikację…
Waiting for other installs… Oczekiwanie na inne instalacje…
# Package formats offered for download (PackageKind::label)
Installer (.msi) Instalator (.msi)
Portable (.zip) Wersja przenośna (.zip)
Disk image (.dmg) Obraz dysku (.dmg)
Debian package (.deb) Pakiet Debiana (.deb)
RPM package (.rpm) Pakiet RPM (.rpm)
Flatpak bundle Pakiet Flatpak
Tarball (.tar.gz) Archiwum (.tar.gz)
# Contextual: each app's tagline, keyed by app id
photocraft Image editing Edycja zdjęć
vectorcraft Vector illustration Ilustracja wektorowa
filmcraft Video editing, color and sound Montaż wideo, kolor i dźwięk
lightcraft Photo library and raw development Biblioteka zdjęć i obróbka RAW
pdfcraft Reading, organizing and protecting PDFs Czytanie, porządkowanie i zabezpieczanie plików PDF
effectcraft Motion graphics and visual effects Grafika ruchoma i efekty wizualne
designcraft Page layout and publishing Skład stron i publikowanie
wordcraft Writing and document design Pisanie i projektowanie dokumentów
deckcraft Presentations and slide shows Prezentacje i pokazy slajdów
gridcraft Spreadsheets and data Arkusze kalkulacyjne i dane
cadcraft Computer-aided design and drafting Projektowanie i kreślenie wspomagane komputerowo
soundcraft Recording, editing and mixing audio Nagrywanie, edycja i miksowanie dźwięku
# Contextual: month abbreviations for date rendering
month Jan sty
month Feb lut
month Mar mar
month Apr kwi
month May maj
month Jun cze
month Jul lip
month Aug sie
month Sep wrz
month Oct paź
month Nov lis
month Dec gru
Not checked yet Jeszcze nie sprawdzono
GitHub's hourly limit was reached; full details return in {min} min Osiągnięto godzinowy limit GitHuba; pełne informacje wrócą za {min} min
GitHub limit hit; back in {min} min Limit GitHuba osiągnięty; powrót za {min} min
Installing for {platform} Instalowanie dla {platform}
Downloads for {platform} Do pobrania dla {platform}
No downloads for this system Brak pobrań dla tego systemu
Join the ArtCraft community on Discord Dołącz do społeczności ArtCraft na Discordzie
ArtCraft on {name} ArtCraft w serwisie {name}
Couldn't open {name}: {e} Nie udało się otworzyć {name}: {e}
Download cancelled Anulowano pobieranie
Available to install Dostępna do instalacji
Available Dostępna
Installed · up to date Zainstalowana · aktualna
Update available Dostępna aktualizacja
Installed Zainstalowana
installed zainstalowana
Not available here Niedostępna w tym systemie
Updating… Aktualizowanie…
Update paused Aktualizacja wstrzymana
Download paused Pobieranie wstrzymane
{version} installed → {latest} released {version} zainstalowana → {latest} wydana
Version {version} Wersja {version}
{installed} installed · latest {latest} {installed} zainstalowana · najnowsza {latest}
Installed · latest {latest} Zainstalowana · najnowsza {latest}
Version {version} installed Wersja {version} zainstalowana
Version {latest} Wersja {latest}
No release published yet Nie opublikowano jeszcze wydania
ArtCraft Launcher ArtCraft Launcher
App Aplikacja
Latest Najnowsza
Released Wydano
Auto Automatycznie
Language Język
Ascending Rosnąco
Descending Malejąco
Click to reverse the order Kliknij, aby odwrócić kolejność
Click to sort by this column Kliknij, aby posortować według tej kolumny
Like {name} Wzorowane na {name}
{done} of {total} {done} z {total}
Downloading Pobieranie
Verifying checksum… Weryfikowanie sumy kontrolnej…
Resume Wznów
{done} of {total} downloaded Pobrano {done} z {total}
{done} downloaded Pobrano {done}
Checking for updates… Sprawdzanie aktualizacji…
Downloads the new version, replaces this one and opens it again Pobiera nową wersję, zastępuje obecną i uruchamia ją ponownie.
Couldn't check for updates: {f} Nie udało się sprawdzić aktualizacji: {f}
Website (coming soon) Strona (wkrótce)
GitHub repository Repozytorium na GitHubie
ArtCraft ArtCraft
Update 1 app Zaktualizuj 1 aplikację
Update all ({count}) Zaktualizuj wszystkie ({count})
⊞ Grid ⊞ Siatka
☰ Table ☰ Tabela
🔍 Search apps 🔍 Wyszukaj aplikacje
{name} {version} {name} {version}
Match system Zgodnie z systemem
Dark Ciemny
Light Jasny
Full changelog Pełna lista zmian
link link
No apps match Brak pasujących aplikacji
Open ArtCraft Launcher to update. Otwórz ArtCraft Launcher, aby zaktualizować aplikacje.
{name} {v} is available Dostępna jest nowa wersja {name} {v}
{count} updates are available Dostępne aktualizacje: {count}
These notes weren't fetched (GitHub's request limit was reached). They're on the release page. Nie udało się pobrać tych notatek (osiągnięto limit zapytań GitHuba). Znajdziesz je na stronie wydania.
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. Gdy włączone, otwarcie launchera z paska menu przenosi jego okno na bieżący pulpit. Gdy wyłączone, macOS przełącza cię na pulpit, na którym było okno.
Couldn't change the login item: {e} Nie udało się zmienić elementu logowania: {e}
# Contextual: table column headers
column App Aplikacja
column Installed Zainstalowana
column Latest Najnowsza
column Released Data wydania
# Contextual: the filter tab names a group (the status pill says "Installed")
filter Installed Zainstalowane
# Plural forms for Polish (one|few|many)
@plural {n} app|{n} apps {n} aplikacja|{n} aplikacje|{n} aplikacji
@plural {n} min ago|{n} min ago {n} min temu|{n} min temu|{n} min temu
@plural {n} h ago|{n} h ago {n} godz. temu|{n} godz. temu|{n} godz. temu
@plural {n} day ago|{n} days ago {n} dzień temu|{n} dni temu|{n} dni temu
# Message templates (@msg): toasts from the backend. {placeholders} match any text.
@msg Couldn't open {name}: {e} Nie udało się otworzyć {name}: {e}
@msg Download cancelled Anulowano pobieranie
@msg {name} updated to {version} Zaktualizowano {name} do wersji {version}
@msg {name} installed: {version} Zainstalowano {name} w wersji {version}
@msg {name} updated to {version} (this release publishes no checksum, so only its size was checked) Zaktualizowano {name} do wersji {version} (to wydanie nie zawiera sumy kontrolnej, więc sprawdzono tylko rozmiar)
@msg {name} installed: {version} (this release publishes no checksum, so only its size was checked) Zainstalowano {name} w wersji {version} (to wydanie nie zawiera sumy kontrolnej, więc sprawdzono tylko rozmiar)
@msg {name} was removed Usunięto {name}
@msg {name} is running. Quit it, then try again. {name} jest uruchomiona. Zamknij ją i spróbuj ponownie.
# Command IDs for menu items (@id)
# Format: @id<TAB>command_id<TAB>translation
@id app.install Instaluj
@id app.update Zaktualizuj do {v}
@id app.open Otwórz
@id app.download Pobierz dla {os}
@id app.reveal Pokaż w Finderze
@id app.reinstall Przeinstaluj
@id app.uninstall Odinstaluj…
@id app.notes Informacje o wydaniu
@id app.website Strona
@id app.github Repozytorium na GitHubie
@id app.releases Wszystkie wydania
@id launcher.update Zaktualizuj i zrestartuj
@id launcher.download Pobierz
@id launcher.notes Co nowego
@id launcher.dismiss Zamknij
@id settings.open Ustawienia
@id settings.quit Wyjdź z ArtCraft Launcher
@id view.grid Siatka
@id view.table Tabela
@id filter.all Wszystkie
@id filter.installed Zainstalowane
@id filter.updates Aktualizacje
@id filter.notinstalled Niezainstalowane
@id sort.suite Domyślna kolejność
@id sort.name Nazwa
@id sort.inspired Wzorowane na
@id sort.status Status
@id sort.installed Zainstalowana wersja
@id sort.latest Najnowsza wersja
@id sort.released Najnowsze wydanie
@id help.discord Discord
@id help.github GitHub
@id help.website Strona
@id update.check Sprawdź dostępność aktualizacji
@id update.check_auto Automatycznie sprawdzaj dostępność aktualizacji
@id update.notify Powiadom mnie o aktualizacjach dla zainstalowanych aplikacji
@id update.prereleases Uwzględnij wersje przedpremierowe (beta i wersje kandydujące)
@id linux.preferred_kind Instaluj nowe aplikacje jako
@id tray.close_to_tray Po zamknięciu okna działaj w zasobniku systemowym
@id tray.show_on_current_desktop Pokaż okno na bieżącym pulpicie
@id tray.login_item Uruchom ArtCraft Launcher po zalogowaniu
@id appearance.theme Wygląd
# Tray (menu bar) menu and tooltip
Show ArtCraft Launcher Pokaż ArtCraft Launcher
Check for Updates Sprawdź dostępność aktualizacji
Visit getartcraft.com Odwiedź getartcraft.com
ArtCraft on GitHub ArtCraft na GitHubie
Join the ArtCraft Discord Dołącz do Discorda ArtCraft
@plural ArtCraft Launcher · {n} update available|ArtCraft Launcher · {n} updates available ArtCraft Launcher · {n} dostępna aktualizacja|ArtCraft Launcher · {n} dostępne aktualizacje|ArtCraft Launcher · {n} dostępnych aktualizacji
Can't render this file because it contains an unexpected character in line 11 and column 15.
+28 -1
View File
@@ -4,7 +4,7 @@
use std::collections::{BTreeSet, HashSet};
use super::catalog::{Catalog, unescape};
use super::lang::{plural_czech, plural_east_slavic};
use super::lang::{plural_czech, plural_east_slavic, plural_polish};
use super::{LANGUAGES, Lang, LangInfo, lang_from_tag, message, tr, trn};
/// Each catalog's rows as (line number, context, source, translation), split exactly as the
@@ -222,6 +222,25 @@ fn czech_plural_rule_works() {
assert_eq!(trn(Lang::CS, 5, "{n} app", "{n} apps"), "5 aplikací");
}
#[test]
fn polish_plural_rule_works() {
// Polish says "one" only for 1 itself: 21, 31 and 101 take "many", unlike Russian.
assert_eq!(plural_polish(1), 0);
for n in [2, 3, 4, 22, 23, 24, 102, 122] {
assert_eq!(plural_polish(n), 1, "{n}");
}
for n in [0, 5, 11, 12, 13, 14, 15, 21, 25, 31, 100, 101, 111, 112] {
assert_eq!(plural_polish(n), 2, "{n}");
}
assert_eq!(trn(Lang::PL, 1, "{n} app", "{n} apps"), "1 aplikacja");
assert_eq!(trn(Lang::PL, 3, "{n} app", "{n} apps"), "3 aplikacje");
assert_eq!(trn(Lang::PL, 22, "{n} app", "{n} apps"), "22 aplikacje");
assert_eq!(trn(Lang::PL, 5, "{n} app", "{n} apps"), "5 aplikacji");
assert_eq!(trn(Lang::PL, 21, "{n} app", "{n} apps"), "21 aplikacji");
// A key the Polish catalog doesn't carry falls back to the English forms.
assert_eq!(trn(Lang::PL, 3, "{n} unknown item", "{n} unknown items"), "3 unknown items");
}
#[test]
fn untranslated_plurals_fall_back_to_english_by_count() {
// Russian's rule says "one" for 21, but English says "other".
@@ -297,6 +316,14 @@ fn spanish_locales_and_plurals_work() {
assert_eq!(message(Lang::ES, "PhotoCraft updated to 0.5.0"), "PhotoCraft actualizada a 0.5.0");
}
#[test]
fn polish_locales_resolve() {
for tag in ["pl", "pl-PL", "pl_PL.UTF-8", "PL_pl"] {
assert_eq!(lang_from_tag(tag), Some(Lang::PL), "{tag}");
}
assert_eq!(super::detect::first_supported(["xx-XX", "pl-PL", "en-US"]), Some(Lang::PL));
}
#[test]
fn simplified_chinese_locales_respect_the_script() {
for tag in ["zh", "zh-CN", "zh_CN.UTF-8", "zh-SG", "zh-Hans", "zh-Hans-CN", "zh-Hans-HK", "ZH_cn"] {
+1 -1
View File
@@ -88,7 +88,7 @@ pub struct Settings {
pub sort_descending: bool,
pub theme: ThemeChoice,
/// Interface language: `auto` (follow the system) or a language code from the registry
/// (`en`, `ru`, `zh-CN`, `de`).
/// (`en`, `ru`, `zh-CN`, `de`, `fr`, `pt-BR`, `pl`).
pub language: String,
/// Closing the window keeps the launcher in the tray (menu bar) instead of quitting.
pub close_to_tray: bool,
+129
View File
@@ -0,0 +1,129 @@
//! The Polish 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 {}
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,
}],
}
}
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
}
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: "pl".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,
)
}
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 polish_ui_translates_labels_dates_and_messages() {
let mut h = harness(ViewMode::Grid);
h.run_steps(8);
snapshot(&mut h, "grid-pl-dark");
// Product names stay in English.
h.get_by_label("Launcher");
h.get_by_label("Craft App Suite");
h.get_by_label("Edycja zdjęć");
h.get_by_label("Zaktualizuj do 0.5.0");
h.get_by_label_contains("Weryfikowanie sumy kontrolnej…");
h.get_by_label_contains("Ostatnio sprawdzono 3 min temu · przy użyciu ArtCraft API");
h.get_by_label("Zaktualizowano LightCraft do wersji 0.5.0");
let mut table = harness(ViewMode::Table);
table.run_steps(8);
snapshot(&mut table, "table-pl-dark");
table.get_by_label("Zainstalowana");
assert!(table.query_all_by_label_contains("sie 2026").count() >= 1);
}