Add Hungarian localization and fix responsive settings layout (#146)

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

* Add Hungarian localization and fix responsive settings layout

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

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

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

* hungarian test: count only the settings body's links, not the cards' Website links behind the modal

#198 gave every card a Website link, and Hungarian labels both Webhely.

* hungarian test: card links are 14px text, settings icons 24px; filter on that

---------

Co-authored-by: DzikStar <82441193+DzikStar@users.noreply.github.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:
FeriVOQ
2026-10-12 08:48:21 +02:00
committed by GitHub
parent 94d6480a11
commit 57105f8445
8 changed files with 639 additions and 41 deletions
+4 -4
View File
@@ -76,10 +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`,
`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.
means adding its row to every catalog marked `complete_menus` (the `*.tsv` files in
`crates/ui-egui/src/i18n/`), 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), 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
(Français), German (Deutsch), Hungarian (Magyar), 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, 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
+62 -24
View File
@@ -178,24 +178,71 @@ impl LauncherUi {
}
fn settings_dialog(&mut self, ui: &mut egui::Ui, host: &mut dyn Host) -> bool {
let t = Tokens::get(ui.ctx());
// Wide enough for the community buttons and every social icon on one row.
ui.set_width(520.0);
// Leave room for the frame (48 px) and a 16 px gutter on each side.
let viewport = ui.ctx().content_rect();
ui.set_width(520.0_f32.min((viewport.width() - 80.0).max(0.0)));
// The area remembers its previous size. Reset the available height so the scroll area
// measures against the viewport immediately, including on the first frame after a resize.
ui.set_max_height((viewport.height() - 80.0).max(0.0));
ui.spacing_mut().scroll = egui::style::ScrollStyle { foreground_color: true, ..egui::style::ScrollStyle::solid() };
let title_top = ui.cursor().top();
Self::title(ui, Some(crate::icons::artcraft_mark()), tl!("Settings"), &format!("ArtCraft Launcher {}", env!("CARGO_PKG_VERSION")));
let title_height = ui.cursor().top() - title_top;
let quit_width = widgets::button_width(ui, tl!("Quit ArtCraft Launcher"), 0.0);
let done_width = widgets::button_width(ui, tl!("Done"), 90.0);
let stack_actions = self.caps.manage_apps && quit_width + done_width + ui.spacing().item_spacing.x > ui.available_width();
let footer_height = if stack_actions { 2.0 * theme::CONTROL_H + ui.spacing().item_spacing.y } else { theme::CONTROL_H };
let body_height = (viewport.height() - 80.0 - title_height - footer_height - 24.0 - ui.spacing().item_spacing.y).max(0.0);
egui::ScrollArea::vertical().id_salt("settings-body").max_height(body_height).auto_shrink([false, true]).show(ui, |ui| {
self.settings_contents(ui, host);
});
ui.add_space(24.0);
let mut close = false;
let quit = |ui: &mut egui::Ui, host: &mut dyn Host| {
if self.caps.manage_apps && widgets::button(ui, tl!("Quit ArtCraft Launcher"), Kind::Secondary, 0.0).clicked() {
host.quit();
}
};
if stack_actions {
quit(ui, host);
ui.with_layout(egui::Layout::right_to_left(egui::Align::Center), |ui| {
close = widgets::button(ui, tl!("Done"), Kind::Primary, 90.0).clicked();
});
} else {
ui.horizontal(|ui| {
quit(ui, host);
ui.with_layout(egui::Layout::right_to_left(egui::Align::Center), |ui| {
close = widgets::button(ui, tl!("Done"), Kind::Primary, 90.0).clicked();
});
});
}
close
}
fn settings_contents(&mut self, ui: &mut egui::Ui, host: &mut dyn Host) {
let t = Tokens::get(ui.ctx());
let section = |ui: &mut egui::Ui, s: &str| {
ui.add_space(14.0);
widgets::hud(ui, s, t.faint);
ui.add_space(6.0);
};
let row = |ui: &mut egui::Ui, label: &str, add: &mut dyn FnMut(&mut egui::Ui)| {
ui.horizontal(|ui| {
let row = |ui: &mut egui::Ui, label: &str, control_width: f32, add: &mut dyn FnMut(&mut egui::Ui)| {
let font = egui::TextStyle::Body.resolve(ui.style());
let label_width = ui.painter().layout_no_wrap(label.to_string(), font, t.ink).size().x;
if label_width + control_width + ui.spacing().item_spacing.x > ui.available_width() {
ui.label(label);
ui.with_layout(egui::Layout::right_to_left(egui::Align::Center), |ui| add(ui));
});
add(ui);
} else {
ui.horizontal(|ui| {
ui.label(label);
ui.with_layout(egui::Layout::right_to_left(egui::Align::Center), |ui| add(ui));
});
}
};
let mut lang_code = self.settings.language.clone();
row(ui, tl!("Language"), &mut |ui| {
row(ui, tl!("Language"), 180.0, &mut |ui| {
egui::ComboBox::from_id_salt("language").selected_text(Settings::language_label(&lang_code)).width(180.0).show_ui(ui, |ui| {
ui.selectable_value(&mut lang_code, Settings::LANGUAGE_AUTO.to_string(), tl!("Auto"));
for lang in crate::i18n::Lang::all() {
@@ -216,7 +263,7 @@ impl LauncherUi {
section(ui, tl!("Updates"));
let mut every = self.settings.check_every;
row(ui, tl!("Check for updates"), &mut |ui| {
row(ui, tl!("Check for updates"), 150.0, &mut |ui| {
egui::ComboBox::from_id_salt("every").selected_text(every.label()).width(150.0).show_ui(ui, |ui| {
for e in CheckEvery::ALL {
ui.selectable_value(&mut every, e, e.label());
@@ -245,7 +292,7 @@ impl LauncherUi {
None
};
let mut choice = self.settings.preferred_kinds.first().copied().or(default_kind);
row(ui, tl!("Install new apps as"), &mut |ui| {
row(ui, tl!("Install new apps as"), 210.0, &mut |ui| {
let default_label = match default_kind {
Some(k) => crate::i18n::t(k.label()),
None => tl!("System package (recommended)"),
@@ -290,7 +337,7 @@ impl LauncherUi {
section(ui, tl!("Community"));
ui.label(RichText::new(tl!("Questions, ideas and news from the ArtCraft team")).color(t.muted));
ui.add_space(6.0);
ui.horizontal(|ui| {
ui.horizontal_wrapped(|ui| {
use artcraft_launcher_core::catalog::{DISCORD_URL, GITHUB_URL, WEBSITE_URL};
if widgets::button(ui, tl!("Join the Discord"), Kind::Primary, 0.0).on_hover_text(DISCORD_URL).clicked() {
ui.ctx().open_url(egui::OpenUrl::new_tab(DISCORD_URL));
@@ -301,7 +348,10 @@ impl LauncherUi {
if widgets::button(ui, tl!("GitHub"), Kind::Secondary, 0.0).on_hover_text(GITHUB_URL).clicked() {
ui.ctx().open_url(egui::OpenUrl::new_tab(GITHUB_URL));
}
ui.with_layout(egui::Layout::right_to_left(egui::Align::Center), |ui| {
// Wrap the social links as a group so their icons stay together.
let count = artcraft_launcher_core::catalog::SOCIAL.len() as f32;
let width = count * (16.0 + 8.0) + (count - 1.0).max(0.0) * 4.0;
ui.allocate_ui_with_layout(vec2(width, theme::CONTROL_H), egui::Layout::left_to_right(egui::Align::Center), |ui| {
ui.spacing_mut().item_spacing.x = 4.0;
widgets::social_icons(ui, 16.0);
});
@@ -313,17 +363,5 @@ impl LauncherUi {
widgets::cells(ui, &mut theme_choice, &ThemeChoice::ALL.map(|c| (c, Cell { label: c.label(), tip: c.label(), count: None })));
});
self.settings.theme = theme_choice;
ui.add_space(24.0);
let mut close = false;
ui.horizontal(|ui| {
if self.caps.manage_apps && widgets::button(ui, tl!("Quit ArtCraft Launcher"), Kind::Secondary, 0.0).clicked() {
host.quit();
}
ui.with_layout(egui::Layout::right_to_left(egui::Align::Center), |ui| {
close = widgets::button(ui, tl!("Done"), Kind::Primary, 90.0).clicked();
});
});
close
}
}
+328
View File
@@ -0,0 +1,328 @@
# Hungarian (hu) 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 and Hungarian).
# @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 Hungarian,
# using the terms Hungarian-speaking users of creative software know. The user is addressed with
# the informal "te", as in most current creative and community 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), "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 Nyílt forráskódú eszközök alkotóknak, barkácsolóknak és fejlesztőknek
Settings Beállítások
Check for updates Frissítések keresése
Grid Rács
Table Táblázat
Show apps as cards Alkalmazások megjelenítése kártyákként
Show apps as a table Alkalmazások megjelenítése táblázatként
All Összes
Updates Frissítések
Not installed Nincs telepítve
Search apps Alkalmazások keresése
Sort Rendezés
Suite order Alapértelmezett sorrend
Name Név
Inspired by Mintául szolgált
Status Állapot
Installed version Telepített verzió
Latest version Legújabb verzió
Newest release Legújabb kiadás
Discord Discord
GitHub GitHub
Website Webhely
Classic apps Eredeti alkalmazások
New apps Új alkalmazások
NEW ÚJ
Install Telepítés
Update to {v} Frissítés: {v}
Open Megnyitás
Download for {os} Letöltés {os} rendszerre
Download Letöltés
Coming soon Hamarosan
Checking… Ellenőrzés…
Not available Nem elérhető
Offline Nincs kapcsolat
Downloading {done} of {total} Letöltés: {done} / {total}
Downloading… Letöltés…
Reconnecting… Újracsatlakozás…
Verifying… Fájl ellenőrzése…
Installing, then restarting… Telepítés, majd újraindítás…
Installing… Telepítés…
Removing… Eltávolítás…
Pause download Letöltés szüneteltetése
Cancel download Letöltés megszakítása
Resume download Letöltés folytatása
Discard download Letöltött fájl törlése
Release notes Kiadási információk
Show in Finder Megjelenítés a Finderben
Show in Explorer Megjelenítés a Fájlkezelőben
Show in Files Megjelenítés a fájlkezelőben
Reinstall Újratelepítés
Uninstall… Eltávolítás…
All releases Minden kiadás
ArtCraft Launcher {version} is available. Elérhető az ArtCraft Launcher új verziója: {version}.
Dismiss Elrejtés
Update and restart Frissítés és újraindítás
A new version is available. Would you like to relaunch with the updated version? Új verzió érhető el. Újraindítod az alkalmazást a frissítéshez?
Later Később
What's new Újdonságok
No notes were published with this release. Ehhez a kiadáshoz nem tettek közzé kiadási információkat.
Released {date} Megjelent: {date}
Open on GitHub Megnyitás a GitHubon
Close Bezárás
ArtCraft Launcher {version} ArtCraft Launcher {version}
Only when I ask Csak kézi kereséskor
Automatically download and install updates Frissítések automatikus letöltése és telepítése
Notify me when an installed app has an update Értesítés a telepített alkalmazásokhoz elérhető frissítésekről
Include pre-releases (betas and release candidates) Előzetes kiadások engedélyezése (béta- és kiadásra jelölt verziók)
Installing on Linux Telepítés Linuxon
Install new apps as Új alkalmazások csomagformátuma
System package (recommended) Rendszercsomag (ajánlott)
Updates always use the format an app was installed with. A frissítések mindig az eredeti telepítés csomagformátumát használják.
Running in the background Futtatás a háttérben
Keep running in the tray when the window is closed Az ablak bezárásakor kerüljön a tálcára
Show the window on the current desktop Ablak megjelenítése az aktuális asztalon
Start ArtCraft Launcher when I log in ArtCraft Launcher indítása bejelentkezéskor
Community Közösség
Questions, ideas and news from the ArtCraft team Kérdések, ötletek és hírek az ArtCraft csapatától
Appearance Megjelenés
Quit ArtCraft Launcher Kilépés az ArtCraft Launcherből
Done Kész
Uninstall {name}? {name} eltávolítása?
The app will be moved to the Trash. Your documents and settings stay. Az alkalmazás a Kukába kerül. A dokumentumaid és beállításaid megmaradnak.
Windows will remove it like any installed program. Your documents and settings stay. A Windows úgy távolítja el az alkalmazást, mint bármely más telepített programot. A dokumentumaid és beállításaid megmaradnak.
Your system's package manager will remove it, and may ask for your password. Your documents and settings stay. Az alkalmazást a rendszer csomagkezelője távolítja el. Ehhez szükség lehet a jelszavadra. A dokumentumaid és beállításaid megmaradnak.
The app will be removed. Your documents and settings stay. Eltávolítjuk az alkalmazást. A dokumentumaid és beállításaid megmaradnak.
Uninstall Eltávolítás
Cancel Mégse
Checked {ago} Ellenőrizve: {ago}
just now épp most
yesterday tegnap
{month} {day}, {year} {year}. {month} {day}.
Automatically Automatikusan
Every Craft app Minden Craft-alkalmazás
Apps on this computer Alkalmazások ezen a számítógépen
Installed apps with a newer version Telepített alkalmazások, amelyekhez frissítés érhető el
Apps you can install Telepíthető alkalmazások
Checked {ago} · using {sources} Ellenőrizve: {ago} · {sources}
{a} and {b} {a} és {b}
Join the Discord Csatlakozás a Discordhoz
Learn more További információ
Minimize Kis méret
Maximize Teljes méret
Restore Előző méret
New Új
OK OK
Open link Hivatkozás megnyitása
Update Frissítés
{name} icon {name} ikonja
Loading ArtCraft Launcher… ArtCraft Launcher betöltése…
Cancel update Frissítés megszakítása
Stop waiting; the update can be started again later Megszakítja a várakozást. A frissítést később újra elindíthatod.
Download again Újbóli letöltés
Install cancelled Telepítés megszakítva
Install cancelled. The download is kept for the next try. Telepítés megszakítva. A letöltött fájl megmarad a következő próbálkozáshoz.
Update cancelled Frissítés megszakítva
Uninstall cancelled Eltávolítás megszakítva
Retry install Telepítés újrapróbálása
Retry update to {version} Frissítés újrapróbálása: {version}
The download is still here. It's checked again, then installed. A letöltött fájl megmaradt. Újra ellenőrizzük, majd telepítjük.
Waiting for another app… Várakozás egy másik alkalmazásra…
Waiting for other installs… Várakozás a többi telepítésre…
# Package formats offered for download (PackageKind::label)
Installer (.msi) Telepítő (.msi)
Portable (.zip) Hordozható (.zip)
Disk image (.dmg) Lemezkép (.dmg)
Debian package (.deb) Debian-csomag (.deb)
RPM package (.rpm) RPM-csomag (.rpm)
Flatpak bundle Flatpak-csomag
Tarball (.tar.gz) Tömörített archívum (.tar.gz)
# Contextual: each app's tagline, keyed by app id
photocraft Image editing Képszerkesztés
vectorcraft Vector illustration Vektoros illusztráció
filmcraft Video editing, color and sound Videószerkesztés, színkorrekció és hang
lightcraft Photo library and raw development Fotók rendszerezése és RAW-előhívás
pdfcraft Reading, organizing and protecting PDFs PDF-ek olvasása, rendezése és védelme
effectcraft Motion graphics and visual effects Mozgógrafika és vizuális effektek
designcraft Page layout and publishing Oldaltervezés és kiadványszerkesztés
wordcraft Writing and document design Írás és dokumentumtervezés
deckcraft Presentations and slide shows Prezentációk és diavetítések
gridcraft Spreadsheets and data Táblázatok és adatok
cadcraft Computer-aided design and drafting Számítógépes tervezés és műszaki rajz
soundcraft Recording, editing and mixing audio Hangrögzítés, hangszerkesztés és keverés
# Contextual: month abbreviations for date rendering
month Jan jan.
month Feb febr.
month Mar márc.
month Apr ápr.
month May máj.
month Jun jún.
month Jul júl.
month Aug aug.
month Sep szept.
month Oct okt.
month Nov nov.
month Dec dec.
Not checked yet Még nem történt ellenőrzés
GitHub's hourly limit was reached; full details return in {min} min Elértük a GitHub óránkénti lekérdezési korlátját; a részletes adatok {min} perc múlva lesznek újra elérhetők
GitHub limit hit; back in {min} min GitHub-korlát elérve; újrapróbálás {min} perc múlva
Installing for {platform} Telepítés {platform} rendszerre
Downloads for {platform} Letöltések {platform} rendszerre
No downloads for this system Nincsenek letöltések ehhez a rendszerhez
Join the ArtCraft community on Discord Csatlakozz az ArtCraft közösségéhez a Discordon
ArtCraft on {name} ArtCraft – {name}
Couldn't open {name}: {e} {name} nem nyitható meg: {e}
Download cancelled Letöltés megszakítva
Available to install Telepíthető
Available Elérhető
Installed · up to date Telepítve · naprakész
Update available Frissítés elérhető
Installed Telepítve
installed telepítve
Not available here Ezen a rendszeren nem érhető el
Updating… Frissítés…
Update paused Frissítés szüneteltetve
Download paused Letöltés szüneteltetve
{version} installed → {latest} released Telepítve: {version} → megjelent: {latest}
Version {version} Verzió: {version}
{installed} installed · latest {latest} Telepítve: {installed} · legújabb: {latest}
Installed · latest {latest} Telepítve · legújabb: {latest}
Version {version} installed Telepített verzió: {version}
Version {latest} Verzió: {latest}
No release published yet Még nem jelent meg kiadás
ArtCraft Launcher ArtCraft Launcher
App Alkalmazás
Latest Legújabb
Released Megjelent
Auto Automatikus
Language Nyelv
Ascending Növekvő
Descending Csökkenő
Click to reverse the order Kattints a sorrend megfordításához
Click to sort by this column Kattints az oszlop szerinti rendezéshez
Like {name} Mintául szolgált: {name}
{done} of {total} {done} / {total}
Downloading Letöltés folyamatban
Verifying checksum… Ellenőrzőösszeg ellenőrzése…
Resume Folytatás
{done} of {total} downloaded {done} / {total} letöltve
{done} downloaded {done} letöltve
Checking for updates… Frissítések keresése…
Downloads the new version, replaces this one and opens it again Letölti és telepíti az új verziót, majd újraindítja az alkalmazást
Couldn't check for updates: {f} Nem sikerült frissítéseket keresni: {f}
Website (coming soon) Webhely (hamarosan)
GitHub repository GitHub-tároló
ArtCraft ArtCraft
Update 1 app 1 alkalmazás frissítése
Update all ({count}) Összes frissítése ({count})
⊞ Grid ⊞ Rács
☰ Table ☰ Táblázat
🔍 Search apps 🔍 Alkalmazások keresése
{name} {version} {name} {version}
Match system Rendszerbeállítás szerint
Dark Sötét
Light Világos
Full changelog Teljes változásnapló
link hivatkozás
No apps match Nincs találat
Open ArtCraft Launcher to update. Nyisd meg az ArtCraft Launchert a frissítéshez.
{name} {v} is available Elérhető: {name} {v}
{count} updates are available {count} frissítés elérhető
These notes weren't fetched (GitHub's request limit was reached). They're on the release page. A kiadási információkat nem sikerült betölteni, mert elértük a GitHub lekérdezési korlátját. A kiadás oldalán megtalálod őket.
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. Ha be van kapcsolva, a menüsorból megnyitott ArtCraft Launcher ablaka az aktuális asztalon jelenik meg. Ha ki van kapcsolva, a macOS arra az asztalra vált, ahol az ablak található.
Couldn't change the login item: {e} Az automatikus indítás beállítását nem sikerült módosítani: {e}
# Contextual: table column headers
column App Alkalmazás
column Installed Telepített
column Latest Legújabb
column Released Dátum
# Contextual: the filter tab names a group (the status pill says "Installed")
filter Installed Telepítve
# Plural forms for Hungarian (one|other). After a number the noun stays singular.
@plural {n} app|{n} apps {n} alkalmazás|{n} alkalmazás
@plural {n} min ago|{n} min ago {n} perce|{n} perce
@plural {n} h ago|{n} h ago {n} órája|{n} órája
@plural {n} day ago|{n} days ago {n} napja|{n} napja
# Message templates (@msg): toasts from the backend. {placeholders} match any text.
@msg Couldn't open {name}: {e} {name} nem nyitható meg: {e}
@msg Download cancelled Letöltés megszakítva
@msg {name} updated to {version} {name} frissítve. Verzió: {version}
@msg {name} installed: {version} {name} telepítve: {version}
@msg {name} updated to {version} (this release publishes no checksum, so only its size was checked) {name} frissítve. Verzió: {version} (ehhez a kiadáshoz nem adtak meg ellenőrzőösszeget, ezért csak a fájl méretét ellenőriztük)
@msg {name} installed: {version} (this release publishes no checksum, so only its size was checked) {name} telepítve: {version} (ehhez a kiadáshoz nem adtak meg ellenőrzőösszeget, ezért csak a fájl méretét ellenőriztük)
@msg {name} was removed {name} eltávolítva
@msg {name} is running. Quit it, then try again. {name} fut. Zárd be, majd próbáld újra.
# Command IDs for menu items (@id)
# Format: @id<TAB>command_id<TAB>translation
@id app.install Telepítés
@id app.update Frissítés: {v}
@id app.open Megnyitás
@id app.download Letöltés {os} rendszerre
@id app.reveal Megjelenítés a Finderben
@id app.reinstall Újratelepítés
@id app.uninstall Eltávolítás…
@id app.notes Kiadási információk
@id app.website Webhely
@id app.github GitHub-tároló
@id app.releases Minden kiadás
@id launcher.update Frissítés és újraindítás
@id launcher.download Letöltés
@id launcher.notes Újdonságok
@id launcher.dismiss Elrejtés
@id settings.open Beállítások
@id settings.quit Kilépés az ArtCraft Launcherből
@id view.grid Rács
@id view.table Táblázat
@id filter.all Összes
@id filter.installed Telepítve
@id filter.updates Frissítések
@id filter.notinstalled Nincs telepítve
@id sort.suite Alapértelmezett sorrend
@id sort.name Név
@id sort.inspired Mintául szolgált
@id sort.status Állapot
@id sort.installed Telepített verzió
@id sort.latest Legújabb verzió
@id sort.released Legújabb kiadás
@id help.discord Discord
@id help.github GitHub
@id help.website Webhely
@id update.check Frissítések keresése
@id update.check_auto Frissítések automatikus keresése
@id update.notify Értesítés a telepített alkalmazásokhoz elérhető frissítésekről
@id update.prereleases Előzetes kiadások engedélyezése (béta- és kiadásra jelölt verziók)
@id linux.preferred_kind Új alkalmazások csomagformátuma
@id tray.close_to_tray Az ablak bezárásakor kerüljön a tálcára
@id tray.show_on_current_desktop Ablak megjelenítése az aktuális asztalon
@id tray.login_item ArtCraft Launcher indítása bejelentkezéskor
@id appearance.theme Megjelenés
# Tray (menu bar) menu and tooltip
Show ArtCraft Launcher ArtCraft Launcher megjelenítése
Check for Updates Frissítések keresése
Visit getartcraft.com getartcraft.com megnyitása
ArtCraft on GitHub ArtCraft a GitHubon
Join the ArtCraft Discord Csatlakozás az ArtCraft Discord-közösségéhez
@plural ArtCraft Launcher · {n} update available|ArtCraft Launcher · {n} updates available ArtCraft Launcher · {n} frissítés elérhető|ArtCraft Launcher · {n} frissítés elérhető
Can't render this file because it contains an unexpected character in line 11 and column 16.
+16 -4
View File
@@ -7,8 +7,8 @@ use super::detect::system_lang;
/// One supported UI language.
pub struct LangInfo {
/// Canonical BCP 47 code (`en`, `ru`, `zh-CN`, `de`, `fr`, `pt-BR`, `es`, `cs`, `pl`). Also the
/// `interfaceLanguage` value.
/// Canonical BCP 47 code (`en`, `ru`, `zh-CN`, `de`, `fr`, `pt-BR`, `es`, `cs`, `pl`, `hu`). Also
/// the `interfaceLanguage` value.
pub code: &'static str,
/// The language's name in itself, shown in the Preferences dropdown.
pub name: &'static str,
@@ -19,7 +19,7 @@ pub struct LangInfo {
pub cjk: bool,
/// 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:
/// 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; Chinese: always 0).
pub plural: fn(u64) -> usize,
@@ -79,7 +79,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; 9] = [
pub static LANGUAGES: [LangInfo; 10] = [
LangInfo {
code: "en",
name: "English",
@@ -179,6 +179,17 @@ pub static LANGUAGES: [LangInfo; 9] = [
complete_menus: true,
catalog: OnceLock::new(),
},
LangInfo {
code: "hu",
name: "Magyar",
english_name: "Hungarian",
cjk: false,
source: include_str!("hu.tsv"),
plural: plural_one_other,
plural_forms: 2,
complete_menus: true,
catalog: OnceLock::new(),
},
];
impl LangInfo {
@@ -215,6 +226,7 @@ impl Lang {
pub const ES: Lang = Lang(&LANGUAGES[6]);
pub const CS: Lang = Lang(&LANGUAGES[7]);
pub const PL: Lang = Lang(&LANGUAGES[8]);
pub const HU: Lang = Lang(&LANGUAGES[9]);
pub fn code(self) -> &'static str {
self.0.code
+18
View File
@@ -289,6 +289,24 @@ fn german_counts_dates_and_messages_work() {
assert_eq!(tr(Lang::DE, "Untranslated diagnostic"), "Untranslated diagnostic");
}
#[test]
fn hungarian_locales_and_plurals_work() {
for tag in ["hu", "hu-HU", "hu_HU.UTF-8", "HU_hu"] {
assert_eq!(lang_from_tag(tag), Some(Lang::HU), "{tag}");
}
assert_eq!(super::detect::first_supported(["xx-XX", "hu-HU", "en-US"]), Some(Lang::HU));
// After a number the noun stays singular; 1 is still the "one" form.
assert_eq!(trn(Lang::HU, 0, "{n} app", "{n} apps"), "0 alkalmazás");
assert_eq!(trn(Lang::HU, 1, "{n} app", "{n} apps"), "1 alkalmazás");
assert_eq!(trn(Lang::HU, 2, "{n} app", "{n} apps"), "2 alkalmazás");
assert_eq!(trn(Lang::HU, 21, "{n} day ago", "{n} days ago"), "21 napja");
assert_eq!(trn(Lang::HU, 1, "{n} day ago", "{n} days ago"), "1 napja");
assert_eq!(super::tr_ctx(Lang::HU, "month", "Mar"), "márc.");
assert_eq!(tr(Lang::HU, "{month} {day}, {year}"), "{year}. {month} {day}.");
assert_eq!(message(Lang::HU, "PhotoCraft updated to 0.5.0"), "PhotoCraft frissítve. Verzió: 0.5.0");
assert_eq!(tr(Lang::HU, "Untranslated diagnostic"), "Untranslated diagnostic");
}
#[test]
fn french_locales_and_plurals_work() {
for tag in ["fr", "fr-FR", "fr_CA.UTF-8", "fr-BE", "FR_ch"] {
+6
View File
@@ -300,6 +300,12 @@ pub enum Kind {
Danger,
}
/// The natural width of a button, including its horizontal padding.
pub fn button_width(ui: &Ui, text: &str, minimum: f32) -> f32 {
let galley = caps(ui, text, theme::HUD_SIZE, theme::mono_bold(), theme::BUTTON_SPACING, Tokens::get(ui.ctx()).ink);
(galley.size().x + 32.0).max(minimum)
}
/// A square button with a mono upper-case label, at least `width` wide.
pub fn button(ui: &mut Ui, text: &str, kind: Kind, width: f32) -> Response {
let t = Tokens::get(ui.ctx());
+195
View File
@@ -0,0 +1,195 @@
//! The Hungarian 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>>,
os: Os,
}
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: self.os, arch: Arch::Arm64 })
}
fn now(&self) -> i64 {
self.clock.get()
}
}
struct DesktopHost;
impl Host for DesktopHost {
fn has_tray(&self) -> bool {
true
}
fn login_item(&self) -> Option<bool> {
Some(false)
}
}
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>> {
harness_on(view, Os::MacOs)
}
fn harness_on(view: ViewMode, os: Os) -> 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)), os });
let settings =
Settings { view, notify: false, language: "hu".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 DesktopHost);
l.ui(ui, &mut DesktopHost);
}
},
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 hungarian_ui_translates_labels_dates_and_messages() {
let mut h = harness(ViewMode::Grid);
h.run_steps(8);
snapshot(&mut h, "grid-hu-dark");
// Product names stay in English.
h.get_by_label("Launcher");
h.get_by_label("Craft App Suite");
h.get_by_label("Képszerkesztés");
h.get_by_label("Frissítés: 0.5.0");
h.get_by_label_contains("Ellenőrzőösszeg ellenőrzése…");
h.get_by_label_contains("Ellenőrizve: 3 perce · ArtCraft API");
h.get_by_label("LightCraft frissítve. Verzió: 0.5.0");
let mut table = harness(ViewMode::Table);
table.run_steps(8);
snapshot(&mut table, "table-hu-dark");
table.get_by_label("Telepített");
assert!(table.query_all_by_label_contains("2026. aug.").count() >= 1);
check_settings_layout();
}
/// Checks the actual modal after resizing it while open, including Linux's extra controls.
fn check_settings_layout() {
let mut h = harness_on(ViewMode::Grid, Os::Linux);
h.run_steps(8);
h.get_by_label("Beállítások").click();
h.run_steps(8);
for (width, height) in [(1180.0, 900.0), (720.0, 500.0), (480.0, 500.0), (1180.0, 900.0)] {
h.set_size(egui::vec2(width, height));
h.run_steps(8);
let screen = egui::Rect::from_min_size(egui::Pos2::ZERO, egui::vec2(width, height));
let done = h.get_by_label("Kész").rect();
let quit = h.get_by_label("Kilépés az ArtCraft Launcherből").rect();
assert!(screen.contains_rect(done), "{width}x{height}: Done is clipped: {done:?}");
assert!(screen.contains_rect(quit), "{width}x{height}: Quit is clipped: {quit:?}");
assert!(!done.intersects(quit));
let title = h.get_all_by_label("Beállítások").last().unwrap().rect();
assert!(screen.contains_rect(title), "{width}x{height}: title is clipped: {title:?}");
h.get_by_label("Rendszerbeállítás szerint").scroll_to_me();
h.run_steps(8);
// The footer has the same services, and the cards behind the modal have Webhely and GitHub
// links as one 14px line of text; keep only the settings body's buttons (34px) and icons (24px).
let discord = h.get_by_label("Csatlakozás a Discordhoz").rect();
let theme = h.get_by_label("Rendszerbeállítás szerint").rect();
assert!(screen.contains_rect(discord), "community links must be reachable by scrolling");
assert!(theme.bottom() < done.top(), "appearance controls must scroll above the fixed actions");
let mut links = vec![discord];
for label in ["Webhely", "GitHub", "X", "Instagram", "LinkedIn", "YouTube", "TikTok"] {
let candidates: Vec<_> = h
.query_all_by_label(label)
.map(|n| n.rect())
.filter(|r| r.top() >= discord.top() - 1.0 && r.bottom() < theme.top() && r.height() > 20.0)
.collect();
assert_eq!(candidates.len(), 1, "{width}x{height}: settings link {label}: {candidates:?}");
links.extend(candidates);
}
for (i, r) in links.iter().enumerate() {
assert!(screen.contains_rect(*r), "{width}x{height}: link is clipped: {r:?}");
for other in links.iter().skip(i + 1) {
assert!(!r.intersects(*other), "{width}x{height}: links overlap: {r:?}, {other:?}");
}
}
snapshot(&mut h, &format!("settings-hu-{width}x{height}-bottom"));
h.get_by_label("Nyelv").scroll_to_me();
h.run_steps(8);
let language = h.get_by_label("Nyelv").rect();
assert!(screen.contains_rect(language), "the top of settings must remain reachable");
snapshot(&mut h, &format!("settings-hu-{width}x{height}-top"));
}
h.get_by_label("Kész").click();
h.run_steps(4);
assert!(h.query_by_label("Kész").is_none(), "Done must still close the resized dialog");
}