collective

Browse / github.com/storytold/craft-launcher / patch

Add an Italian interface

Add an Italian interface - it.tsv: the full catalog (307 rows, every key of the most complete catalog), written clean-room from the English labels in standard Italian. The user is addressed as "tu"; "app" is feminine, so app statuses agree with it ("Installata · aggiornata"). - Registered as "Italiano": locales it, it-IT, it-CH, it_IT.UTF-8 and ?lang=it select it; one|other plurals ("1 giorno fa", "3 giorni fa"), dates as "8 ott 2026"; complete_menus = true so the completeness tests cover it. - tests/italian.rs: the Italian UI test, modeled on the French one, plus locale, plural, date and message checks in i18n/tests.

Status
open
Score
0.00
Adopters
0 other operators
Author
mccoy88f (imported from pull request #510, open)
Base
3fd2d56999fa00a7b90f159cb686a5fc64347e7e
Size
+489 −5 in 5 files
Review
pending
Shared
2026-10-28
Id
7878a41e612aab94ece4f3c6

How it went

No other operator has reported using it yet.

Diff

raw · README.md crates/ui-egui/src/i18n/it.tsv crates/ui-egui/src/i18n/lang.rs crates/ui-egui/src/i18n/tests.rs crates/ui-egui/tests/italian.rs

diff --git a/README.md b/README.md--- a/README.md+++ b/README.md@@ -85,12 +85,12 @@ 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), Hungarian (Magyar), Japanese (日本語), Polish (Polski), Russian+(Français), German (Deutsch), Hungarian (Magyar), Italian (Italiano), 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,+(`hu`, `hu-HU`, `hu_HU.UTF-8`…) Hungarian, Italian ones (`it-IT`, `it-CH`, `it_IT.UTF-8`…) Italian, 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`) selectdiff --git a/crates/ui-egui/src/i18n/it.tsv b/crates/ui-egui/src/i18n/it.tsvnew file mode 100644--- /dev/null+++ b/crates/ui-egui/src/i18n/it.tsv@@ -0,0 +1,327 @@+# Italian (it) 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 (only 1 is "one" in Italian; "app" doesn't change).+# @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 standard Italian.+# The interface addresses the user as "tu", as Italian software usually does. "App" is feminine+# ("l'app è installata"), so statuses about an app agree with it.+#+# 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".+# 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	Strumenti open source per creativi, maker e costruttori+	Settings	Impostazioni+	Check for updates	Controlla aggiornamenti+	Grid	Griglia+	Table	Tabella+	Show apps as cards	Mostra le app come schede+	Show apps as a table	Mostra le app come tabella+	All	Tutte+	Updates	Aggiornamenti+	Not installed	Non installate+	Search apps	Cerca app+	Sort	Ordina+	Suite order	Ordine della suite+	Name	Nome+	Inspired by	Ispirata a+	Status	Stato+	Installed version	Versione installata+	Latest version	Ultima versione+	Newest release	Rilascio più recente+	Discord	Discord+	GitHub	GitHub+	Website	Sito web+	Classic apps	App classiche+	New apps	Nuove app+	Classic	Classica+	NEW	NUOVA+	Install	Installa+	Update to {v}	Aggiorna a {v}+	Open	Apri+	Download for {os}	Scarica per {os}+	Download	Scarica+	Coming soon	In arrivo+	Checking…	Controllo in corso…+	Not available	Non disponibile+	Offline	Offline+	Downloading {done} of {total}	Download di {done} su {total}+	Downloading…	Download in corso…+	Reconnecting…	Riconnessione…+	Verifying…	Verifica…+	Installing, then restarting…	Installazione, poi riavvio…+	Installing…	Installazione…+	Removing…	Rimozione…+	Pause download	Metti in pausa il download+	Cancel download	Annulla il download+	Resume download	Riprendi il download+	Discard download	Elimina il download+	Release notes	Note di rilascio+	Show in Finder	Mostra nel Finder+	Show in Explorer	Mostra in Esplora file+	Show in Files	Mostra in File+	Reinstall	Reinstalla+	Uninstall…	Disinstalla…+	All releases	Tutti i rilasci+	ArtCraft Launcher {version} is available.	ArtCraft Launcher {version} è disponibile.+	Dismiss	Ignora+	Update and restart	Aggiorna e riavvia+	A new version is available. Would you like to relaunch with the updated version?	È disponibile una nuova versione. Vuoi riavviare con la versione aggiornata?+	Later	Più tardi+	What's new	Novità+	No notes were published with this release.	Con questo rilascio non sono state pubblicate note.+	Released {date}	Rilasciata il {date}+	Open on GitHub	Apri su GitHub+	Close	Chiudi+	ArtCraft Launcher {version}	ArtCraft Launcher {version}+	Only when I ask	Solo quando lo chiedo+	Automatically download and install updates	Scarica e installa automaticamente gli aggiornamenti+	Notify me when an installed app has an update	Avvisami quando un'app installata ha un aggiornamento+	Include pre-releases (betas and release candidates)	Includi le versioni di anteprima (beta e release candidate)+	Installing on Linux	Installazione su Linux+	Install new apps as	Installa le nuove app come+	System package (recommended)	Pacchetto di sistema (consigliato)+	Updates always use the format an app was installed with.	Gli aggiornamenti usano sempre il formato con cui l'app è stata installata.+	Running in the background	Esecuzione in background+	Keep running in the tray when the window is closed	Resta attivo nell'area di notifica quando la finestra viene chiusa+	Show the window on the current desktop	Mostra la finestra sulla scrivania attuale+	Start ArtCraft Launcher when I log in	Avvia ArtCraft Launcher all'accesso+	Community	Community+	Questions, ideas and news from the ArtCraft team	Domande, idee e novità dal team di ArtCraft+	Appearance	Aspetto+	Quit ArtCraft Launcher	Esci da ArtCraft Launcher+	Done	Fine+	Uninstall {name}?	Disinstallare {name}?+	The app will be moved to the Trash. Your documents and settings stay.	L'app verrà spostata nel Cestino. I tuoi documenti e le impostazioni restano.+	Windows will remove it like any installed program. Your documents and settings stay.	Windows la rimuoverà come qualsiasi programma installato. I tuoi documenti e le impostazioni restano.+	Your system's package manager will remove it, and may ask for your password. Your documents and settings stay.	Il gestore di pacchetti del sistema la rimuoverà e potrebbe chiederti la password. I tuoi documenti e le impostazioni restano.+	The app will be removed. Your documents and settings stay.	L'app verrà rimossa. I tuoi documenti e le impostazioni restano.+	Uninstall	Disinstalla+	Cancel	Annulla+	Checked {ago}	Controllato {ago}+	just now	adesso+	yesterday	ieri+	{month} {day}, {year}	{day} {month} {year}+	Automatically	Automaticamente+	Every Craft app	Tutte le app Craft+	Apps on this computer	App su questo computer+	Installed apps with a newer version	App installate con una versione più recente+	Apps you can install	App che puoi installare+	Hide App	Nascondi app+	Unhide App	Mostra app+	Hidden Apps	App nascoste+	Apps you hid	App che hai nascosto+	Checked {ago} · using {sources}	Controllato {ago} · tramite {sources}+	{a} and {b}	{a} e {b}+	Join the Discord	Unisciti al Discord+	Learn more	Scopri di più+	Minimize	Riduci a icona+	Maximize	Ingrandisci+	Restore	Ripristina+	New	Nuova+	OK	OK+	Open link	Apri link+	Update	Aggiorna+	{name} icon	Icona di {name}+	Loading ArtCraft Launcher…	Caricamento di ArtCraft Launcher…+	Cancel update	Annulla l'aggiornamento+	Stop waiting; the update can be started again later	Smetti di attendere; potrai riavviare l'aggiornamento più tardi+	Download again	Scarica di nuovo+	Install cancelled	Installazione annullata+	Install cancelled. The download is kept for the next try.	Installazione annullata. Il download viene conservato per il prossimo tentativo.+	Update cancelled	Aggiornamento annullato+	Uninstall cancelled	Disinstallazione annullata+	Retry install	Riprova l'installazione+	Retry update to {version}	Riprova l'aggiornamento a {version}+	The download is still here. It's checked again, then installed.	Il download è ancora qui. Viene verificato di nuovo, poi installato.+	Waiting for another app…	In attesa di un'altra app…+	Waiting for other installs…	In attesa di altre installazioni…+	Installer (.msi)	Programma di installazione (.msi)+	Portable (.zip)	Portatile (.zip)+	Disk image (.dmg)	Immagine disco (.dmg)+	Debian package (.deb)	Pacchetto Debian (.deb)+	RPM package (.rpm)	Pacchetto RPM (.rpm)+	Flatpak bundle	Pacchetto Flatpak+	Tarball (.tar.gz)	Archivio tar (.tar.gz)+photocraft	Image editing	Fotoritocco+vectorcraft	Vector illustration	Illustrazione vettoriale+filmcraft	Video editing, color and sound	Montaggio video, colore e audio+lightcraft	Photo library and raw development	Libreria fotografica e sviluppo raw+pdfcraft	Reading, organizing and protecting PDFs	Lettura, organizzazione e protezione dei PDF+effectcraft	Motion graphics and visual effects	Motion graphics ed effetti visivi+designcraft	Page layout and publishing	Impaginazione ed editoria+wordcraft	Writing and document design	Scrittura e impaginazione di documenti+deckcraft	Presentations and slide shows	Presentazioni e slideshow+gridcraft	Spreadsheets and data	Fogli di calcolo e dati+cadcraft	Computer-aided design and drafting	Progettazione e disegno tecnico assistiti dal computer+soundcraft	Recording, editing and mixing audio	Registrazione, editing e missaggio audio+figcraft	Interface design and prototyping	Design di interfacce e prototipazione+month	Jan	gen+month	Feb	feb+month	Mar	mar+month	Apr	apr+month	May	mag+month	Jun	giu+month	Jul	lug+month	Aug	ago+month	Sep	set+month	Oct	ott+month	Nov	nov+month	Dec	dic+	Not checked yet	Non ancora controllato+	GitHub's hourly limit was reached; full details return in {min} min	Raggiunto il limite orario di GitHub; i dettagli completi tornano tra {min} min+	GitHub limit hit; back in {min} min	Limite GitHub raggiunto; di nuovo tra {min} min+	Installing for {platform}	Installazione per {platform}+	Downloads for {platform}	Download per {platform}+	No downloads for this system	Nessun download per questo sistema+	Join the ArtCraft community on Discord	Unisciti alla community di ArtCraft su Discord+	ArtCraft on {name}	ArtCraft su {name}+	Couldn't open {name}: {e}	Impossibile aprire {name}: {e}+	Download cancelled	Download annullato+	Available to install	Disponibile da installare+	Available	Disponibile+	Installed · up to date	Installata · aggiornata+	Update available	Aggiornamento disponibile+	Installed	Installata+	installed	installata+	Not available here	Non disponibile qui+	Updating…	Aggiornamento…+	Update paused	Aggiornamento in pausa+	Download paused	Download in pausa+	{version} installed → {latest} released	{version} installata → {latest} rilasciata+	Version {version}	Versione {version}+	{installed} installed · latest {latest}	{installed} installata · ultima {latest}+	Installed · latest {latest}	Installata · ultima {latest}+	Version {version} installed	Versione {version} installata+	Version {latest}	Versione {latest}+	No release published yet	Nessun rilascio ancora pubblicato+	ArtCraft Launcher	ArtCraft Launcher+	App	App+	Latest	Ultima+	Released	Rilasciata+	Auto	Automatica+	Language	Lingua+	Ascending	Crescente+	Descending	Decrescente+	Click to reverse the order	Fai clic per invertire l'ordine+	Click to sort by this column	Fai clic per ordinare per questa colonna+	Like {name}	Come {name}+	{done} of {total}	{done} di {total}+	Downloading	Download+	Verifying checksum…	Verifica del checksum…+	Resume	Riprendi+	{done} of {total} downloaded	{done} di {total} scaricati+	{done} downloaded	{done} scaricati+	Checking for updates…	Ricerca aggiornamenti…+	Downloads the new version, replaces this one and opens it again	Scarica la nuova versione, sostituisce questa e la riapre+	Couldn't check for updates: {f}	Impossibile controllare gli aggiornamenti: {f}+	Website (coming soon)	Sito web (in arrivo)+	GitHub repository	Repository GitHub+	ArtCraft	ArtCraft+	Update 1 app	Aggiorna 1 app+	Update all ({count})	Aggiorna tutte ({count})+	⊞  Grid	⊞  Griglia+	☰  Table	☰  Tabella+	🔍  Search apps	🔍  Cerca app+	{name} {version}	{name} {version}+	Match system	Come il sistema+	Dark	Scuro+	Light	Chiaro+	Full changelog	Registro completo delle modifiche+	link	link+	No apps match	Nessuna app corrisponde+	Open ArtCraft Launcher to update.	Apri ArtCraft Launcher per aggiornare.+	{name} {v} is available	{name} {v} è disponibile+	{count} updates are available	Sono disponibili {count} aggiornamenti+	These notes weren't fetched (GitHub's request limit was reached). They're on the release page.	Queste note non sono state scaricate (raggiunto il limite di richieste di GitHub). Le trovi nella pagina del rilascio.+	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.	Se attivo, aprendo il launcher dalla barra dei menu la sua finestra si sposta sulla scrivania in cui ti trovi. Se disattivo, macOS ti porta sulla scrivania in cui si trovava la finestra.+	Couldn't change the login item: {e}	Impossibile modificare l'avvio all'accesso: {e}+column	App	App+column	Installed	Installata+column	Latest	Ultima+column	Released	Rilasciata+filter	Installed	Installate+	App details	Dettagli dell'app+	Close (Esc)	Chiudi (Esc)+	Features	Funzionalità+	Latest release	Ultimo rilascio+	Version	Versione+	Platforms	Piattaforme+	Releases	Rilasci+	Enlarge picture	Ingrandisci immagine+	Click to enlarge	Fai clic per ingrandire+	Previous picture (←)	Immagine precedente (←)+	Next picture (→)	Immagine successiva (→)+	Loading pictures…	Caricamento immagini…+	Pictures couldn't be loaded. Reopen this page to try again.	Impossibile caricare le immagini. Riapri questa pagina per riprovare.+	{name} details	Dettagli di {name}+@plural	{n} app|{n} apps	{n} app|{n} app+@plural	{n} min ago|{n} min ago	{n} min fa|{n} min fa+@plural	{n} h ago|{n} h ago	{n} h fa|{n} h fa+@plural	{n} day ago|{n} days ago	{n} giorno fa|{n} giorni fa+@msg	Couldn't open {name}: {e}	Impossibile aprire {name}: {e}+@msg	Download cancelled	Download annullato+@msg	{name} updated to {version}	{name} aggiornata alla versione {version}+@msg	{name} installed: {version}	{name} installata: {version}+@msg	{name} updated to {version} (this release publishes no checksum, so only its size was checked)	{name} aggiornata alla versione {version} (questo rilascio non pubblica un checksum, quindi è stata verificata solo la dimensione)+@msg	{name} installed: {version} (this release publishes no checksum, so only its size was checked)	{name} installata: {version} (questo rilascio non pubblica un checksum, quindi è stata verificata solo la dimensione)+@msg	{name} was removed	{name} è stata rimossa+@msg	{name} is running. Quit it, then try again.	{name} è in esecuzione. Chiudila, poi riprova.+@id	app.install	Installa+@id	app.update	Aggiorna a {v}+@id	app.open	Apri+@id	app.download	Scarica per {os}+@id	app.reveal	Mostra nel Finder+@id	app.reinstall	Reinstalla+@id	app.uninstall	Disinstalla…+@id	app.notes	Note di rilascio+@id	app.website	Sito web+@id	app.github	Repository GitHub+@id	app.releases	Tutti i rilasci+@id	launcher.update	Aggiorna e riavvia+@id	launcher.download	Scarica+@id	launcher.notes	Novità+@id	launcher.dismiss	Ignora+@id	settings.open	Impostazioni+@id	settings.quit	Esci da ArtCraft Launcher+@id	view.grid	Griglia+@id	view.table	Tabella+@id	filter.all	Tutte+@id	filter.installed	Installate+@id	filter.updates	Aggiornamenti+@id	filter.notinstalled	Non installate+@id	sort.suite	Ordine della suite+@id	sort.name	Nome+@id	sort.inspired	Ispirata a+@id	sort.status	Stato+@id	sort.installed	Versione installata+@id	sort.latest	Ultima versione+@id	sort.released	Rilascio più recente+@id	help.discord	Discord+@id	help.github	GitHub+@id	help.website	Sito web+@id	update.check	Controlla aggiornamenti+@id	update.check_auto	Controlla automaticamente gli aggiornamenti+@id	update.notify	Avvisami quando un'app installata ha un aggiornamento+@id	update.prereleases	Includi le versioni di anteprima (beta e release candidate)+@id	linux.preferred_kind	Installa le nuove app come+@id	tray.close_to_tray	Resta attivo nell'area di notifica quando la finestra viene chiusa+@id	tray.show_on_current_desktop	Mostra la finestra sulla scrivania attuale+@id	tray.login_item	Avvia ArtCraft Launcher all'accesso+@id	appearance.theme	Aspetto+	Show ArtCraft Launcher	Mostra ArtCraft Launcher+	Check for Updates	Controlla aggiornamenti+	Visit getartcraft.com	Visita getartcraft.com+	ArtCraft on GitHub	ArtCraft su GitHub+	Join the ArtCraft Discord	Unisciti al Discord di ArtCraft+@plural	ArtCraft Launcher · {n} update available|ArtCraft Launcher · {n} updates available	ArtCraft Launcher · {n} aggiornamento disponibile|ArtCraft Launcher · {n} aggiornamenti disponibilidiff --git a/crates/ui-egui/src/i18n/lang.rs b/crates/ui-egui/src/i18n/lang.rs--- a/crates/ui-egui/src/i18n/lang.rs+++ b/crates/ui-egui/src/i18n/lang.rs@@ -22,7 +22,7 @@ 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`, `zh-CN`, `de`, `fr`, `pt-BR`, `es`, `cs`, `pl`, `hu`, `ja`, `it`).     /// Also the `interfaceLanguage` value.     pub code: &'static str,     /// The language's name in itself, shown in the Preferences dropdown.@@ -33,7 +33,7 @@ pub struct LangInfo {     pub script: Option<Script>,     /// 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;+    /// Plural form index for a count (English, German, Spanish, Hungarian and Italian: 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).@@ -94,7 +94,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 +216,17 @@ pub static LANGUAGES: [LangInfo; 11] = [         complete_menus: true,         catalog: OnceLock::new(),     },+    LangInfo {+        code: "it",+        name: "Italiano",+        english_name: "Italian",+        script: None,+        source: include_str!("it.tsv"),+        plural: plural_one_other,+        plural_forms: 2,+        complete_menus: true,+        catalog: OnceLock::new(),+    }, ];  impl LangInfo {@@ -254,6 +265,7 @@ impl Lang {     pub const PL: Lang = Lang(&LANGUAGES[8]);     pub const HU: Lang = Lang(&LANGUAGES[9]);     pub const JA: Lang = Lang(&LANGUAGES[10]);+    pub const IT: Lang = Lang(&LANGUAGES[11]);      pub fn code(self) -> &'static str {         self.0.codediff --git a/crates/ui-egui/src/i18n/tests.rs b/crates/ui-egui/src/i18n/tests.rs--- a/crates/ui-egui/src/i18n/tests.rs+++ b/crates/ui-egui/src/i18n/tests.rs@@ -308,6 +308,22 @@ fn hungarian_locales_and_plurals_work() {     assert_eq!(tr(Lang::HU, "Untranslated diagnostic"), "Untranslated diagnostic"); } +#[test]+fn italian_locales_and_plurals_work() {+    for tag in ["it", "it-IT", "it_IT.UTF-8", "it-CH", "IT_sm"] {+        assert_eq!(lang_from_tag(tag), Some(Lang::IT), "{tag}");+    }+    assert_eq!(super::detect::first_supported(["xx-XX", "it-IT", "en-US"]), Some(Lang::IT));+    // Only 1 is singular, as in English; "app" itself doesn't change.+    assert_eq!(trn(Lang::IT, 0, "{n} app", "{n} apps"), "0 app");+    assert_eq!(trn(Lang::IT, 1, "{n} day ago", "{n} days ago"), "1 giorno fa");+    assert_eq!(trn(Lang::IT, 3, "{n} day ago", "{n} days ago"), "3 giorni fa");+    assert_eq!(super::tr_ctx(Lang::IT, "month", "May"), "mag");+    assert_eq!(tr(Lang::IT, "{month} {day}, {year}"), "{day} {month} {year}");+    assert_eq!(message(Lang::IT, "PhotoCraft updated to 0.5.0"), "PhotoCraft aggiornata alla versione 0.5.0");+    assert_eq!(tr(Lang::IT, "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"] {diff --git a/crates/ui-egui/tests/italian.rs b/crates/ui-egui/tests/italian.rsnew file mode 100644--- /dev/null+++ b/crates/ui-egui/tests/italian.rs@@ -0,0 +1,129 @@+//! The Italian 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: "it".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 italian_ui_translates_labels_dates_and_messages() {+    let mut h = harness(ViewMode::Grid);+    h.run_steps(8);+    snapshot(&mut h, "grid-it-dark");+    // Product names stay in English.+    h.get_by_label("Launcher");+    h.get_by_label("Craft App Suite");+    h.get_by_label("Fotoritocco");+    h.get_by_label("Aggiorna a 0.5.0");+    h.get_by_label_contains("Verifica del checksum…");+    h.get_by_label_contains("Controllato 3 min fa · tramite ArtCraft API");+    h.get_by_label("LightCraft aggiornata alla versione 0.5.0");++    let mut table = harness(ViewMode::Table);+    table.run_steps(8);+    snapshot(&mut table, "table-it-dark");+    table.get_by_label("Installata");+    assert!(table.query_all_by_label_contains("ago 2026").count() >= 1);+}