i18n: add Spanish (es) interface language (#196)

es.tsv translates the whole interface, the tray menu and tooltip, each
app's tagline and the backend's install/update messages, in neutral
international Spanish. Settings > Language offers Español; es, es-ES,
es-MX, es-419 and the like select it under Auto.

AI-assisted, native review welcome.

Models: Opus 5.5, Terra 5.6, DeepSeek V4 Flash

Co-authored-by: Diego Ospina <diegozpy@users.noreply.github.com>
This commit is contained in:
Diego Ospina 2026-10-12 00:54:00 -05:00 committed by GitHub
parent 96ed437966
commit c064fde960
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
5 changed files with 471 additions and 8 deletions

View File

@ -85,12 +85,13 @@ The web version shows the same catalog in the browser, with the latest download
## Interface languages ## Interface languages
The interface is in English, Brazilian Portuguese (Português (Brasil)), French (Français), German The interface is in English, Brazilian Portuguese (Português (Brasil)), French (Français), German
(Deutsch), Russian (Русский) or Simplified Chinese (简体中文). **Auto**, the default, follows the (Deutsch), Russian (Русский), Simplified Chinese (简体中文) or Spanish (Español). **Auto**, the
system or browser language; **Settings → Language** picks one and switches at once, and the choice default, follows the system or browser language; **Settings → Language** picks one and switches at
is kept between runs. German locales (`de-DE`, `de-AT`, `de-CH`, `de_DE.UTF-8`…) select German, once, and the choice is kept between runs. German locales (`de-DE`, `de-AT`, `de-CH`,
French ones (`fr-FR`, `fr-CA`…) French, and Portuguese ones (`pt-BR`, and `pt-PT` too, for now) `de_DE.UTF-8`…) select German, French ones (`fr-FR`, `fr-CA`…) French, Spanish ones (`es-ES`,
Brazilian Portuguese. Simplified Chinese locales (`zh-CN`, `zh-SG`, `zh-Hans`, and POSIX forms such `es-MX`, `es-419`…) Spanish, and Portuguese ones (`pt-BR`, and `pt-PT` too, for now) Brazilian
as `zh_CN.UTF-8`) select Chinese; Traditional Chinese ones don't. To try a language without changing 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 the setting, start the desktop app with `ARTCRAFT_LAUNCHER_LOCALE=pt-BR`, or add `?lang=ru` to the
web address. web address.

View File

@ -0,0 +1,307 @@
# Spanish (es) 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 Spanish).
# @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
# international Spanish for Spain and Latin America (the interface addresses the user as "tú";
# no regional words such as "ordenador").
#
# 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", "Finder", "makers".
# Trailing "…" (U+2026) must be preserved. Placeholders must appear in the translation:
# {ago}, {a}, {b}, {count}, {date}, {day}, {done}, {e}, {f}, {installed}, {latest}, {min},
# {month}, {name}, {n}, {os}, {platform}, {sources}, {total}, {version}, {v}, {year}.
Launcher Launcher
Craft App Suite Craft App Suite
Open Source Tools for Creatives, Makers, and Builders Herramientas de código abierto para creativos, makers y constructores
Settings Ajustes
Check for updates Buscar actualizaciones
Grid Cuadrícula
Table Tabla
Show apps as cards Mostrar las aplicaciones como tarjetas
Show apps as a table Mostrar las aplicaciones como tabla
All Todas
Updates Actualizaciones
Not installed No instaladas
Search apps Buscar aplicaciones
Sort Ordenar
Suite order Orden de la suite
Name Nombre
Inspired by Inspirada en
Status Estado
Installed version Versión instalada
Latest version Última versión
Newest release Publicación más reciente
Discord Discord
GitHub GitHub
Website Sitio web
Classic apps Aplicaciones clásicas
New apps Aplicaciones nuevas
NEW NUEVA
Install Instalar
Update to {v} Actualizar a {v}
Open Abrir
Download for {os} Descargar para {os}
Download Descargar
Coming soon Próximamente
Checking… Comprobando…
Not available No disponible
Offline Sin conexión
Downloading {done} of {total} Descargando {done} de {total}
Downloading… Descargando…
Reconnecting… Reconectando…
Verifying… Verificando…
Installing, then restarting… Instalando y reiniciando…
Installing… Instalando…
Removing… Quitando…
Pause download Pausar la descarga
Cancel download Cancelar la descarga
Resume download Reanudar la descarga
Discard download Descartar la descarga
Release notes Notas de la versión
Show in Finder Mostrar en el Finder
Show in Explorer Mostrar en el Explorador
Show in Files Mostrar en Archivos
Reinstall Reinstalar
Uninstall… Desinstalar…
All releases Todas las versiones
ArtCraft Launcher {version} is available. ArtCraft Launcher {version} está disponible.
Dismiss Descartar
Update and restart Actualizar y reiniciar
A new version is available. Would you like to relaunch with the updated version? Hay una versión nueva. ¿Quieres reiniciar con la versión actualizada?
Later Más tarde
What's new Novedades
No notes were published with this release. Esta versión no se publicó con notas.
Released {date} Publicada el {date}
Open on GitHub Abrir en GitHub
Close Cerrar
ArtCraft Launcher {version} ArtCraft Launcher {version}
Only when I ask Solo cuando lo pida
Automatically download and install updates Descargar e instalar actualizaciones automáticamente
Notify me when an installed app has an update Avisarme cuando una aplicación instalada tenga una actualización
Include pre-releases (betas and release candidates) Incluir versiones preliminares (betas y versiones candidatas)
Installing on Linux Instalación en Linux
Install new apps as Instalar las aplicaciones nuevas como
System package (recommended) Paquete del sistema (recomendado)
Updates always use the format an app was installed with. Las actualizaciones siempre usan el formato con el que se instaló cada aplicación.
Running in the background En segundo plano
Keep running in the tray when the window is closed Seguir abierto en la bandeja del sistema al cerrar la ventana
Show the window on the current desktop Mostrar la ventana en el escritorio actual
Start ArtCraft Launcher when I log in Abrir ArtCraft Launcher al iniciar sesión
Community Comunidad
Questions, ideas and news from the ArtCraft team Preguntas, ideas y noticias del equipo de ArtCraft
Appearance Apariencia
Quit ArtCraft Launcher Salir de ArtCraft Launcher
Done Listo
Uninstall {name}? ¿Desinstalar {name}?
The app will be moved to the Trash. Your documents and settings stay. La aplicación se moverá a la Papelera. Tus documentos y ajustes se conservan.
Windows will remove it like any installed program. Your documents and settings stay. Windows la quitará como cualquier programa instalado. Tus documentos y ajustes se conservan.
Your system's package manager will remove it, and may ask for your password. Your documents and settings stay. El gestor de paquetes del sistema la quitará y puede pedirte la contraseña. Tus documentos y ajustes se conservan.
The app will be removed. Your documents and settings stay. La aplicación se quitará. Tus documentos y ajustes se conservan.
Uninstall Desinstalar
Cancel Cancelar
Checked {ago} Comprobado {ago}
just now ahora mismo
yesterday ayer
{month} {day}, {year} {day} {month} {year}
Automatically Automáticamente
Every Craft app Todas las aplicaciones Craft
Apps on this computer Aplicaciones en este equipo
Installed apps with a newer version Aplicaciones instaladas con una versión más reciente
Apps you can install Aplicaciones que puedes instalar
Checked {ago} · using {sources} Comprobado {ago} · con {sources}
{a} and {b} {a} y {b}
Join the Discord Únete al Discord
Learn more Más información
Minimize Minimizar
Maximize Maximizar
Restore Restaurar
New Nueva
OK Aceptar
Open link Abrir enlace
Update Actualizar
{name} icon Icono de {name}
Loading ArtCraft Launcher… Cargando ArtCraft Launcher…
Cancel update Cancelar la actualización
Stop waiting; the update can be started again later Dejar de esperar; la actualización se puede volver a iniciar más tarde
Download again Volver a descargar
Install cancelled Instalación cancelada
Install cancelled. The download is kept for the next try. Instalación cancelada. La descarga se guarda para el próximo intento.
Update cancelled Actualización cancelada
Uninstall cancelled Desinstalación cancelada
Retry install Reintentar la instalación
Retry update to {version} Reintentar la actualización a {version}
The download is still here. It's checked again, then installed. La descarga sigue aquí. Se vuelve a comprobar y luego se instala.
Waiting for another app… Esperando a otra aplicación…
Waiting for other installs… Esperando a otras instalaciones…
Installer (.msi) Instalador (.msi)
Portable (.zip) Portátil (.zip)
Disk image (.dmg) Imagen de disco (.dmg)
Debian package (.deb) Paquete Debian (.deb)
RPM package (.rpm) Paquete RPM (.rpm)
Flatpak bundle Paquete Flatpak
Tarball (.tar.gz) Archivo tar (.tar.gz)
photocraft Image editing Edición de imágenes
vectorcraft Vector illustration Ilustración vectorial
filmcraft Video editing, color and sound Edición de video, color y sonido
lightcraft Photo library and raw development Fototeca y revelado RAW
pdfcraft Reading, organizing and protecting PDFs Leer, organizar y proteger PDF
effectcraft Motion graphics and visual effects Gráficos en movimiento y efectos visuales
designcraft Page layout and publishing Maquetación y publicación
wordcraft Writing and document design Escritura y diseño de documentos
deckcraft Presentations and slide shows Presentaciones y diapositivas
gridcraft Spreadsheets and data Hojas de cálculo y datos
cadcraft Computer-aided design and drafting CAD: diseño y dibujo técnico
soundcraft Recording, editing and mixing audio Grabación, edición y mezcla de audio
month Jan ene.
month Feb feb.
month Mar mar.
month Apr abr.
month May may.
month Jun jun.
month Jul jul.
month Aug ago.
month Sep sept.
month Oct oct.
month Nov nov.
month Dec dic.
Not checked yet Aún sin comprobar
GitHub's hourly limit was reached; full details return in {min} min Se alcanzó el límite por hora de GitHub; todos los detalles estarán disponibles de nuevo en {min} min
GitHub limit hit; back in {min} min Límite de GitHub alcanzado; disponible de nuevo en {min} min
Installing for {platform} Instalación para {platform}
Downloads for {platform} Descargas para {platform}
No downloads for this system No hay descargas para este sistema
Join the ArtCraft community on Discord Únete a la comunidad de ArtCraft en Discord
ArtCraft on {name} ArtCraft en {name}
Couldn't open {name}: {e} No se pudo abrir {name}: {e}
Download cancelled Descarga cancelada
Available to install Disponible para instalar
Available Disponible
Installed · up to date Instalada · al día
Update available Actualización disponible
Installed Instalada
installed instalada
Not available here No disponible aquí
Updating… Actualizando…
Update paused Actualización en pausa
Download paused Descarga en pausa
{version} installed → {latest} released {version} instalada → {latest} publicada
Version {version} Versión {version}
{installed} installed · latest {latest} {installed} instalada · última {latest}
Version {version} installed Versión {version} instalada
Version {latest} Versión {latest}
No release published yet Aún no hay ninguna versión publicada
ArtCraft Launcher ArtCraft Launcher
App Aplicación
Latest Última
Released Publicada
Auto Automático
Language Idioma
Ascending Ascendente
Descending Descendente
Click to reverse the order Haz clic para invertir el orden
Click to sort by this column Haz clic para ordenar por esta columna
Like {name} Al estilo de {name}
{done} of {total} {done} de {total}
Downloading Descargando
Verifying checksum… Verificando la suma de comprobación…
Resume Reanudar
{done} of {total} downloaded {done} de {total} descargados
{done} downloaded {done} descargados
Checking for updates… Buscando actualizaciones…
Downloads the new version, replaces this one and opens it again Descarga la versión nueva, reemplaza esta y vuelve a abrirla
Couldn't check for updates: {f} No se pudieron buscar actualizaciones: {f}
Website (coming soon) Sitio web (próximamente)
GitHub repository Repositorio en GitHub
ArtCraft ArtCraft
Update 1 app Actualizar 1 aplicación
Update all ({count}) Actualizar todas ({count})
⊞ Grid ⊞ Cuadrícula
☰ Table ☰ Tabla
🔍 Search apps 🔍 Buscar aplicaciones
{name} {version} {name} {version}
Match system Igual que el sistema
Dark Oscuro
Light Claro
Full changelog Registro de cambios completo
link enlace
No apps match Ninguna aplicación coincide
Open ArtCraft Launcher to update. Abre ArtCraft Launcher para actualizar.
{name} {v} is available {name} {v} está disponible
{count} updates are available Hay {count} actualizaciones disponibles
These notes weren't fetched (GitHub's request limit was reached). They're on the release page. Estas notas no se descargaron (se alcanzó el límite de solicitudes de GitHub). Están en la página de la versión.
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. Si está activado, abrir ArtCraft Launcher desde la barra de menús lleva su ventana al escritorio en el que estás. Si está desactivado, macOS te cambia al escritorio en el que estaba la ventana.
Couldn't change the login item: {e} No se pudo cambiar el elemento de inicio de sesión: {e}
column App Aplicación
column Installed Instalada
column Latest Última
column Released Publicada
filter Installed Instaladas
@plural {n} app|{n} apps {n} aplicación|{n} aplicaciones
@plural {n} min ago|{n} min ago hace {n} min|hace {n} min
@plural {n} h ago|{n} h ago hace {n} h|hace {n} h
@plural {n} day ago|{n} days ago hace {n} día|hace {n} días
@msg Couldn't open {name}: {e} No se pudo abrir {name}: {e}
@msg Download cancelled Descarga cancelada
@msg {name} updated to {version} {name} actualizada a {version}
@msg {name} installed: {version} {name} instalada: {version}
@msg {name} updated to {version} (this release publishes no checksum, so only its size was checked) {name} actualizada a {version} (esta versión no publica la suma de comprobación, así que solo se comprobó su tamaño)
@msg {name} installed: {version} (this release publishes no checksum, so only its size was checked) {name} instalada: {version} (esta versión no publica la suma de comprobación, así que solo se comprobó su tamaño)
@msg {name} was removed Se quitó {name}
@msg {name} is running. Quit it, then try again. {name} está abierta. Ciérrala y vuelve a intentarlo.
@id app.install Instalar
@id app.update Actualizar a {v}
@id app.open Abrir
@id app.download Descargar para {os}
@id app.reveal Mostrar en el Finder
@id app.reinstall Reinstalar
@id app.uninstall Desinstalar…
@id app.notes Notas de la versión
@id app.website Sitio web
@id app.github Repositorio en GitHub
@id app.releases Todas las versiones
@id launcher.update Actualizar y reiniciar
@id launcher.download Descargar
@id launcher.notes Novedades
@id launcher.dismiss Descartar
@id settings.open Ajustes
@id settings.quit Salir de ArtCraft Launcher
@id view.grid Cuadrícula
@id view.table Tabla
@id filter.all Todas
@id filter.installed Instaladas
@id filter.updates Actualizaciones
@id filter.notinstalled No instaladas
@id sort.suite Orden de la suite
@id sort.name Nombre
@id sort.inspired Inspirada en
@id sort.status Estado
@id sort.installed Versión instalada
@id sort.latest Última versión
@id sort.released Publicación más reciente
@id help.discord Discord
@id help.github GitHub
@id help.website Sitio web
@id update.check Buscar actualizaciones
@id update.check_auto Buscar actualizaciones automáticamente
@id update.notify Avisarme cuando una aplicación instalada tenga una actualización
@id update.prereleases Incluir versiones preliminares (betas y versiones candidatas)
@id linux.preferred_kind Instalar las aplicaciones nuevas como
@id tray.close_to_tray Seguir abierto en la bandeja del sistema al cerrar la ventana
@id tray.show_on_current_desktop Mostrar la ventana en el escritorio actual
@id tray.login_item Abrir ArtCraft Launcher al iniciar sesión
@id appearance.theme Apariencia
Show ArtCraft Launcher Mostrar ArtCraft Launcher
Check for Updates Buscar actualizaciones
Visit getartcraft.com Visitar getartcraft.com
ArtCraft on GitHub ArtCraft en GitHub
Join the ArtCraft Discord Únete al Discord de ArtCraft
@plural ArtCraft Launcher · {n} update available|ArtCraft Launcher · {n} updates available ArtCraft Launcher · {n} actualización disponible|ArtCraft Launcher · {n} actualizaciones disponibles
Can't render this file because it contains an unexpected character in line 5 and column 48.

