Volver al Blog
Angular4 de julio de 202610 min de lectura

Apps bilingües en Canadá: Desafíos técnicos y de UX

DR

Doug Ribeiro

10 min de lectura

Trabajé de forma remota desde Montreal para un banco con sede en Toronto, donde el bilingüismo no era un feature request, era un requisito base. En ese trabajo, cada pantalla tenía que sostenerse igual de bien en inglés y en francés, no solo en términos de labels traducidos, sino en términos de compliance legal, formateo, accesibilidad, y calidad de UX. La experiencia en francés no puede ser un ciudadano de segunda clase del mismo codebase.

La mayoría de los tutoriales de i18n cubren la mecánica de los archivos de traducción. Este post cubre lo que esos tutoriales no cubren: los casos borde, las decisiones de UX que difieren entre idiomas, los requisitos legales específicos de aplicaciones bilingües canadienses, y los patrones de implementación en Angular que me han funcionado en producción.

La configuración de ngx-translate que realmente escala

ngx-translate es la librería de traducción a la que recurro en Angular, combinada con archivos de traducción lazy-loaded por feature module. Cargar todas las traducciones al inicio en un archivo JSON monolítico funciona para aplicaciones pequeñas pero se convierte en un problema de rendimiento a medida que el archivo de traducción crece. En una aplicación bancaria grande con cientos de pantallas, el archivo de traducción puede crecer a varios cientos de kilobytes.

Mi patrón es dividir las traducciones por feature module y cargarlas junto con el código lazy-loaded del módulo. Cada feature registra su propio namespace de traducción con un resolver en su ruta raíz, de modo que el router retiene la navegación hasta que llegan los strings y nadie ve claves crudas. No recurras a APP_INITIALIZER aquí: los app initializers corren una sola vez en el bootstrap, así que uno declarado en los providers de una ruta lazy nunca se dispara. Las traducciones raíz cubren elementos compartidos como navegación, labels comunes de botones y mensajes de error globales. Las traducciones de feature cubren contenido específico del dominio.

typescript
// Route resolver: the router waits for it before the feature renders
export function featureTranslations(feature: string): ResolveFn<boolean> {
  return () => {
    const translate = inject(TranslateService);
    const http = inject(HttpClient);
    return forkJoin([
      http.get<Record<string, unknown>>(`/assets/i18n/${feature}/en.json`),
      http.get<Record<string, unknown>>(`/assets/i18n/${feature}/fr.json`),
    ]).pipe(
      tap(([en, fr]) => {
        translate.setTranslation('en', en, true); // merge: true
        translate.setTranslation('fr', fr, true);
      }),
      map(() => true)
    );
  };
}

// accounts.routes.ts (lazy loaded via loadChildren)
export const ACCOUNTS_ROUTES: Routes = [
  {
    path: '',
    resolve: { i18n: featureTranslations('accounts') },
    children: [
      // feature routes
    ],
  },
];

// Translation key convention: feature.section.element
// accounts.dashboard.welcomeMessage
// accounts.transactions.emptyState

Formateo de fechas y números: No es solo una configuración de locale

El inglés canadiense y el francés canadiense coinciden en las fechas numéricas y se separan en las largas. Tanto en-CA como fr-CA llevan un patrón corto y-MM-dd en los datos CLDR que Angular incluye, así que DatePipe renderiza 2025-07-13 en ambos idiomas. La forma larga es donde divergen: July 13, 2025 en inglés, 13 juillet 2025 en francés. Eso convierte los datos de locale en el camino seguro y al formateo hecho a mano en el peligroso. En el momento en que alguien escribe su propio helper MM/DD, o imprime una fecha corta al estilo de Estados Unidos directamente de una respuesta de API, 07/06/2025 se lee como 6 de julio para quien espera MM/DD y como 7 de junio para quien espera DD/MM. En una fecha de vencimiento de préstamo o una fecha de pago, esa mala lectura tiene consecuencias financieras reales. Pasa cada fecha por DatePipe con el locale registrado y la ambigüedad nunca tiene oportunidad de aparecer.

El DatePipe built-in de Angular maneja el formateo consciente del locale correctamente cuando se configura apropiadamente, pero requiere que registres los datos de locale explícitamente:

typescript
// main.ts
import { registerLocaleData } from '@angular/common';
import localeFrCA from '@angular/common/locales/fr-CA';
import localeEnCA from '@angular/common/locales/en-CA';

registerLocaleData(localeFrCA, 'fr-CA');
registerLocaleData(localeEnCA, 'en-CA');

// In your app config. LOCALE_ID is resolved once, when the injector creates it,
// so this sets the startup locale; it does not follow a runtime language switch
{
  provide: LOCALE_ID,
  useFactory: (languageService: LanguageService) => languageService.currentLocaleId(),
  deps: [LanguageService],
}

