i18n
tauri-plugin-i18n-vsk#
Lee los catálogos de idioma de una carpeta y responde el texto de una clave en el idioma activo. Un composable de Vue mantiene la interfaz al día y cambia de idioma sin recargar la ventana.
- Crate:
tauri-plugin-i18n-vsk - Paquete JS:
@vasakgroup/tauri-plugin-i18n - Plataforma: cualquiera (Linux en las aplicaciones de VasakOS)
El crate se publica como tauri-plugin-i18n-vsk —el sufijo evita el nombre ya
tomado en crates.io—, pero el plugin se llama i18n: de ahí salen los permisos
(i18n:…) y los comandos que la interfaz invoca (plugin:i18n|…).
Instalación#
| |
| |
| |
init recibe el idioma inicial (Option<String>); con None arranca en en.
El plugin no lee el idioma de la sesión: eso lo decide la aplicación. Las de
VasakOS miran LC_ALL, LC_MESSAGES y LANG, toman la primera parte
(es_AR.UTF-8 → es) y pasan es como idioma inicial, con en como única
excepción.
Con init_with_path(idioma, Some(ruta)) se indica la carpeta de traducciones:
| |
Sin ruta, el plugin la busca solo: locales/ y src-tauri/locales/ bajo el
directorio de trabajo (sólo si el binario no está instalado), ../locales y
../../locales respecto del ejecutable, /usr/share/<nombre-del-binario>/locales
—el caso del binario instalado, que es el que antes fallaba— y la carpeta
Bundle/Resources/locales de macOS. Se queda con la primera que exista.
Catálogos de idioma#
Un archivo por idioma, nombrado con el idioma (es.yml, en.yml,
pt-BR.json); el idioma se lee del último punto del nombre, así que es.yml es
es. Se aceptan .yml, .yaml, .json y .toml.
El formato habitual es un catálogo anidado, todo de un idioma:
| |
Y las claves se piden aplanadas con puntos: vistas.inicio.titulo.
El otro formato reparte los idiomas dentro del mismo archivo y se declara con
_version: 2. En ese caso el nombre del archivo no importa:
| |
Las claves que falten en el disco siguen viniendo de las traducciones
empaquetadas dentro del binario: el cargador del plugin trae un
bundled_locales.rs vacío por omisión, y cada aplicación puede reemplazarlo por
el suyo antes de compilar. En las aplicaciones de VasakOS los textos vienen del
disco, y el catálogo del disco gana clave por clave, así que se puede corregir
una cadena en /usr/share sin recompilar.
Permisos#
i18n:default, en el archivo de capacidades:
| |
El permiso por omisión habilita los cinco comandos. También existen por
separado: i18n:allow-load-translations, i18n:allow-translate,
i18n:allow-get-locale, i18n:allow-set-locale,
i18n:allow-get-available-locales y sus deny- correspondientes.
Una aplicación que traduce en JavaScript —el uso normal— necesita cuatro:
i18n:allow-load-translations, i18n:allow-get-locale,
i18n:allow-set-locale y, sólo si llama a I18n.getAvailableLocales(),
i18n:allow-get-available-locales. i18n:allow-translate es para quien
pregunta el texto al backend en vez de traducirlo en el WebView.
API de JavaScript#
useI18n()#
El composable de Vue. Devuelve el estado y las funciones que usan los componentes:
t(clave): string— El texto de la clave en el idioma activo. Si la clave no está, devuelve la clave tal cual (views.home.title), que es lo que hace que alguien la agregue al catálogo.locale— El idioma activo, reactivo.setLocale(idioma): Promise<void>— Cambia el idioma.availableLocales— Los idiomas con catálogo cargado.isLoaded— Si los catálogos ya llegaron.reload(): Promise<void>— Vuelve a leer los catálogos del disco.
Se puede llamar desde fuera de un componente (main.ts, antes de montar), que
es el uso recomendado.
I18n.getInstance(): I18n#
La clase que guarda los catálogos y el idioma, en una sola instancia por ventana. Es el único lugar donde vive ese estado: el composable lo refleja, no lo copia.
load(): Promise<void>#
Carga los catálogos y se suscribe al cambio de idioma. Se puede llamar todas las veces que haga falta: la primera hace el trabajo y las demás esperan a esa misma.
reload(): Promise<void>#
Vuelve a pedir los catálogos aunque ya estén cargados. Sirve cuando los archivos de idioma cambiaron en el disco; no vuelve a suscribirse al cambio de idioma.
translate(clave): string#
El texto de la clave, leído del estado local —sin ir al backend—. Igual que
t(), devuelve la clave si no la encuentra.
bind(elemento, clave): void#
Escribe el texto en un elemento del DOM y lo deja anotado para volver a escribirlo cuando cambie el idioma.
subscribe(oyente): () => void#
Avisa cuando cambian los catálogos o el idioma. Devuelve cómo darse de baja; es lo que usa el composable para reflejar este estado.
destroy(): void#
Cancela la suscripción al cambio de idioma y olvida la carga, de modo que un
load() posterior la vuelva a hacer. Los oyentes registrados con subscribe()
se conservan.
I18n.setLocale(idioma): Promise<void>#
Cambia el idioma y avisa a la interfaz, incluso antes de la primera carga. Un idioma sin catálogo deja las claves crudas.
I18n.getLocale(): Promise<string>#
El idioma activo, preguntado al backend.
I18n.getAvailableLocales(): Promise<string[]>#
Todos los idiomas con catálogo.
Comandos Rust#
load_translations()— Todos los catálogos, como{ idioma: { clave: texto } }.translate(key)— El texto de la clave en el idioma activo;Nonesi no está.set_locale(locale)— Cambia el idioma y emite el aviso al frontend.get_locale()— El idioma activo.get_available_locales()— Los idiomas con catálogo.
Eventos#
| Evento | Cuándo se emite | Carga |
|---|---|---|
i18n:locale_changed | Cambia el idioma de la aplicación | El idioma nuevo (string) |
| |
El evento llega a todas las ventanas: un cambio de idioma pedido en una se ve en
las demás. La clase I18n ya lo escucha, así que una interfaz que use useI18n()
o subscribe() no necesita suscribirse por su cuenta.
Ejemplo#
Conviene cargar los catálogos antes de montar la aplicación, para que la primera pantalla no muestre las claves crudas:
| |
| |
Detalles de implementación#
- Un solo estado. La clase
I18nes un singleton y el composable se suscribe a ella. Cargar antes de montar deja los textos puestos para todos los componentes. - Una sola carga. Una aplicación con cincuenta componentes que usan
useI18n()hace una sola llamada al backend y una sola suscripción al evento de cambio de idioma, no cincuenta de cada una. - Una sola suscripción, reintentable. Si suscribirse al cambio de idioma falla, no queda marcado como hecho: la carga siguiente lo vuelve a intentar.
- Manda la última carga pedida, no la última que contesta: un
reload()encima de una carga lenta no deja los catálogos viejos, y un cambio de idioma en medio de una carga no se deshace. - Un catálogo roto no tumba nada. Un archivo ilegible, que no sea UTF-8 o mal formado se saltea y el resto se carga igual; la aplicación abre con las claves a la vista en lugar de quedarse sin ventana.
- Los arreglos no se guardan como texto: la clave falta y se ve cruda, en lugar
de aparecer un hueco. Un valor
nulles una cadena vacía a propósito, y los números y los booleanos se muestran como texto. - Si no se encuentra ninguna carpeta de catálogos, la aplicación abre igual y
las claves se ven crudas; el motivo se escribe en
stderr, con las rutas que se probaron. - Un binario en
/usr,/opto/snapse considera instalado: no mira las rutas relativas al directorio de trabajo, que es de quien lo lanzó y no de la aplicación. Sin esa guarda, una aplicación abierta desde el menú cargaba los catálogos de otra y mostraba toda la interfaz en crudo. - Con
RUST_I18N_DEBUG=1el cargador imprime en la salida decargoqué rutas y qué archivos leyó. - El comando
set_localeemite el aviso y el estado del plugin lo emite otra vez, así que un solo cambio de idioma dispara el evento dos veces. Recibir el mismo idioma dos veces no cambia nada.