Internacionalización (i18n)
El frontend de CryoNova Labs está completamente internacionalizado, con soporte para español (idioma principal) e inglés. Utilizamos las herramientas de i18n nativas de Angular para implementar este soporte multiidioma.
Configuración básica
La configuración de internacionalización se define en el archivo angular.json
:
"i18n": {
"sourceLocale": "es",
"locales": {
"en": {
"translation": "src/locale/messages.en.xlf"
}
}
}
Marcado de textos para traducción
Utilizamos las directivas i18n
y los pipes translate
para marcar el contenido que debe ser traducido:
Textos en plantillas (HTML)
<!-- Atributo i18n para textos simples -->
<h1 i18n="@@homeTitle">Panel de control CryoNova</h1>
<!-- Para elementos con etiquetas HTML internas -->
<p i18n="@@welcomeMessage">
Bienvenido al <strong>sistema de control</strong> CryoNova
</p>
<!-- Para atributos -->
<img [alt]="'Logotipo CryoNova' | translate" src="assets/logo.svg">
<!-- Pluralización -->
<span i18n="@@qubitCount">
{qubits, plural,
=0 {No hay qubits activos}
=1 {Hay 1 qubit activo}
other {Hay {{qubits}} qubits activos}
}
</span>
Textos en código TypeScript
// messages.ts
export const messages = {
welcome: $localize`Bienvenido al sistema`,
error: $localize`Ha ocurrido un error`,
success: $localize`Operación completada con éxito`,
};
// component.ts
import { messages } from './messages';
@Component({ ... })
export class MyComponent {
showMessage() {
this.snackBar.open(messages.success, 'OK');
}
}
Extracción y traducción
Extracción de textos
Para extraer los textos marcados para traducción, utilizamos el comando:
ng extract-i18n --output-path src/locale
Esto genera un archivo messages.xlf
con todos los textos marcados para traducción.
Proceso de traducción
- Se copia el archivo
messages.xlf
amessages.en.xlf
- Se traduce utilizando herramientas como Crowdin o manualmente
- Se compila la aplicación para cada idioma
Ejemplo de entrada en el archivo XLIFF:
<trans-unit id="homeTitle" datatype="html">
<source>Panel de control CryoNova</source>
<target>CryoNova Control Panel</target>
<context-group purpose="location">
<context context-type="sourcefile">src/app/home/home.component.html</context>
<context context-type="linenumber">5</context>
</context-group>
</trans-unit>
Cambio de idioma en tiempo de ejecución
Aunque Angular recomienda generar una compilación separada para cada idioma, hemos implementado un sistema para cambiar el idioma en tiempo de ejecución:
// i18n.service.ts
@Injectable({
providedIn: 'root'
})
export class I18nService {
private translations: { [key: string]: { [key: string]: string } } = {};
private currentLang = 'es';
constructor(private http: HttpClient) {}
async loadTranslations(lang: string): Promise<void> {
if (this.translations[lang]) {
return;
}
try {
const translations = await this.http
.get<{ [key: string]: string }>(`/assets/i18n/${lang}.json`)
.toPromise();
this.translations[lang] = translations;
} catch (error) {
console.error(`No se pudieron cargar las traducciones para ${lang}`, error);
}
}
async setLanguage(lang: string): Promise<void> {
if (lang !== 'es' && lang !== 'en') {
lang = 'es'; // Fallback al idioma por defecto
}
await this.loadTranslations(lang);
this.currentLang = lang;
document.documentElement.lang = lang;
// Emitir evento para que los componentes se actualicen
this.languageChanged.next(lang);
}
translate(key: string): string {
const translations = this.translations[this.currentLang];
if (!translations) {
return key;
}
return translations[key] || key;
}
// Observable para notificar cambios de idioma
private languageChanged = new Subject<string>();
languageChanged$ = this.languageChanged.asObservable();
}
Fechas, números y monedas
Para formatear correctamente fechas, números y monedas según la configuración regional, utilizamos los pipes de Angular:
<!-- Fechas -->
<div>{{ fechaMedicion | date:'fullDate':undefined:locale }}</div>
<!-- Números -->
<div>{{ temperatura | number:'1.2-2':locale }}</div>
<!-- Monedas -->
<div>{{ costo | currency:'EUR':true:'1.2-2':locale }}</div>
En el componente correspondiente:
export class MedicionComponent {
fechaMedicion = new Date();
temperatura = 4.2;
costo = 120500.75;
locale: string;
constructor(private i18nService: I18nService) {
this.locale = this.i18nService.currentLang;
// Actualizar cuando cambie el idioma
this.i18nService.languageChanged$.subscribe(lang => {
this.locale = lang;
});
}
}
Flujo de trabajo de traducción
- Los desarrolladores marcan los textos para traducción con las directivas i18n
- El equipo de traducción recibe los archivos XLIFF actualizados
- Se completan las traducciones para cada idioma
- Se integran los archivos traducidos en el proyecto
- Se compila la aplicación para todos los idiomas soportados
Consideraciones adicionales
Buenas prácticas
- No concatenar cadenas de texto para frases completas
- Usar variables en lugar de orden de palabras fijo
- Incluir comentarios contextuales para los traductores
- Respetar los plurales y las reglas gramaticales de cada idioma
Pruebas
Realizamos pruebas específicas para verificar la correcta traducción de la interfaz:
- Pruebas visuales para detectar textos desbordados
- Revisión de traducciones en contexto
- Pruebas automatizadas que cambian el idioma y verifican elementos críticos
Rendimiento
Para minimizar el impacto en el rendimiento, utilizamos estrategias como:
- Carga asíncrona de archivos de traducción
- Caché de traducciones en localStorage
- Compilación AOT para cada idioma en producción