// Usage in templates. Pass the current locale explicitly so a language switch
// re-formats without a reload (currentLocale tracks LanguageService)
// {{ transactionDate | date:'longDate':undefined:currentLocale }} → July 13, 2025 (en-CA)
// {{ transactionDate | date:'longDate':undefined:currentLocale }} → 13 juillet 2025 (fr-CA)

// Currency formatting
// {{ amount | currency:'CAD':'symbol-narrow':'1.2-2':currentLocale }}
// en-CA: $1,234.56 | fr-CA: 1 234,56 $

Nota la diferencia en la posición del símbolo de moneda. En francés canadiense, el símbolo de moneda va después del número con un espacio. Esto no es solo estilístico: es la convención establecida que los usuarios francófonos esperan. Equivocarse en esto se lee como amateur para un usuario bilingüe.

Compliance legal para interfaces bilingües en Canadá

Las instituciones federales canadienses están sujetas a la Ley de Idiomas Oficiales, que requiere que los servicios estén disponibles en ambos idiomas oficiales con igual calidad. Los bancos no son instituciones federales, pero la Ley sobre el uso del francés en las empresas privadas de jurisdicción federal (aprobada en 2023, aún no en vigor) dará a los consumidores de Quebec y de regiones con fuerte presencia francófona el derecho a ser atendidos en francés por empresas de jurisdicción federal, bancos incluidos. Para productos digitales, esto tiene implicaciones específicas que van más allá de tener un toggle de idioma.

Toda comunicación dirigida al usuario debe estar disponible en ambos idiomas simultáneamente o bajo demanda. Esto incluye: mensajes de error (incluyendo errores del servidor), divulgaciones legales y términos, emails de notificación, documentos PDF generados por la aplicación, y anuncios de accesibilidad hechos a lectores de pantalla. No es suficiente traducir la UI si el PDF generado por la UI está solo en inglés.

En la práctica, el contenido bilingüe tiene que mantenerse para cada tipo de contenido que toca un flujo. La regla que recomiendo, y esta es mi recomendación y no la descripción del proceso de ningún empleador: el contenido no está terminado hasta que ambas versiones de idioma estén completas, aplicado a nivel de workflow, no solo como convención. En el front-end, solicita contenido de la API con un parámetro de idioma y espera una respuesta completa en el idioma solicitado.

Expansión de texto: El problema oculto de layout

El texto en francés es más largo que su equivalente en inglés: alrededor de 20% a 30% en texto corrido, y a menudo mucho más en labels cortos. Este es un desafío bien documentado de i18n, pero sus implicaciones prácticas en el diseño de UI financiera están subestimadas. Un label que dice 'Account balance' en inglés se convierte en 'Solde du compte' en francés, longitud similar en este caso. Pero 'View transaction details' se convierte en 'Afficher les détails de la transaction', que es casi 60% más largo. En un dashboard compacto con espaciado ajustado de botones, esa diferencia de longitud causa problemas de layout.

InglésSubmit → 6 caracteresView details → 12 caracteresAccount settings → 16 caracteresTransaction history → 19 caract…Forgot your password? → 21 cara…Next payment due → 16 caracteresFrancés canadienseSoumettre → 9 caracteres (+50%)Afficher les détails → 20 carac…Paramètres du compte → 20 carac…Historique des transactions → 2…Mot de passe oublié? → 20 carac…Prochain paiement dû → 20 carac…VS

Mi enfoque de diseño para aplicaciones bilingües canadienses es diseñar en francés primero cuando trabajo en componentes con mucho texto. Si el layout funciona en francés, casi siempre funciona en inglés. Lo inverso no es verdad: un layout optimizado para inglés frecuentemente se romperá con texto en francés.

En la práctica, uso técnicas de CSS que manejan la expansión de texto con gracia: layouts basados en flex con overflow ellipsis para truncamiento donde sea apropiado, restricciones de min-width en elementos de botón en lugar de anchos fijos, y container queries en componentes de tarjeta para redistribuir contenido cuando la relación texto-contenedor se vuelve desfavorable.

Diferencias idiomáticas más allá de la traducción palabra por palabra

La traducción no es reemplazo palabra por palabra. El francés financiero en Canadá tiene convenciones terminológicas específicas que difieren tanto del francés general como del francés europeo. Yo no resuelvo eso con mi propio francés. Consulto la Office québécois de la langue française. Su Vitrine linguistique registra compte chèques como el término canadiense para una chequing account, mientras que compte courant suena a francés europeo para un usuario de Quebec. Usar el término regional incorrecto le señala a ese usuario que la interfaz no fue escrita para él.

Por eso pongo los pases de traducción financiera con un traductor profesional especializado en francés canadiense, seguidos de una revisión por un hablante nativo de francés de Quebec. Esa es la cadena que recomiendo, no un relato de cómo lo hace ningún empleador. La traducción automática, incluso la traducción automática de alta calidad, produce consistentemente modismos de francés europeo para terminología financiera. En un contexto regulatorio de Quebec, esto no es aceptable.

