Trabalhei remotamente de Montreal para um banco sediado em Toronto, onde o bilinguismo não era um pedido de feature. Era um requisito básico. Nesse trabalho, cada tela precisava se sustentar igualmente bem em inglês e francês, não apenas em termos de rótulos traduzidos, mas em termos de conformidade legal, formatação, acessibilidade e qualidade de UX. A experiência em francês não pode ser cidadã de segunda classe do mesmo codebase.
A maioria dos tutoriais de i18n cobre a mecânica dos arquivos de tradução. Este post cobre o que esses tutoriais não cobrem: os casos extremos, as decisões de UX que diferem entre idiomas, os requisitos legais específicos para aplicações bilíngues canadenses e os padrões de implementação Angular que funcionaram para mim em produção.
A Configuração do ngx-translate Que Realmente Escala
ngx-translate é a biblioteca de tradução que eu escolho no Angular, combinada com arquivos de tradução carregados sob demanda por módulo de feature. Carregar todas as traduções antecipadamente em um único arquivo JSON monolítico funciona para aplicações pequenas, mas se torna um problema de performance conforme o arquivo de tradução cresce. Em uma aplicação bancária grande com centenas de telas, o arquivo de tradução pode chegar a várias centenas de kilobytes.
Meu padrão é dividir as traduções por módulo de feature e carregá-las junto com o código do módulo via lazy loading. Cada feature registra seu próprio namespace de tradução com um resolver na rota raiz dela, então o router segura a navegação até as strings chegarem e ninguém vê chaves cruas. Não recorra ao APP_INITIALIZER aqui: app initializers rodam uma única vez no bootstrap, então um declarado nos providers de uma rota lazy nunca dispara. As traduções raiz cobrem elementos compartilhados como navegação, rótulos comuns de botões e mensagens de erro globais. As traduções de feature cobrem conteúdo específico do domínio.
// 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.emptyStateFormatação de Data e Número: Não É Só Uma Configuração de Locale
O inglês canadense e o francês canadense concordam nas datas numéricas e divergem nas longas. Tanto en-CA quanto fr-CA carregam um padrão curto y-MM-dd nos dados CLDR que o Angular empacota, então o DatePipe renderiza 2025-07-13 nos dois idiomas. A forma longa é onde eles divergem: July 13, 2025 em inglês, 13 juillet 2025 em francês. Isso faz dos dados de locale o caminho seguro e da formatação feita na mão o caminho perigoso. No momento em que alguém escreve o próprio helper MM/DD, ou imprime uma data curta no estilo americano direto de uma resposta de API, 07/06/2025 é lido como 6 de julho por quem espera MM/DD e como 7 de junho por quem espera DD/MM. Em uma data de vencimento de empréstimo ou uma data de pagamento, essa leitura errada tem consequências financeiras reais. Passe toda data pelo DatePipe com o locale registrado e a ambiguidade nunca tem chance de aparecer.
O DatePipe nativo do Angular lida corretamente com formatação sensível ao locale quando configurado adequadamente, mas exige que você registre os dados de locale explicitamente:
// 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 $Observe a diferença na posição do símbolo da moeda. No francês canadense, o símbolo da moeda vai depois do número com um espaço. Isso não é apenas estilístico. É a convenção estabelecida que os usuários francófonos esperam. Errar isso passa uma impressão amadora para um usuário bilíngue.
Conformidade Legal para Interfaces Bilíngues no Canadá
Instituições federais canadenses estão sujeitas à Lei dos Idiomas Oficiais, que exige que os serviços estejam disponíveis em ambos os idiomas oficiais com qualidade igual. Bancos não são instituições federais, mas a Lei sobre o uso do francês nas empresas privadas sob jurisdição federal (aprovada em 2023, ainda não em vigor) vai dar aos consumidores do Quebec e de regiões com forte presença francófona o direito de serem atendidos em francês por empresas sob jurisdição federal, bancos incluídos. Para produtos digitais, isso tem implicações específicas que vão além de ter um botão de troca de idioma.
Toda comunicação voltada ao usuário deve estar disponível em ambos os idiomas simultaneamente ou sob demanda. Isso inclui: mensagens de erro (incluindo erros do servidor), divulgações legais e termos, e-mails de notificação, documentos PDF gerados pela aplicação e anúncios de acessibilidade feitos para leitores de tela. Não basta traduzir a interface se o PDF gerado pela interface está apenas em inglês.
Na prática, o conteúdo bilíngue precisa ser mantido para cada tipo de conteúdo que um fluxo toca. A regra que eu recomendo, e essa é a minha recomendação e não a descrição do processo de nenhum empregador: o conteúdo não está pronto até que ambas as versões de idioma estejam completas, e isso é aplicado no nível do workflow, não apenas como convenção. No front-end, solicite conteúdo da API com um parâmetro de idioma e espere uma resposta completa no idioma solicitado.
Expansão de Texto: O Problema de Layout Oculto
O texto em francês é mais longo do que seu equivalente em inglês: cerca de 20% a 30% em texto corrido, e muitas vezes bem mais em rótulos curtos. Esse é um desafio de i18n bem documentado, mas suas implicações práticas no design de UI financeira são subestimadas. Um rótulo que diz 'Account balance' em inglês se torna 'Solde du compte' em francês. Comprimento similar neste caso. Mas 'View transaction details' se torna 'Afficher les détails de la transaction', que é quase 60% mais longo. Em um dashboard compacto com espaçamento apertado entre botões, essa diferença de comprimento causa problemas de layout.
Minha abordagem de design para aplicações bilíngues canadenses é projetar primeiro em francês ao trabalhar com componentes que têm muito texto. Se o layout funciona em francês, quase sempre funciona em inglês. O inverso não é verdade. Um layout otimizado para inglês frequentemente quebra com texto em francês.
Na prática, eu uso técnicas de CSS que lidam graciosamente com expansão de texto: layouts baseados em flex com overflow ellipsis para truncamento onde apropriado, restrições de min-width em elementos de botão em vez de larguras fixas, e container queries em componentes de card para reorganizar o conteúdo quando a proporção texto-container se torna desfavorável.
Diferenças Idiomáticas Além da Tradução Palavra por Palavra
Tradução não é substituição palavra por palavra. O francês financeiro no Canadá tem convenções terminológicas específicas que diferem tanto do francês geral quanto do francês europeu. Eu não decido isso com o meu próprio francês. Eu consulto o Office québécois de la langue française. A Vitrine linguistique dele traz compte chèques como o termo canadense para uma chequing account, enquanto compte courant soa como francês europeu para um usuário do Quebec. Usar o termo regional errado sinaliza para esse usuário que a interface não foi feita para ele.
É por isso que eu coloco as etapas de tradução financeira com um tradutor profissional especializado em francês canadense, seguidas de uma revisão por um falante nativo de francês do Quebec. Essa é a cadeia que eu recomendo, não um relato de como algum empregador faz. Tradução automatizada, mesmo tradução automática de alta qualidade, consistentemente produz expressões do francês europeu para terminologia financeira. Em um contexto regulatório do Quebec, isso não é aceitável.
As convenções de tom e formalidade também diferem. Interfaces financeiras em inglês canadense usam linguagem relativamente casual e direta, 'You owe' em vez de 'Your outstanding balance is'. Interfaces financeiras em francês canadense são mais formais, e uma formulação como 'Le montant dû est' fica mais perto do registro que a terminologia financeira do OQLF usa do que uma tradução literal da versão casual em inglês. Essa é uma decisão do seu tradutor e das fiches do OQLF, não do desenvolvedor front-end. É uma decisão de estratégia de conteúdo que afeta toda a interface.
UX do Seletor de Idioma: Persistente, Previsível, Acessível
O próprio seletor de idioma é uma decisão de design de UX que carrega mais implicações do que aparenta. As convenções que valem a pena seguir:
- O seletor de idioma deve sempre exibir o idioma para o qual você vai trocar, não o idioma em que está atualmente. Se a interface está em inglês, o seletor diz 'Français'. Se está em francês, diz 'English'. Esse é o padrão do governo canadense e a convenção que usuários bilíngues esperam.
- A preferência de idioma deve persistir entre sessões quando o usuário está autenticado. Armazená-la em um perfil de usuário no servidor, em vez de no armazenamento do navegador, garante consistência entre dispositivos.
- Mudanças de idioma devem ter efeito imediatamente sem recarregar a página inteira sempre que possível. No Angular, isso significa que a mudança do TranslateService se propaga reativamente pela árvore de componentes sem navegação.
- O seletor de idioma deve ser acessível pelo teclado e deve ser anunciado corretamente por leitores de tela. Não é um botão que executa uma ação na página atual. Ele muda o idioma de toda a interface, o que é uma mudança de estado significativa o suficiente para justificar um anúncio aria-live.
- Posicione o seletor de idioma consistentemente em um local previsível. Em designs inspirados no governo canadense, o canto superior direito do cabeçalho é a convenção estabelecida. Não o mova para o rodapé nem o esconda em um menu de configurações.
Lidando com Conteúdo Dinâmico e Conteúdo Gerado pelo Usuário
Rótulos estáticos da interface são a parte fácil. O problema mais difícil é o conteúdo gerado pelo usuário e dados dinâmicos. Se um usuário insere seu endereço em francês, a versão em inglês da interface exibe corretamente? Se um assessor financeiro escreve uma nota em inglês e um cliente francófono a lê pela interface em francês, o que acontece?
Não existe uma solução técnica perfeita para isso. A resposta honesta é que o conteúdo gerado pelo usuário é neutro em termos de idioma. É armazenado e exibido como foi inserido, sem tradução. O que você pode controlar são os elementos de interface ao redor e a rotulagem desse conteúdo. Você também pode, em contextos apropriados, fornecer tradução automática como recurso assistivo com uma indicação clara de que é tradução automática e não oficial.
Para conteúdo gerado pelo sistema, como mensagens de status da solicitação, notificações automatizadas e erros de validação, o conteúdo deve estar sempre disponível em ambos os idiomas. Um padrão que funciona: armazenar o conteúdo de sistema, como códigos de status e requisitos de documentos, com uma descrição em inglês e uma em francês, e deixar a camada de exibição selecionar o idioma com base na preferência atual do usuário. Isso requer que o modelo de dados do back-end seja projetado com o bilinguismo em mente desde o início, não adaptado depois.
Planejando para RTL: Preparando para o Futuro Sem Excesso de Engenharia
O Canadá não tem requisitos de idioma RTL da mesma forma que aplicações voltadas para mercados árabes ou hebraicos. No entanto, se você está construindo uma biblioteca de componentes para uma grande instituição financeira que opera internacionalmente, vale a pena tomar decisões compatíveis com RTL desde o início em vez de adaptá-las depois.
A implicação prática para apps canadenses EN/FR é usar propriedades CSS lógicas (margin-inline-start em vez de margin-left, padding-inline-end em vez de padding-right) nos estilos de componentes compartilhados, e garantir que o atributo dir seja definido corretamente no elemento html quando o idioma muda. Esses hábitos custam muito pouco para estabelecer e fornecem opcionalidade significativa para internacionalização futura.
Bilinguismo em software canadense não é uma feature. É o produto mínimo viável para atender a população do país.
Construir aplicações genuinamente bilíngues, não apenas traduzidas, requer tratar EN e FR como preocupações de design co-iguais desde o primeiro dia de um projeto. A implementação técnica é direta uma vez que as bases de design e estratégia de conteúdo estão corretas. O trabalho difícil é organizacional: garantir que cada membro da equipe, do product owner ao desenvolvedor backend, entenda que o francês não é um adendo.
Referências
- Official Languages Act. https://laws-lois.justice.gc.ca/eng/acts/O-3.01/
- Government of Canada Content Style Guide. https://www.canada.ca/en/treasury-board-secretariat/services/government-communications/canada-content-style-guide.html
- Angular i18n overview. https://angular.dev/guide/i18n-overview
- ngx-translate. https://github.com/ngx-translate/core
- Office québécois de la langue française. https://www.oqlf.gouv.qc.ca/english/charter/index.html