Saltar al contenido principal

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

  1. Se copia el archivo messages.xlf a messages.en.xlf
  2. Se traduce utilizando herramientas como Crowdin o manualmente
  3. 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

Proceso de traducción
  1. Los desarrolladores marcan los textos para traducción con las directivas i18n
  2. El equipo de traducción recibe los archivos XLIFF actualizados
  3. Se completan las traducciones para cada idioma
  4. Se integran los archivos traducidos en el proyecto
  5. 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