Internacionalização com @angular/localize
Foram feitos grandes progressos na frente i18n! Um novo pacote chamado @angular/localize
foi introduzido no Angular 9.0.
É usado debaixo do capô para nos dar as mesmas funcionalidades que tínhamos anteriormente:traduções em templates em tempo de compilação.
Mas permite-nos esperar por mais no futuro,com funcionalidades não documentadas já disponíveis,como traduções em código,ou traduções em tempo de execução em vez de traduções de compilação apenas 😎.
Comecemos por ver o que podemos fazer com a ajuda de @angular/localize
e do CLI na v9.
i18n nos templates
O novo pacote @angular/localize
oferece uma função chamada $localize
.
O suporte i18n existente na Angular agora usa $localize
,significando que templates como:
<h1 i18n>Hello</h1>
>
serão compilados para $localize
chamadas.
Se você executar ng serve
com tal modelo, você irá encontrar um erro de tempo de execução:
Error: It looks like your application or one of its dependencies is using i18n.Angular 9 introduced a global `$localize()` function that needs to be loaded.Please run `ng add @angular/localize` from the Angular CLI.(For non-CLI projects, add `import '@angular/localize/init';` to your `polyfills.ts` file.For server-side rendering applications add the import to your `main.server.ts` file.)
>
>
O erro é auto-explicativo:como os atributos i18n
são agora convertidos para $localize
chamadas no código gerado, precisamos carregar a função $localize
.Pode ser feito por defeito,mas como só é necessário se estiver a usar internacionalização,não faria sentido.
É por isso que se a sua aplicação, ou uma das suas dependências, usa i18n
atributos nos seus templates,então terá de adicionar import '@angular/localize/init'
aos seus polyfills!
A CLI oferece um esquema para fazer isto por si.Basta correr:
ng add @angular/localize
e o CLI adiciona o pacote às suas dependências e a importação necessária aos seus polyfills.
Então quando correr a sua aplicação com um simples ng serve
,$localize
simplesmente exibe a mensagem original.
Agora como traduz estas mensagens?
O processo é muito semelhante ao que tínhamos anteriormente.Primeiro corra ng xi18n
para extrair as mensagens num ficheiro messages.xlf
e depois traduza o ficheiro para os seus locais, por exemplo messages.fr.xlf
e messages.es.xlf
.
Então você precisa configurar o CLI, em angular.json
{ "projects": { "ponyracer": { "projectType": "application", // ... "i18n": { "locales": { "fr": "src/locale/messages.fr.xlf", "es": "src/locale/messages.es.xlf", } }, "architect": { "build": { "builder": "@angular-devkit/build-angular:browser", // ... "configurations": { "production": { // ... }, "fr": { "localize": }, "es": { "localize": } } }, "serve": { // ... "configurations": { "production": { // ... }, "fr": { "browserTarget": "ponyracer:build:fr" }, "es": { "browserTarget": "ponyracer:build:es" } } } // ...}
>
Agora, as configurações es
ou fr
permitem executar:
ng serve --configuration=fr
E o aplicativo servido agora está em francês!
>
Você também pode construir o aplicativo com um locale específico:
ng build --configuration=production,es
>
>
ou com todos os locais de uma vez:
ng build --prod --localize
>
>
Este é um grande progresso em relação às versões angulares anteriores.Agora, quando Angular compila a aplicação, ela gera $localize
chamadas. Então, quando isso é feito, uma ferramenta pega a aplicação compilada e substitui todas as $localize
chamadas pelas traduções apropriadas. Isto é super rápido. Você então tem um pacote contendo sem chamadas para $localize
e todas as strings i18n foram traduzidas.
Até agora, você tinha que compilar sua aplicação uma vez por locale,e isto era uma compilação completa. Então digamos que é uma compilação de 30s,e você queria 4 locales, então você esteve dentro por 2 minutos.
Com a nova abordagem, a compilação é feita uma vez,e então as várias versões i18n são geradas em poucos segundos(é até gerado em paralelo se possível),então você vai de 2 minutos para ~40 segundos! 🌈
Você então tem vários pacotes, um por locale,e você pode servir o apropriado para seus usuários dependendo de sua preferência como você costumava fazer.
Esta estratégia é chamada de compilação em tempo de compilação como você inline as traduções diretamente,e então não há mais nada a fazer em tempo de execução.
Agora vamos falar sobre algo ainda não documentado,que pode mudar no futuro,mas ainda interessante de saber:agora também podemos traduzir mensagens em nosso código TypeScript!
A função $localize
de que tenho falado pode ser usada diretamente. É uma função peculiar que você pode usar para marcar uma string de template para localização.
Mas talvez devêssemos começar por explicar o que é uma string de template marcada?
Template strings e tag functions
Quando usar strings de modelo, você pode definir uma função tag, e aplicá-la a uma string de modelo. Aqui askQuestion
adiciona um ponto de interrogação no final da string:
const askQuestion = strings => strings + '?';const template = askQuestion`Is anyone here`;
Então qual é a diferença com uma função simples?A função tag de fato recebe vários argumentos:
- uma array das partes estáticas da string
- os valores resultantes da avaliação das expressões
Por exemplo, se tivermos um template de string contendo expressões:
const person1 = 'Cedric';const person2 = 'Agnes';const template = `Hello ${person1}! Where is ${person2}?`;
então a função tag receberá as várias partes estáticas e dinâmicas.Aqui temos uma função tag para colocar em maiúsculas os nomes dos protagonistas:
>
const uppercaseNames = (strings, ...values) => { // `strings` is an array with the static parts // `values` is an array with the evaluated expressions const names = values.map(name => name.toUpperCase()); // `names` now has // let's merge the `strings` and `names` arrays return strings.map((string, i) => `${string}${names ? names : ''}`).join('');};const result = uppercaseNames`Hello ${person1}! Where is ${person2}?`;// returns 'Hello CEDRIC! Where is AGNES?'
>
>
i18n com $localize em TypeScript code
$localize
usa este mecanismo para nos deixar escrever:
@Component({ template: '{{ title }}'})export class HomeComponent { title = $localize`You have 10 users`;}
>
>
>
Nota que você não tem que importar a função.Desde que você adicione import '@angular/localize/init'
uma vez na sua aplicação,$localize
será adicionado ao objeto global.
Você pode então traduzir a mensagem da mesma forma que você faria para um template.Mas, agora mesmo (v9.0.0), o CLI não extrai essas mensagens com o comando xi18n
como faz para os templates.
Se você servir a aplicação e nenhuma tradução for encontrada,$localize
simplesmente exibe a string original,e registra um aviso no console:
No translation found for "6480943972743237078" ("You have 10 users").
Então você tem que adicioná-lo manualmente ao seu messages.fr.xlf
com o IDif que você quer tentar:
<trans-unit> <source>You have 10 users</source> <target>Vous avez 10 utilisateurs</target></trans-unit>
>
O template do meu HomeComponent
então exibe Vous avez 10 utilisateurs
!
O que acontece se você tiver alguma expressão dinâmica na sua cadeia de modelos?
title = $localize`Hi ${this.name}! You have ${this.users.length} users.`;
As expressões serão automaticamente nomeadas PH
e PH_1
(PH
é para placeholder).Então você pode usar esses placeholders onde você quiser nas traduções:
<trans-unit> <source>Hi <x/>! You have <x/> users.</source> <target>Bonjour <x/> ! Vous avez <x/> utilisateurs.</target></trans-unit>
Mas a melhor prática é dar um placeholdername significativo para a expressão você mesmo, e você pode fazer isso usando a sintaxe ${expression}:placeholder:
.
title = $localize`Hi ${this.name}:name:! You have ${this.users.length}:userCount: users.`;
Então você pode usar este placeholder onde você quiser nas traduções:
<trans-unit> <source>Hi <x/>! You have <x/> users.</source> <target>Bonjour <x/> ! Vous avez <x/> utilisateurs.</target></trans-unit>
IDs personalizadas
Note que se você tiver traduções comIDs personalizadas, elas são usadas por $localize
(como era o caso anteriormente):
<h1 i18n="@@home.greetings">Hello</h1>
Então a sua tradução parece-se com:
<trans-unit> <source>Hello</source> <target>Bonjour</target></trans-unit>
>
o que é obviamente mais agradável de usar.
Como para traduções em código?$localize
também entende uma sintaxe que permite especificar um ID:
title = $localize`:@@home.users:You have 10 users`;
>
A sintaxe para o ID personalizado é a mesma que nos templates, e o ID é rodeado por dois pontos para separá-lo do conteúdo da tradução.
Como para a sintaxe do template, você também pode especificar uma descrição e um significado,para ajudar os tradutores com um pouco de contexto: :meaning|description@@id:message
.
Por exemplo:
title = $localize`:greeting message with the number of users currently logged in@@home.users:You have 10 users`;
>
>
Cuidado que esta é uma API de baixo nível, não documentada. A equipe ou comunidade Angular provavelmente oferecerá funções de nível superior com uma melhor experiência de desenvolvedor (bem, espero que sim!). Locl de Olivier Combe,o autor de ngx-translateis provavelmente vale a pena ficar de olho em 🧐.
Runtime translations
Como eu estava mencionando,se você usar os comandos CLI acima (ng serve --configuration=fr
ou ng build --localize
) então a aplicação é compilada e então traduzida antes de atingir o navegador, então não há $localize
chamadas em runtime.
Mas $localize
foi projetado para oferecer outra possibilidade:runtime translations.O que isso significa? Bem, seríamos capazes de enviar apenas uma aplicação, contendo $localize
chamadas, e antes da aplicação iniciar, poderíamos carregar as traduções que quiséssemos. Sem mais builds N e N bundles para N locales \o/
Sem mergulhar muito nos detalhes, isto já é possível com a v9, usando a função loadTranslations
oferecida por @angular/localize
.Mas isto tem que ser feito antes da aplicação começar.
Você pode carregar suas traduções em polyfills.ts
com:
import '@angular/localize/init';import { loadTranslations } from '@angular/localize';loadTranslations({ '1815172606781074132': 'Bonjour {$name}\xa0! Vous avez {$userCount} utilisateurs.'});
Como você pode ver não há consideração locale:você apenas carrega sua tradução como um objeto,cujas chaves são as cordas a traduzir,e os valores, suas traduções.
Agora se você executar um simples ng serve
,o título é exibido em francês! E não há mais necessidade de ng xi18n
, ou messages.fr.xlf
ou configuração específica para cada locale em angular.json
.A longo prazo, quando isso for devidamente suportado e documentado, devemos ser capazes de carregar arquivos JSON em tempo de execução, como a maioria das bibliotecas i18n fazem.Você poderia até consegui-lo na v9, é apenas um pouco de trabalho manual, mas é exequível.
E que tal mudar o locale na hora? Podemos carregar outro conjunto de traduções quando a aplicação for iniciada? Bem, não. A maneira atual $localize
chamadas são geradas torna impossível mudá-las depois: você tem que reiniciar a aplicação. Mas se você não se importa de atualizar o navegador, é possível.Eu testei uma estratégia simples que funciona:
- um usuário seleciona um novo idioma (por exemplo, espanhol).
- armazenamos o idioma no browser (por exemplo no localStorage)
- recarregamos a página, que reinicia a aplicação
- em
polyfills.ts
, começamos por ler o idioma armazenado - carregamos o conjunto adequado de traduções para o espanhol com
loadTranslations
.
>
>
O curso, isto será mais suave no futuro, seja numa versão futura do Angular,ou através de uma biblioteca do eco-sistema. De qualquer forma, estamos a aproximar-nos de enviar apenas uma versão da nossa aplicação, e apenas carregar as traduções em tempo de execução \o/
Todos os nossos materiais (ebook, formação e treino online) estão actualizados com estas alterações se quiser aprender mais!
Leave a Reply