Las convenciones de tono y formalidad también difieren. Las interfaces financieras en inglés canadiense usan un lenguaje relativamente casual y directo: 'You owe' en lugar de 'Your outstanding balance is'. Las interfaces financieras en francés canadiense son más formales, y una formulación como 'Le montant dû est' queda más cerca del registro que usa la terminología financiera de la OQLF que una traducción literal de la versión casual en inglés. Esa es una decisión de tu traductor y de las fiches de la OQLF, no del desarrollador front-end. Es una decisión de estrategia de contenido que afecta toda la interfaz.

UX del toggle de idioma: Persistente, predecible, accesible

El toggle de idioma en sí es una decisión de diseño de UX que tiene más implicaciones de las que aparenta. Las convenciones que vale la pena seguir:

  • El toggle de idioma siempre debe mostrar el idioma AL QUE cambiarás, no el idioma en el que estás actualmente. Si la interfaz está en inglés, el toggle dice 'Français'. Si está en francés, dice 'English'. Este es el estándar del gobierno canadiense y la convención que los usuarios bilingües esperan.
  • La preferencia de idioma debe persistir entre sesiones cuando el usuario está autenticado. Almacenarla en un perfil de usuario del lado del servidor en lugar de browser storage asegura consistencia entre dispositivos.
  • Los cambios de idioma deben tomar efecto inmediatamente sin una recarga completa de página donde sea posible. En Angular, esto significa que el cambio de TranslateService se propaga reactivamente a través del árbol de componentes sin navegación.
  • El toggle de idioma debe ser alcanzable por teclado y debe ser anunciado correctamente por lectores de pantalla. No es un botón que realiza una acción en la página actual: cambia todo el idioma de la interfaz, que es un cambio de estado lo suficientemente significativo como para justificar un anuncio aria-live.
  • Coloca el toggle de idioma consistentemente en una ubicación predecible: en diseños inspirados en el gobierno canadiense, la esquina superior derecha del header es la convención establecida. No lo muevas al footer ni lo entierres en un menú de configuración.

Manejando contenido dinámico y contenido generado por usuarios

Los labels estáticos de UI son la parte fácil. El problema más difícil es el contenido generado por usuarios y los datos dinámicos. Si un usuario ingresa su dirección en francés, ¿la versión en inglés de la interfaz la muestra correctamente? Si un asesor financiero escribe una nota en inglés, y un cliente francófono la lee a través de la interfaz en francés, ¿qué pasa?

No hay una solución técnica limpia para esto. La respuesta honesta es que el contenido generado por usuarios es neutral en idioma: se almacena y muestra como se ingresó, sin traducción. Lo que puedes controlar son los elementos de UI circundantes y el etiquetado de ese contenido. También puedes, en contextos apropiados, proporcionar traducción automática como funcionalidad de asistencia con una indicación clara de que es traducción automática y no oficial.

Para contenido generado por el sistema, como mensajes de estado de la solicitud, notificaciones automatizadas y errores de validación, el contenido siempre debe estar disponible en ambos idiomas. Un patrón que funciona: almacenar el contenido del sistema, como los códigos de estado y los requisitos de documentos, con una descripción tanto en inglés como en francés, y dejar que la capa de presentación seleccione el idioma según la preferencia del usuario actual. Esto requiere que el modelo de datos del back-end sea diseñado con bilingüismo en mente desde el inicio, no retrofitteado después.

Planificando para RTL: Preparación para el futuro sin sobredimensionar

Canadá no tiene requisitos de idioma RTL de la misma manera que aplicaciones dirigidas a mercados árabes o hebreos. Sin embargo, si estás construyendo una librería de componentes para una gran institución financiera que opera internacionalmente, vale la pena tomar decisiones compatibles con RTL desde el inicio en lugar de retrofittearlas después.

La implicación práctica para apps canadienses EN/FR es usar propiedades CSS lógicas (margin-inline-start en lugar de margin-left, padding-inline-end en lugar de padding-right) en estilos de componentes compartidos, y asegurar que el atributo dir se establezca correctamente en el elemento html cuando cambia el idioma. Estos hábitos cuestan muy poco establecerlos y proveen opcionalidad significativa para internacionalización futura.

El bilingüismo en software canadiense no es un feature, es el producto mínimo viable para servir a la población del país.

Construir aplicaciones genuinamente bilingües, no solo traducidas, requiere tratar EN y FR como preocupaciones de diseño co-iguales desde el primer día de un proyecto. La implementación técnica es directa una vez que las bases de diseño y estrategia de contenido son correctas. El trabajo duro es organizacional: asegurar que cada miembro del equipo, desde el product owner hasta el developer de backend, entienda que el francés no es algo que se agrega después.

Referencias

i18nCanadaAngularUX/UIngx-translate