Browse / github.com/storytold/craft-launcher / patch
Add an Italian interface
Add an Italian interface it.tsv translates the whole interface, the tray menu and tooltip, each app's tagline, the package formats and the backend's install/update messages into Italian, and Settings > Language offers Italiano. Italian locales (it, it-IT, it-CH, it_IT.UTF-8...) select it through Auto. Italian has two plural forms (one|other), like English. Dates read "23 ott 2026", relative times "5 min fa" / "2 giorni fa". Product and app names stay in English. Written clean-room from the meaning of the English labels, not through another catalog. Adds locale, plural, date and message tests, a UI test (tests/itali
- Status
- open
- Score
- 0.00
- Adopters
- 0 other operators
- Author
- CosmoNetinfo (imported from pull request #825, open)
- Base
3829b32c3bb8a98e1dab781dc21c33e600091eb0- Size
- +617 −6 in 5 files
- Review
- pending
- Shared
- 2026-10-28
- Id
36697c24a47b2ac5fd5bfe59
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
@@ -92,12 +92,13 @@ 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-(Русский), Simplified Chinese (简体中文) or Spanish (Español). **Auto**, the default, follows the+(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`, `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`) selectnew file mode 100644@@ -0,0 +1,379 @@+# 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 (English and Italian).+# @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 Italian,+# using the terms Italian-speaking users of creative software know. The user is addressed with+# the informal "tu" where a sentence needs a pronoun; buttons use the infinitive or imperative.+#+# 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".+# 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 Strumenti open source per creativi, maker e sviluppatori+ Settings Impostazioni+ Check for updates Cerca aggiornamenti+ Grid Griglia+ Table Tabella+ Show apps as cards Mostra le app come schede+ Show apps as a table Mostra le app in una 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+ 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 in corso…+ Installing, then restarting… Installazione e riavvio…+ Installing… Installazione in corso…+ Removing… Rimozione in corso…+ Pause download Sospendi il download+ Cancel download Annulla il download+ Resume download Riprendi il download+ Discard download Scarta il download+ Release notes Note di rilascio+ Show in Finder Mostra nel Finder+ Show in Explorer Mostra in Esplora risorse+ Show in Files Mostra nel gestore di file+ Reinstall Reinstalla+ Uninstall… Disinstalla…+ All releases Tutti i rilasci+ ArtCraft Launcher {version} is available. È disponibile ArtCraft Launcher {version}.+ 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?+ A new version is available. Download it to replace this version? È disponibile una nuova versione. Vuoi scaricarla per sostituire questa?+ Later Più tardi+ What's new Novità+ No notes were published with this release. Per 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 su richiesta+ Automatically download and install updates Scarica e installa gli aggiornamenti automaticamente+ 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 prova (beta e release candidate)+ Installing on Linux Installazione su Linux+ Installing on Windows Installazione su Windows+ 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 sul desktop corrente+ 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 Fatto+ 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 tue impostazioni resteranno al loro posto.+ 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 tue impostazioni resteranno al loro posto.+ 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 tue impostazioni resteranno al loro posto.+ The app will be removed. Your documents and settings stay. L'app verrà rimossa. I tuoi documenti e le tue impostazioni resteranno al loro posto.+ Uninstall Disinstalla+ Cancel Annulla+ Checked {ago} Controllato {ago}+ just now adesso+ yesterday ieri+ {month} {day}, {year} {day} {month} {year}++ Automatically Automatica+ 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 il 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 aspettare; l'aggiornamento potrà essere riavviato 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 ricontrollato e poi installato.+ Waiting for another app… In attesa di un'altra app…+ Waiting for other installs… In attesa di altre installazioni…++# Package formats offered for download (PackageKind::label)+ Installer (.msi) Programma di installazione (.msi)+ Portable (.zip) Versione portatile (.zip)+ Disk image (.dmg) Immagine disco (.dmg)+ Debian package (.deb) Pacchetto Debian (.deb)+ RPM package (.rpm) Pacchetto RPM (.rpm)+ Arch package (.pkg.tar.zst) Pacchetto Arch (.pkg.tar.zst)+ Flatpak bundle Bundle Flatpak+ Tarball (.tar.gz) Archivio tar (.tar.gz)++# Contextual: each app's tagline, keyed by app id+photocraft Image editing Modifica delle immagini+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 Leggere, organizzare e proteggere i PDF+effectcraft Motion graphics and visual effects Motion graphics ed effetti visivi+designcraft Page layout and publishing Impaginazione e pubblicazione+wordcraft Writing and document design Scrittura e progettazione di documenti+deckcraft Presentations and slide shows Presentazioni e proiezioni di diapositive+gridcraft Spreadsheets and data Fogli di calcolo e dati+cadcraft Computer-aided design and drafting Progettazione e disegno assistiti dal computer+soundcraft Recording, editing and mixing audio Registrazione, modifica e missaggio audio+figcraft Interface design and prototyping Progettazione di interfacce e prototipazione++# Contextual: month abbreviations for date rendering+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 Il limite orario di GitHub è stato raggiunto; i dettagli completi torneranno tra {min} min+ GitHub limit hit; back in {min} min Limite di GitHub raggiunto; di nuovo disponibile 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 Installabile+ Available Disponibile+ Installed · up to date Installata · aggiornata+ Update available Aggiornamento disponibile+ Installed Installata+ installed installata+ Not available here Non disponibile qui+ Updating… Aggiornamento in corso…+ Update paused Aggiornamento sospeso+ Download paused Download sospeso+ {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 pubblicato+ ArtCraft Launcher ArtCraft Launcher+ App App+ Latest Ultima+ Released Rilascio+ Auto Auto+ Language Lingua+ Ascending Crescente+ Descending Decrescente+ Click to reverse the order Clicca per invertire l'ordine+ Click to sort by this column Clicca per ordinare per questa colonna+ Like {name} Come {name}+ {done} of {total} {done} di {total}+ Downloading Download in corso+ Verifying checksum… Verifica del checksum…+ Resume Riprendi+ {done} of {total} downloaded {done} di {total} scaricati+ {done} downloaded {done} scaricati+ Checking for updates… Ricerca degli 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 cercare gli aggiornamenti: {f}+ 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 Elenco 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 Ci sono {count} aggiornamenti disponibili+ These notes weren't fetched (GitHub's request limit was reached). They're on the release page. Queste note non sono state caricate (è stato raggiunto il limite di richieste di GitHub). Si trovano 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 attiva, aprendo il launcher dalla barra dei menu la sua finestra viene portata sul desktop in cui ti trovi. Se disattivata, macOS ti porta sul desktop in cui si trovava la finestra.+ Couldn't change the login item: {e} Impossibile modificare l'elemento di login: {e}++# Contextual: table column headers+column App App+column Installed Installata+column Latest Ultima+column Released Data+# Contextual: the filter tab names a group (the status pill says "Installed")+filter Installed Installate+ {name} details Dettagli di {name}++# Plural forms for Italian (one|other)+@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++# Message templates (@msg): toasts from the backend. {placeholders} match any text.+@msg Couldn't open {name}: {e} Impossibile aprire {name}: {e}+@msg Download cancelled Download annullato+@msg {name} updated to {version} {name} aggiornata a {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 a {version} (questo rilascio non pubblica un checksum, quindi è stata controllata 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 controllata solo la dimensione)+@msg {name} was removed {name} è stata rimossa+@msg {name} is running. Quit it, then try again. {name} è in esecuzione. Chiudila e riprova.+@msg {name} was installed from the {repo} repository or the AUR ({pkg}). Update it with {cmd} or sudo pacman -Syu {name} è stata installata dal repository {repo} o dall'AUR ({pkg}). Aggiornala con {cmd} o con sudo pacman -Syu+@msg {name} was installed from the {repo} repository or the AUR ({pkg}). Update it with your AUR helper (for example: yay -S {pkg}) or sudo pacman -Syu {name} è stata installata dal repository {repo} o dall'AUR ({pkg}). Aggiornala con il tuo helper AUR (per esempio: yay -S {pkg}) o con sudo pacman -Syu+@msg {name} was installed from the AUR ({pkg}). Update it with {cmd} {name} è stata installata dall'AUR ({pkg}). Aggiornala con {cmd}+@msg {name} was installed from the AUR ({pkg}). Update it with your AUR helper (for example: yay -S {pkg}) {name} è stata installata dall'AUR ({pkg}). Aggiornala con il tuo helper AUR (per esempio: yay -S {pkg})++# Command IDs for menu items (@id)+# Format: @id<TAB>command_id<TAB>translation+@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 Cerca aggiornamenti+@id update.check_auto Cerca gli aggiornamenti automaticamente+@id update.notify Avvisami quando un'app installata ha un aggiornamento+@id update.prereleases Includi le versioni di prova (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 sul desktop corrente+@id tray.login_item Avvia ArtCraft Launcher all'accesso+@id appearance.theme Aspetto++# Tray (menu bar) menu and tooltip+ Show ArtCraft Launcher Mostra ArtCraft Launcher+ Check for Updates Cerca aggiornamenti+ Visit getartcraft.com Visita getartcraft.com+ ArtCraft on GitHub ArtCraft su GitHub+ Join the ArtCraft Discord Unisciti al Discord di ArtCraft+ App details Dettagli dell'app+ Close (Esc) Chiudi (Esc)+ Features Funzionalità+ Latest release Ultimo rilascio+ Version Versione+ Platforms Piattaforme+ Releases Rilasci+ Enlarge picture Ingrandisci l'immagine+ Click to enlarge Clicca per ingrandire+ Previous picture (←) Immagine precedente (←)+ Next picture (→) Immagine successiva (→)+ Loading pictures… Caricamento delle immagini…+ Pictures couldn't be loaded. Reopen this page to try again. Impossibile caricare le immagini. Riapri questa pagina per riprovare.+@plural ArtCraft Launcher · {n} update available|ArtCraft Launcher · {n} updates available ArtCraft Launcher · {n} aggiornamento disponibile|ArtCraft Launcher · {n} aggiornamenti disponibili+ Install folder Cartella di installazione+ Default install folder Cartella di installazione predefinita+ System default Predefinita del sistema+ Browse… Sfoglia…+ Use default Usa quella predefinita+ New installs use this folder. Updates stay in their current location. Le nuove installazioni usano questa cartella. Gli aggiornamenti restano dove si trovano.+ Custom folders use AppImage on Linux. Le cartelle personalizzate usano AppImage su Linux.+ On Linux, custom folders use AppImage format. Su Linux le cartelle personalizzate usano il formato AppImage.+ The system manages install locations here. Qui le posizioni di installazione sono gestite dal sistema.+@msg couldn't use install folder {path}: {reason} impossibile usare la cartella di installazione {path}: {reason}+@msg this release has no AppImage for a custom install folder questo rilascio non include un AppImage per una cartella di installazione personalizzata+@msg custom install folders aren't supported on this system le cartelle di installazione personalizzate non sono supportate su questo sistema+@msg {path} already exists and wasn't installed by ArtCraft Launcher {path} esiste già e non è stato installato da ArtCraft Launcher+@msg couldn't move {path} to Trash ({reason}). Remove it in Finder instead. impossibile spostare {path} nel Cestino ({reason}). Rimuovilo dal Finder.+ Apps are placed inside this folder. Le app vengono collocate in questa cartella.+ Replace what's there? Sostituire il contenuto esistente?+ {path} already exists. Installing {name} will overwrite it. {path} esiste già. L'installazione di {name} lo sovrascriverà.+ Replace Sostituisci+ Recently updated Aggiornate di recente+ Name (A–Z) Nome (A–Z)+ Name (Z–A) Nome (Z–A)+ Frequently used Più usate+ Next app App successiva+ Previous app App precedente+ Show {n} more changes Mostra altre {n} modifiche+ Last updated: {date} Ultimo aggiornamento: {date}+column Version Versione@@ -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, Italian, Spanish and Hungarian: 0 = one, 1 = other; /// Russian: 0 = one, 1 = few, 2 = many; Czech: 0 = one, 1 = few, 2 = other; Polish: 0 = one only /// for 1, 1 = few, 2 = many; French: 0 = one for 0 and 1; a language whose nouns don't inflect /// for number: always 0).@@ -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.code@@ -306,6 +306,27 @@ fn german_counts_dates_and_messages_work() { assert_eq!(tr(Lang::DE, "Untranslated diagnostic"), "Untranslated diagnostic"); } +#[test]+fn italian_locales_resolve() {+ for tag in ["it", "it-IT", "it_IT.UTF-8", "it-CH", "it_CH.UTF-8", "IT_it"] {+ 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));+}++#[test]+fn italian_counts_dates_and_messages_work() {+ // Only 1 is singular in Italian, as in English; "app" doesn't change in the plural.+ assert_eq!(trn(Lang::IT, 1, "{n} app", "{n} apps"), "1 app");+ assert_eq!(trn(Lang::IT, 0, "{n} app", "{n} apps"), "0 app");+ assert_eq!(trn(Lang::IT, 21, "{n} day ago", "{n} days ago"), "21 giorni fa");+ assert_eq!(trn(Lang::IT, 1, "{n} day ago", "{n} days ago"), "1 giorno 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 a 0.5.0");+ assert_eq!(tr(Lang::IT, "Untranslated diagnostic"), "Untranslated diagnostic");+}+ #[test] fn hungarian_locales_and_plurals_work() { for tag in ["hu", "hu-HU", "hu_HU.UTF-8", "HU_hu"] {new file mode 100644@@ -0,0 +1,198 @@+//! 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`).++use std::cell::{Cell, RefCell};+use std::rc::Rc;++use artcraft_launcher_core::protocol::{Backend, Capabilities, Command, Event, JobPhase, LatestRelease, Origin, PausedDownload, 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>,+ sent: Rc<RefCell<Vec<Command>>>,+ manage: bool,+ clock: Rc<Cell<i64>>,+}++impl Backend for Fake {+ fn send(&mut self, command: Command) {+ self.sent.borrow_mut().push(command);+ }+ fn poll(&mut self) -> Vec<Event> {+ std::mem::take(&mut self.events)+ }+ fn capabilities(&self) -> Capabilities {+ Capabilities { manage_apps: self.manage, reveal_label: "Show in Finder", self_update: self.manage }+ }+ 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: "## What's Changed\n* Faster things by @a in https://github.com/storytold/x/pull/1".into(),+ 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,+ }],+ }+}++/// A realistic mix: updates, up-to-date apps, a download in progress, one app with no release.+fn events() -> Vec<Event> {+ let mut ev = Vec::new();+ let versions = [+ ("photocraft", "0.5.0"),+ ("vectorcraft", "0.7.0"),+ ("filmcraft", "0.4.0"),+ ("lightcraft", "0.4.0"),+ ("pdfcraft", "0.4.0"),+ ("effectcraft", "0.6.0"),+ ("designcraft", "0.4.0"),+ ("wordcraft", "0.3.0"),+ ("deckcraft", "0.3.0"),+ ("gridcraft", "0.3.0"),+ ("cadcraft", "0.3.0"),+ ];+ for (i, (id, v)) in versions.iter().enumerate() {+ ev.push(Event::Latest {+ app: (*id).into(),+ latest: LatestRelease::Found(release(id, v, NOW - 86_400 - i as i64 * 600)),+ from: Origin::ArtCraftApi,+ });+ }+ ev.push(Event::Latest { app: "soundcraft".into(), latest: LatestRelease::None, from: Origin::ArtCraftApi });+ let installed = [("photocraft", "0.4.1"), ("lightcraft", "0.4.0"), ("wordcraft", "0.3.0"), ("effectcraft", "0.5.0")];+ for app in &SUITE {+ let inst = installed.iter().find(|(id, _)| *id == app.id).map(|(_, v)| Installed {+ version: version::parse(v),+ source: InstallSource::MacApp,+ location: format!("/Applications/{}.app", app.name),+ package: None,+ });+ ev.push(Event::Detected { app: app.id.into(), installed: inst });+ }+ ev.push(Event::Progress { app: "filmcraft".into(), phase: JobPhase::Downloading { done: 48 << 20, total: 131 << 20 } });+ ev.push(Event::Paused { app: "deckcraft".into(), download: Some(PausedDownload { done: 30 << 20, total: 120 << 20 }) });+ ev.push(Event::Progress { app: "gridcraft".into(), phase: JobPhase::Reconnecting { done: 10 << 20, total: 90 << 20 } });+ ev.push(Event::CheckFinished { at: NOW - 180, source: Source::GitHubApi, rate_limited_until: None });+ ev+}++fn harness_language(+ view: ViewMode,+ theme: egui::Theme,+ manage: bool,+ language: &str,+) -> (Harness<'static, Option<LauncherUi>>, Rc<RefCell<Vec<Command>>>) {+ artcraft_launcher_ui_egui::i18n::set_test_lang(artcraft_launcher_ui_egui::i18n::Lang::EN);+ let sent = Rc::new(RefCell::new(Vec::new()));+ let sent2 = sent.clone();+ let mut backend = Some(Fake { events: events(), sent: sent2, manage, clock: Rc::new(Cell::new(NOW)) });+ let settings = Settings { view, notify: false, language: language.into(), ..Settings::default() };+ let harness = Harness::builder().with_size(egui::vec2(1180.0, 900.0)).with_theme(theme).build_ui_state(+ move |ui, state: &mut Option<LauncherUi>| {+ if state.is_none()+ && let Some(b) = backend.take()+ {+ let mut s = settings.clone();+ s.theme = if theme == egui::Theme::Dark {+ artcraft_launcher_ui_egui::theme::ThemeChoice::Dark+ } else {+ artcraft_launcher_ui_egui::theme::ThemeChoice::Light+ };+ *state = Some(LauncherUi::new(ui.ctx(), Box::new(b), s));+ }+ if let Some(l) = state.as_mut() {+ l.logic(&ui.ctx().clone(), &mut NoHost);+ l.ui(ui, &mut NoHost);+ }+ },+ None,+ );+ (harness, sent)+}++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_switches_persists_and_keeps_install_actions() {+ let (mut h, sent) = harness_language(ViewMode::Grid, egui::Theme::Dark, true, "en");+ h.run_steps(6);+ h.get_by_label("Settings").click();+ h.run_steps(3);+ h.get_by_value("English").click();+ h.run_steps(2);+ h.get_by_label("Italiano").click();+ h.run_steps(3);+ h.get_by_value("Italiano");+ h.get_by_label("Avvisami quando un'app installata ha un aggiornamento");+ snapshot(&mut h, "settings-it-dark");+ let settings = h.state().as_ref().unwrap().settings.clone();+ assert_eq!(settings.language, "it");+ let saved = serde_json::to_string(&settings).unwrap();+ let restored: Settings = serde_json::from_str(&saved).unwrap();+ assert_eq!(restored.language, "it");+ h.get_by_label("Fatto").click();+ h.run_steps(3);+ h.get_by_label("Modifica delle immagini");+ h.get_by_label_contains("Controllato 3 min fa · tramite ArtCraft API");+ h.get_by_label("Aggiorna a 0.5.0");+ // Product names stay in English.+ assert!(h.query_by_label("Launcher").is_none());+ snapshot(&mut h, "grid-it-dark");+ h.get_by_label("Aggiorna tutte (2)").click();+ h.run_steps(2);+ let installs: Vec<_> = sent.borrow().iter().filter_map(|c| if let Command::Install { app } = c { Some(app.clone()) } else { None }).collect();+ assert_eq!(installs, ["photocraft", "effectcraft"]);++ let (mut table, _) = harness_language(ViewMode::Table, egui::Theme::Light, true, &restored.language);+ table.run_steps(6);+ table.get_by_label("Data");+ assert!(table.query_all_by_label_contains(" ott 2026").count() >= 1);+ snapshot(&mut table, "table-it-light");+ let (mut web, _) = harness_language(ViewMode::Grid, egui::Theme::Dark, false, &restored.language);+ web.run_steps(6);+ assert!(web.query_all_by_label("Scarica per macOS").count() >= 3);+ for width in [720.0, 800.0, 1180.0] {+ web.set_size(egui::vec2(width, 900.0));+ web.run_steps(6);+ for label in ["Show apps as cards", "Show apps as a table", "Settings", "Check for updates"] {+ let rect = web.get_by_label(artcraft_launcher_ui_egui::i18n::t(label)).rect();+ assert!(rect.left() >= 0.0 && rect.right() <= width, "{width}: {label} {rect:?}");+ if let Some(text) = web.query_by_label("Craft App Suite") {+ assert!(!rect.intersects(text.rect()), "{width}: Craft App Suite overlaps {label} {rect:?}");+ }+ }+ }+}