View File

@ -18,7 +18,7 @@ pub struct LangInfo {
pub cjk: bool, pub cjk: bool,
/// Catalog file contents (empty for the built-in English). /// Catalog file contents (empty for the built-in English).
pub source: &'static str, pub source: &'static str,
/// Plural form index for a count (English and German: 0 = one, 1 = other; Russian: 0 = one, /// Plural form index for a count (English, German and Spanish: 0 = one, 1 = other; Russian: 0 = one,
/// 1 = few, 2 = many; French: 0 = one for 0 and 1; Chinese: always 0). /// 1 = few, 2 = many; French: 0 = one for 0 and 1; Chinese: always 0).
pub plural: fn(u64) -> usize, pub plural: fn(u64) -> usize,
/// Number of plural forms the catalog's `@plural` rows carry (checked by tests). /// Number of plural forms the catalog's `@plural` rows carry (checked by tests).
@ -56,7 +56,7 @@ pub(super) fn plural_east_slavic(n: u64) -> usize {
} }
/// The registry. English first: it is the fallback and the source language. /// The registry. English first: it is the fallback and the source language.
pub static LANGUAGES: [LangInfo; 6] = [ pub static LANGUAGES: [LangInfo; 7] = [
LangInfo { LangInfo {
code: "en", code: "en",
name: "English", name: "English",
@ -123,6 +123,17 @@ pub static LANGUAGES: [LangInfo; 6] = [
complete_menus: true, complete_menus: true,
catalog: OnceLock::new(), catalog: OnceLock::new(),
}, },
LangInfo {
code: "es",
name: "Español",
english_name: "Spanish",
cjk: false,
source: include_str!("es.tsv"),
plural: plural_one_other,
plural_forms: 2,
complete_menus: true,
catalog: OnceLock::new(),
},
]; ];
impl LangInfo { impl LangInfo {
@ -156,6 +167,7 @@ impl Lang {
pub const DE: Lang = Lang(&LANGUAGES[3]); pub const DE: Lang = Lang(&LANGUAGES[3]);
pub const FR: Lang = Lang(&LANGUAGES[4]); pub const FR: Lang = Lang(&LANGUAGES[4]);
pub const PT_BR: Lang = Lang(&LANGUAGES[5]); pub const PT_BR: Lang = Lang(&LANGUAGES[5]);
pub const ES: Lang = Lang(&LANGUAGES[6]);
pub fn code(self) -> &'static str { pub fn code(self) -> &'static str {
self.0.code self.0.code

View File

@ -265,6 +265,20 @@ fn french_locales_and_plurals_work() {
assert_eq!(trn(Lang::FR, 3, "{n} day ago", "{n} days ago"), "il y a 3 jours"); assert_eq!(trn(Lang::FR, 3, "{n} day ago", "{n} days ago"), "il y a 3 jours");
} }
#[test]
fn spanish_locales_and_plurals_work() {
for tag in ["es", "es-ES", "es_MX.UTF-8", "es-419", "es-PA", "ES_ar"] {
assert_eq!(lang_from_tag(tag), Some(Lang::ES), "{tag}");
}
assert_eq!(super::detect::first_supported(["xx-XX", "es-PA", "en-US"]), Some(Lang::ES));
// Only 1 is singular in Spanish, as in English.
assert_eq!(trn(Lang::ES, 0, "{n} app", "{n} apps"), "0 aplicaciones");
assert_eq!(trn(Lang::ES, 1, "{n} app", "{n} apps"), "1 aplicación");
assert_eq!(trn(Lang::ES, 3, "{n} day ago", "{n} days ago"), "hace 3 días");
assert_eq!(tr(Lang::ES, "{month} {day}, {year}"), "{day} {month} {year}");
assert_eq!(message(Lang::ES, "PhotoCraft updated to 0.5.0"), "PhotoCraft actualizada a 0.5.0");
}
#[test] #[test]
fn simplified_chinese_locales_respect_the_script() { fn simplified_chinese_locales_respect_the_script() {
for tag in ["zh", "zh-CN", "zh_CN.UTF-8", "zh-SG", "zh-Hans", "zh-Hans-CN", "zh-Hans-HK", "ZH_cn"] { for tag in ["zh", "zh-CN", "zh_CN.UTF-8", "zh-SG", "zh-Hans", "zh-Hans-CN", "zh-Hans-HK", "ZH_cn"] {

View File

@ -0,0 +1,129 @@
//! The Spanish 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: "es".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 spanish_ui_translates_labels_dates_and_messages() {
let mut h = harness(ViewMode::Grid);
h.run_steps(8);
snapshot(&mut h, "grid-es-dark");
// Product names stay in English.
h.get_by_label("Launcher");
h.get_by_label("Craft App Suite");
h.get_by_label("Edición de imágenes");
h.get_by_label("Actualizar a 0.5.0");
h.get_by_label_contains("Verificando la suma de comprobación…");
h.get_by_label_contains("Comprobado hace 3 min · con ArtCraft API");
h.get_by_label("LightCraft actualizada a 0.5.0");
let mut table = harness(ViewMode::Table);
table.run_steps(8);
snapshot(&mut table, "table-es-dark");
table.get_by_label("Instalada");
assert!(table.query_all_by_label_contains("ago. 2026").count() >= 1);
}