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/>&nbsp;! 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/>&nbsp;! 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|[email protected]@id:message.

Por exemplo:

title = $localize`:greeting message with the number of users currently logged [email protected]@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.xlfou 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