Skip to content

Internacionalização

O pacote oficial @adonisjs/i18n adiciona suporte para internacionalização e localização aos seus aplicativos AdonisJS.

  • Os auxiliares de internacionalização permitem que você execute formatação sensível ao idioma de valores específicos, como data, moeda e nome.
  • A camada de localização permite que você armazene traduções e faça referência a elas dentro dos modelos Edge, erros de validação, exceções de autenticação e assim por diante.

O pacote I18n (abreviação de Internacionalização) deve ser instalado e configurado separadamente.

sh
npm i @adonisjs/i18n@1.6.0
sh
node ace configure @adonisjs/i18n

# CREATE: app/Middleware/DetectUserLocale.ts
# CREATE: ./resources/lang
# CREATE: config/i18n.ts
# UPDATE: .adonisrc.json { providers += "@adonisjs/i18n" }
  • Auxiliares para executar formatação sensível ao idioma para datas, moedas, nomes e assim por diante. Formato de mensagens ICU.
  • Adicione seu formatador de mensagens personalizado e carregador de traduções.

 

Ver no npmVer no GitHubReferência de API

Uso

A seguir está um exemplo básico de importação do pacote instalado e valores de formatação.

NOTA

O método I18n.locale retorna uma instância da classe I18n para uma localidade específica. O código de localidade deve ser um código de idioma padrão ISO 639-1 válido.

ts
import I18n from '@ioc:Adonis/Addons/I18n'

I18n.locale('en-US').formatDate(new Date())
// 10/8/2021

I18n.locale('fr').formatCurrency(100, { currency: 'EUR' })
// 100,00 €

const luxonDate = DateTime.local().minus({ minutes: 10 })
I18n.locale('pt').formatRelativeTime(luxonDate, 'auto')
// há 10 minutos

Você pode usar o método formatMessage para formatar traduções armazenadas. O método aceita a chave da mensagem como o primeiro argumento e os dados como o segundo argumento.

ts
import I18n from '@ioc:Adonis/Addons/I18n'

I18n
  .locale('en-US')
  .formatMessage('messages.greeting', { name: 'Virk' })

Saiba mais sobre formatação de traduções →

Uso durante solicitações HTTP

É recomendável usar o objeto ctx.i18n durante as solicitações HTTP. É uma instância isolada da classe I18n para a solicitação atual.

ts
Route.get('/', async ({ i18n }) => {
  return i18n.formatCurrency(100, { currency: 'EUR' })
})

Por padrão, a localidade de ctx.i18n é definida como a localidade padrão do aplicativo. Portanto, é recomendável usar o middleware DetectUserLocale para encontrar a localidade do usuário e atualizá-la para o restante da solicitação.

Config

A configuração é armazenada dentro do arquivo config/i18n.ts. Você sempre pode encontrar o config stub atualizado no GitHub.

ts
import Application from '@ioc:Adonis/Core/Application'
import { I18nConfig } from '@ioc:Adonis/Addons/I18n'

const i18nConfig: I18nConfig = {
  translationsFormat: 'icu',
  defaultLocale: 'en',

  // Opcional
  supportedLocales: [],
  fallbackLocales: {},

  provideValidatorMessages: true,
  loaders: {
    fs: {
      enabled: true,
      location: Application.resourcesPath('lang'),
    },
  },
}

export default i18nConfig

translationsFormat

O formato a ser usado para formatar traduções. Oficialmente, apenas o formato de mensagens ICU é suportado.

defaultLocale

O defaultLocale é o idioma padrão do seu aplicativo. Ele é sempre estático e não pode ser alterado em tempo de execução. Nós procuramos traduções do local padrão quando o idioma do usuário atual não é suportado e, também, não há fallback disponível.

ts
const i18nConfig: I18nConfig = {
  defaultLocale: 'en'
}

supportedLocales

É uma matriz de códigos de idioma formatados ISO 639-1 que seu aplicativo suporta. Se o idioma do usuário não for mencionado dentro deste array, usaremos o defaultLocale para procurar traduções.

Você pode opcionalmente definir o supportedLocales dentro do arquivo de configuração. Caso contrário, inferiremos os locais suportados dos diretórios de idiomas que você criou dentro do diretório resources/lang.

ts
const i18nConfig: I18nConfig = {
  supportedLocales: ['fr', 'en', 'it']
}

fallbackLocales

O fallbackLocales é um par de chave-valor dos locais que seu aplicativo suporta junto com seus locais de fallback.

Por exemplo: usar o espanhol como fallback para o idioma catalão faz mais sentido do que usar o inglês. Portanto, você mesmo pode definir os locais de fallback.

NOTA

O local para o qual você definiu o fallback deve fazer parte do array supportedLocales.

ts
const i18nConfig: I18nConfig = {
  fallbackLocales: {
    ca: 'es'
  }
}

provideValidatorMessages

Habilite/desabilite o suporte para fornecer mensagens de validação por meio de arquivos de tradução. As mensagens são fornecidas quando o sinalizador é definido como true.

Saiba mais sobre traduzir mensagens de validação.

loaders

Os loaders são usados ​​para carregar mensagens de algum tipo de armazenamento. Oficialmente, enviamos com uma implementação do carregador fs que carrega arquivos .json ou .yaml do sistema de arquivos.

Correspondência de localidade

Permitimos que você defina traduções para uma região específica ou use um código de idioma de dois dígitos para suporte genérico.

Por exemplo: se você armazenar as traduções para o idioma francês dentro do diretório fr, todas as variações do idioma francês verão as mesmas mensagens.

No entanto, se você criar diretórios específicos da região, como fr-ca ou fr-ch, as traduções do melhor local correspondente serão servidas.

O estilo de correspondência de localidade é conhecido como negociação de conteúdo. Em vez de procurar a correspondência exata, negociamos a correspondência mais próxima.

Encontrando o melhor local correspondente

Você deve usar o método I18n.getSupportedLocale para encontrar o melhor local para o idioma do usuário.

O método aceita uma string ou uma matriz de idiomas do usuário e retorna o local correspondente suportado pelo seu aplicativo. null é retornado quando nenhuma correspondência é encontrada.

ts
import I18n from '@ioc:Adonis/Addons/I18n'

const userLanguage = 'en-US'
const bestMatch = I18n.getSupportedLocale(userLanguage)

if (bestMatch) {
  I18n.locale(bestMatch).formatMessage()
} else {
  I18n.locale(I18n.defaultLocale).formatMessage()
}

Detectando a localidade do usuário

Você deve usar o middleware DetectUserLocale armazenado dentro do diretório app/Middleware para encontrar a localidade para a solicitação HTTP de entrada.

Por padrão, o middleware usa o cabeçalho HTTP Accept-language para encontrar o idioma do navegador do usuário.

No entanto, você pode alterar a implementação deste middleware e usar qualquer estratégia que se ajuste ao seu caso de uso e às necessidades do aplicativo. Apenas tenha os seguintes pontos em mente.

  • Certifique-se de sempre passar a localidade selecionada pelo usuário para o método I18n.getSupportedLocale(userLocale) para encontrar a melhor localidade possível suportada pelo seu aplicativo.
  • Se uma correspondência for encontrada, chame o método ctx.i18n.switchLocale(locale) para alternar o local para o restante da solicitação.

Além disso, certifique-se de registrar o middleware dentro do arquivo start/kernel.ts.

ts
// start/kernel.ts

Server.middleware.register([
  // ... other middleware(s)
  () => import('App/Middleware/DetectUserLocale')
])

Confira este projeto de exemplo que usa o alternador de idioma no aplicativo e sessões para gerenciar o idioma preferido do usuário.

Armazenamento de traduções

O carregador fs (padrão) procura as traduções dentro do diretório resources/lang. Você deve criar um subdiretório para cada local que seu aplicativo suporta. Por exemplo:

NOTA

O diretório de idioma deve ser nomeado após um código de idioma ISO 639-1 válido

bash
# resources/lang

├── en
└── fr

O carregador lerá todos os arquivos .json e .yaml. Além disso, sinta-se à vontade para criar vários subdiretórios ou arquivos dentro de um diretório de idioma.

bash
# resources/lang

├── en
│   ├── emails.yaml
│   └── validator.json
└── fr
    └── validator.json
json
// resources/lang/fr/validator.json

{
  "shared": {
    "required": "Ce champ est requis"
  }
}
yaml
# resources/lang/en/emails.yaml

welcome:
  content: >-
    <h2> Welcome to AdonisJS </h2>
    <p> Click <a href="{ url }"> here </a> to verify your account </p>

Formatando traduções

O formatador icu permite que você escreva traduções usando o formato de mensagens ICU. É um formato padrão da indústria para escrever traduções e é suportado por muitos serviços de tradução como Crowdin e Lokalise.

Dada a seguinte mensagem dentro do arquivo en/messages.json.

json
// resources/lang/en/messages.json

{
  "title": "A fully featured web framework for Node.js."
}

Você pode renderizá-lo da seguinte forma.

ts
import I18n from '@ioc:Adonis/Addons/I18n'
I18n.locale('en').formatMessage('messages.title')

E renderizá-lo dentro de modelos usando o método auxiliar t.

edge
<h1> {{ t('messages.title') }} </h1>

Interpolação

A sintaxe de mensagens ICU usa uma única chave para referenciar valores dinâmicos. Por exemplo:

NOTA

A sintaxe de mensagens ICU não suporta conjuntos de dados aninhados e, portanto, você só pode acessar propriedades de um objeto plano durante a interpolação.

json
{
  "greeting": "Hello { username }"
}
edge
{{ t('messages.greeting', { username: 'Virk' }) }}

Você também pode escrever HTML dentro das mensagens. No entanto, certifique-se de usar três chaves dentro dos modelos do Edge para renderizar HTML sem escapar dele.

Formato numérico

Você pode formatar valores numéricos dentro das mensagens de tradução usando a sintaxe {key, type, format}. No exemplo a seguir:

  • O amount é o valor de tempo de execução.
  • O number é o tipo de formatação. esqueleto numérico
json
{
  "bagel_price": "The price of this bagel is {amount, number, ::currency/USD}"
}
edge
{{ t('bagel_price', { amount: 2.49 }) }}
The price of this bagel is $2.49

A seguir estão alguns exemplos usando o formato number com diferentes estilos de formatação e esqueletos numéricos.

Length of the pole: {price, number, ::measure-unit/length-meter}
Account balance: {price, number, ::currency/USD compact-long}

Formato de data/hora

Você pode formatar as instâncias Date ou as instâncias luxon DateTime usando a sintaxe {key, type, format}. No exemplo a seguir:

  • expectedDate é o valor de tempo de execução.
  • date é o tipo de formatação.
  • E o medium é o formato da data.
json
{
  "shipment_update": "Your package will arrive on {expectedDate, date, medium}"
}
edge
{{ t('shipment_update', { expectedDate: luxonDateTime }) }}
Your package will arrive on Oct 16, 2021

Da mesma forma, você pode usar o formato de hora para formatar a hora para o local atual.

json
{
  "appointment": "You have an appointment today at {appointmentAt, time, ::h:m a}"
}
txt
You have an appointment today at 2:48 PM

Esqueletos de data/hora disponíveis

A ICU fornece uma ampla gama de padrões para personalizar o formato de data e hora. No entanto, nem todos eles estão disponíveis via API Intl do ECMA402. Portanto, oferecemos suporte apenas aos seguintes padrões.

SímboloDescrição
GDesignador de era
yano
Mmês no ano
Lmês autônomo no ano
ddia no mês
Edia da semana
edia local da semana e..eee não é suportado
cdia local autônomo da semana c..ccc não é suportado
aMarcador AM/PM
hHora [1-12]
HHora [0-23]
KHora [0-11]
kHora [1-24]
mMinuto
sSegundo
zFuso horário

Regras plurais

A sintaxe da mensagem ICU tem suporte de primeira classe para definir as regras plurais dentro de suas mensagens. Por exemplo:

NOTA

No exemplo a seguir, usamos YAML em vez de JSON, pois é mais fácil escrever texto multilinha em YAML.

yaml
cart_summary:
  "You have {itemsCount, plural,
    =0 {no items}
    one {1 item}
    other {# items}
  } in your cart"
edge
{{ t('messages.cart_summary', { itemsCount: 1 }) }}
You have 1 item in your cart.

O # é um token especial a ser usado como um espaço reservado para o valor numérico. Ele será formatado como {key, number}.

edge
{{ t('messages.cart_summary', { itemsCount: 1000 }) }}

<!-- Output -->
<!-- You have 1,000 items in your cart -->

Categorias plurais disponíveis

A regra plural usa a sintaxe {key, plural, matches}. O matches é um valor literal e corresponde a uma das seguintes categorias plurais.

CategoriaDescrição
zeroEsta categoria é usada para idiomas com gramática especializada especificamente para número zero de itens. (Exemplos são árabe e letão)
oneEsta categoria é usada para idiomas com gramática explicitamente especializada para um item. Muitos idiomas, mas não todos, usam esta categoria plural. (Muitos idiomas asiáticos populares, como chinês e japonês, não usam esta categoria.)
twoEsta categoria é usada para idiomas que têm gramática explicitamente especializada para dois itens. (Exemplos são árabe e galês.)
fewEsta categoria é usada para idiomas com gramática explicitamente especializada para um pequeno número de itens. Para alguns idiomas, isso é usado para 2-4 itens, para alguns 3-10 itens, e outros idiomas têm regras ainda mais complexas.
manyEsta categoria é usada para idiomas que têm uma gramática especializada para um número mais significativo de itens. (Exemplos são árabe, polonês e russo.)
otherEsta categoria é usada se o valor não corresponder a uma das outras categorias plurais. Observe que isso é usado para "plural" para idiomas (como inglês) que têm uma dicotomia simples "singular" versus "plural".
=valueIsso é usado para corresponder a um valor específico, independentemente das categorias plurais do local atual.

Tabela: O conteúdo da tabela é referenciado em formatjs.io

Selecionar

O formato select permite que você escolha a saída comparando um valor com uma das muitas opções. Escrever texto específico de gênero é um excelente exemplo do formato select.

yaml
# Yaml

auto_reply:
  "{gender, select,
    male {He}
    female {She}
    other {They}
  } will respond shortly."
edge
{{ t('messages.auto_reply', { gender: 'female' }) }}
She will respond shortly.

Selecionar ordinal

O formato select ordinal permite que você escolha a saída com base nas regras de pluralização ordinal. O formato é semelhante ao formato plural. No entanto, o valor é mapeado para uma categoria plural ordinal.

yaml
anniversary_greeting:
  "It's my {years, selectordinal,
    one {#st}
    two {#nd}
    few {#rd}
    other {#th}
  } anniversary"
edge
{{ t('messages.anniversary_greeting', { years: 2 }) }}
txt
It's my 2nd anniversary

Categorias ordinais selecionadas disponíveis

O formato ordinal selecionado usa a sintaxe {key, selectordinal, matches}. A correspondência é um valor literal e corresponde a uma das seguintes categorias plurais.

CategoriaDescrição
zeroEsta categoria é usada para idiomas com gramática especializada especificamente para zero número de itens. (Exemplos são árabe e letão.)
oneEsta categoria é usada para idiomas com gramática explicitamente especializada para um item. Muitos idiomas, mas não todos, usam esta categoria plural. (Muitos idiomas asiáticos populares, como chinês e japonês, não usam esta categoria.)
twoEsta categoria é usada para idiomas com gramática explicitamente especializada para dois itens. (Exemplos são árabe e galês.)
fewEsta categoria é usada para idiomas com gramática explicitamente especializada para um pequeno número de itens. Para alguns idiomas, isto é usado para 2-4 itens, para alguns 3-10 itens, e outros idiomas têm regras ainda mais complexas.
manyEsta categoria é usada para idiomas com gramática especializada para um número maior de itens. (Exemplos são árabe, polonês e russo.)
otherEsta categoria é usada se o valor não corresponder a uma das outras categorias plurais. Observe que isto é usado para "plural" para idiomas (como inglês) que têm uma dicotomia simples "singular" versus "plural".
=valueIsso é usado para corresponder a um valor específico, independentemente das categorias plurais do local atual.

Tabela: O conteúdo da tabela é referenciado de formatjs.io

Formatadores Intl

Os formatadores Intl são wrappers finos sobre a API Intl do Node.js. Criar uma nova instância das classes Intl é lento, então memorizamos os construtores para acelerar as coisas. Veja benchmarks

formatNumber

O formatNumber usa a classe Intl.NumberFormat para formatar um valor numérico.

  • O primeiro argumento é o valor a ser formatado. Deve ser um número, bigint ou uma representação de string de um número.
  • O segundo argumento são as opções. Elas são as mesmas que as opções aceitas pela classe Intl.NumberFormat.
ts
import I18n from '@ioc:Adonis/Addons/I18n'

I18n
  .locale('en')
  .formatNumber(123456.789, {
    maximumSignificantDigits: 3
  })

formatCurrency

O método formatCurrency usa a classe Intl.NumberFormat, mas implicitamente define o style para moeda.

ts
import I18n from '@ioc:Adonis/Addons/I18n'

I18n
  .locale('en')
  .formatCurrency(200, {
    currency: 'USD'
  })

formatDate

O método formatDate usa a classe Intl.DateTimeFormat para formatar uma data.

  • O primeiro argumento é a data a ser formatada. Pode ser uma string de data ISO, um timestamp, uma instância da classe JavaScript Date ou um luxon DateTime.
  • O segundo argumento são as opções. Elas são as mesmas que as opções aceitas pela classe Intl.DateTimeFormat.
ts
import I18n from '@ioc:Adonis/Addons/I18n'

I18n
  .locale('en')
  .formatDate(new Date(), {
    dateStyle: 'long'
  })

formatTime

O método formatTime usa a classe Intl.DateTimeFormat, mas implicitamente define o timeStyle como médio.

ts
import I18n from '@ioc:Adonis/Addons/I18n'

I18n
  .locale('en')
  .formatTime(new Date(), {
    timeStyle: 'long'
  })

formatRelativeTime

O método formatRelativeTime usando a classe Intl.RelativeTimeFormat para formatar um valor para uma string de representação de tempo relativo.

  • O primeiro argumento é o valor do tempo relativo. Pode ser uma string de data ISO, um diff numérico absoluto, uma instância da classe JavaScript Date ou uma instância de luxon DateTime. unidades oficialmente suportadas, também suportamos uma unidade auto adicional.
  • O terceiro argumento são as opções. Elas são as mesmas que as opções aceitas pela classe Intl.RelativeTimeFormat.
ts
import { DateTime } from 'luxon'
import I18n from '@ioc:Adonis/Addons/I18n'

const luxonDate = DateTime.local().plus({ hours: 2 })
I18n
  .locale('en')
  .formatRelativeTime(luxonDate, 'hours')

Encontraremos a melhor unidade ao usar a unidade de formatação definida como auto. Por exemplo:

ts
const luxonDate = DateTime.local().plus({ hours: 2 })
I18n
  .locale('en')
  .formatRelativeTime(luxonDate, 'auto')

// In 2 hours 👈
ts
const luxonDate = DateTime.local().plus({ hours: 200 })
I18n
  .locale('en')
  .formatRelativeTime(luxonDate, 'auto')

// In 8 days 👈

formatPlural

O método formatPlural usa o Intl.PluralRules e retorna uma categoria plural para um determinado valor numérico.

  • O primeiro argumento é o valor. Deve ser um número ou uma representação de string de um número.
  • O segundo argumento são as opções. Elas são as mesmas que as opções aceitas pela classe Intl.PluralRules.
ts
import I18n from '@ioc:Adonis/Addons/I18n'

I18n.locale('en').formatPlural(0)
// other

I18n.locale('en').formatPlural(1)
// one

I18n.locale('en').formatPlural(2)
// other

Mensagens do validador

A seguir estão as etapas para configurar o pacote i18n para fornecer as mensagens de validação dos arquivos de traduções.

  1. Defina o valor de provideValidatorMessages = true dentro do arquivo de configuração.
  2. Crie um arquivo validator.json dentro de cada diretório de idioma.
  3. Defina mensagens para as regras de validação dentro do objeto shared.
json
// resources/lang/en/validator.json

{
  "shared": {
    "required": "The value for the field is required",
    "unique": "Email is already in use",
    "minLength": "The field must have { minLength } items"
  }
}

As mensagens da chave shared são fornecidas automaticamente ao validador. Você também pode ser específico e definir uma mensagem para uma combinação field + rule. Por exemplo:

json
{
  "shared": {
    "required": "The value for the field is required",
    "username.required": "Username is required to create an account"
  }
}

Saco de mensagens personalizadas

Se alguma parte do seu aplicativo precisar de mensagens de validação específicas, você pode defini-las dentro do arquivo validator.json sob uma chave de nível superior diferente e, em seguida, referenciá-las usando o método i18n.validatorMessages().

json
// resources/lang/en/validator.json

{
  "shared": {},
  "contact": {
    "email.required": "Enter the email so that we can contact you",
    "message.required": "Describe your project in a few words."
  }
}

Agora, você pode referenciar as mensagens do objeto contact no validador da seguinte forma.

ts
import { schema } from '@ioc:Adonis/Core/Validator'
import { HttpContextContract } from '@ioc:Adonis/Core/HttpContext'

export default class ContactValidator {
  constructor(protected ctx: HttpContextContract) {}

  public schema = schema.create({})

  public messages = this.ctx.i18n.validatorMessages('validator.contact')
}

Mensagens de autenticação

Você também pode fornecer traduções para as exceções geradas pelo pacote auth. As traduções devem ser definidas dentro do arquivo auth.json usando o código de exceção como a chave de tradução.

NOTA

As traduções são usadas para o texto de resposta e não para a propriedade error.message. Elas ainda estarão em inglês e codificadas.

json
{
  "E_INVALID_AUTH_SESSION": "Your session has expired",
  "E_INVALID_API_TOKEN": "Invalid or expired API token",
  "E_INVALID_BASIC_CREDENTIALS": "Invalid credentials",
  "E_INVALID_AUTH_UID": "Invalid credentials",
  "E_INVALID_AUTH_PASSWORD": "Invalid credentials"
}

Traduzindo e-mails

Como os e-mails geralmente são enviados em segundo plano (fora do ciclo de vida da solicitação HTTP), você deve passar explicitamente a instância i18n para os modelos de e-mail.

O método auxiliar t é um alias para i18n.formatMessage, ele formatará mensagens no mesmo idioma para o qual você criou a instância da classe i18n e a passou para o estado do modelo.

ts
import Mail from '@ioc:Adonis/Addons/Mail'
import I18n from '@ioc:Adonis/Addons/I18n'

const i18n = I18n.locale(customerLocale)

await Mail.send((message) => {
  message
    .subject(i18n.formatMessage('emails.welcome_subject'))
    .htmlView('emails/welcome', { i18n })
})

Recarregando traduções

As traduções são carregadas e armazenadas em cache na memória na inicialização do aplicativo. Portanto, quaisquer alterações feitas nos arquivos de tradução não serão refletidas até que você reinicie o processo.

Durante o desenvolvimento, o servidor dev será reiniciado na alteração do arquivo. No entanto, na produção, você terá que reiniciar o servidor manualmente (como uma nova implantação).

Se por algum motivo você quiser recarregar as traduções dentro do processo em execução, poderá usar o método I18n.reloadTranslations() para fazer isso.

ts
import I18n from '@ioc:Adonis/Addons/I18n'

await I18n.reloadTranslations()

Relatando traduções ausentes

Para ajudar você a adicionar progressivamente traduções para novos idiomas, relatamos as traduções ausentes emitindo o evento i18n:missing:translation.

Crie um novo arquivo de pré-carregamento start/i18n.ts executando o seguinte comando Ace. Selecione todos os ambientes.

sh
node ace make:prldfile i18n

Abra o arquivo recém-criado e cole o seguinte conteúdo dentro dele. Atualmente, estamos usando o método I18n.prettyPrint para registrar a mensagem no console. No entanto, você também pode usar o logger para registrar a mensagem.

ts
// start/i18n.ts

import Event from '@ioc:Adonis/Core/Event'
import I18n from '@ioc:Adonis/Addons/I18n'

Event.on('i18n:missing:translation', I18n.prettyPrint)

Adicionar formatador de mensagem personalizado

O formatador de mensagem define a sintaxe e os recursos das traduções armazenadas. O pacote é fornecido com um formatador icu que usa a sintaxe de mensagens ICU para escrever traduções.

No entanto, você também pode registrar um formatador de mensagem personalizado usando o método I18n.extend. A implementação do formatador deve aderir à interface TranslationsFormatterContract.

ts
interface TranslationsFormatterContract {
  readonly name: string
  format(message: string, locale: string, data?: Record<string, any>): string
}

name

Um nome exclusivo para o formatador. Será um valor de string estático.

format

O método format recebe os seguintes argumentos e deve retornar uma string formatada.

  • O primeiro argumento é o texto da mensagem.
  • O segundo argumento é o locale para o qual a formatação deve acontecer.
  • Finalmente, o objeto de dados para valores dinâmicos.

Implementação fictícia

A seguir está uma implementação muito direta que usa o mecanismo de modelo Edge para formatar traduções.

Etapa 1. Crie a classe formatadora.

Crie um novo arquivo MustacheFormatter.ts dentro do diretório providers e cole o seguinte conteúdo dentro dele.

ts
import type { ViewContract } from '@ioc:Adonis/Core/View'
import type { TranslationsFormatterContract } from '@ioc:Adonis/Addons/I18n'

export class MustacheFormatter implements TranslationsFormatterContract {
  public readonly name = 'mustache'
  constructor(private view: ViewContract) {}

  public format(message: string, _: string, data?: Record<string, any>) {
    return this.view.renderRawSync(message, data)
  }
}

Etapa 2. Estenda o I18n e registre o formatador

Abra o arquivo providers/AppProvider.ts e registre o formatador dentro do método boot.

ts
// providers/AppProvider.ts

import { ApplicationContract } from '@ioc:Adonis/Core/Application'
import { MustacheFormatter } from './MustacheFormatter'

export default class AppProvider {
  constructor(protected app: ApplicationContract) {}

  public register() {
    // Registre suas próprias ligações
  }

  public async boot() {
    const I18n = this.app.container.resolveBinding('Adonis/Addons/I18n')
    const View = this.app.container.resolveBinding('Adonis/Core/View')

    I18n.extend('mustache', 'formatter', () => new MustacheFormatter(View))
  }

  public async ready() {
    // O aplicativo está pronto
  }

  public async shutdown() {
    // Limpeza, pois o aplicativo está fechando
  }
}

Etapa 3. Use o formatador mustache

Atualize o arquivo de configuração e defina o translationsFormat para mustache.

ts
{
  translationsFormat: 'mustache'
}

Adicione um carregador de mensagens personalizado

O carregador de mensagens é responsável por carregar as mensagens de uma fonte permanente. O pacote é fornecido com um formatador fs que lê os arquivos .json e .yaml do sistema de arquivos.

No entanto, você também pode registrar carregadores personalizados usando o método I18n.extend. A implementação do carregador deve aderir à interface LoaderContract.

ts
type Translations = {
  [lang: string]: Record<string, string>
}

interface LoaderContract {
  load(): Promise<Translations>
}

Os carregadores só precisam implementar um único método chamado load que retorna todas as traduções como um objeto.

As chaves de nível superior do objeto são os códigos de idioma, e o valor é outro objeto de mensagens.

ts
{
  en: {},
  fr: {},
  it: {}
}

Além disso, certifique-se de converter mensagens aninhadas dentro de um objeto de idioma para um objeto simples. Por exemplo:

ts
{
  en: {
    'messages.title': '',
    'messages.subtitle': ''
  }
}

Implementação fictícia

A seguir está uma implementação muito direta que lê as mensagens do banco de dados usando Lucid.

Etapa 1. Crie a classe do carregador.

Crie um novo arquivo DbLoader.ts dentro do diretório providers e cole o seguinte conteúdo dentro dele.

ts
import type { DatabaseContract } from '@ioc:Adonis/Lucid/Database'
import type {
  Translations,
  LoaderContract
} from '@ioc:Adonis/Addons/I18n'

export type DbLoaderConfig = {
  enabled: boolean
  table: string
}

export class DbLoader implements LoaderContract {
  constructor(private db: DatabaseContract, private config: DbLoaderConfig) {}

  public async load() {
    const rows = await this.db.from(this.config.table)

    return rows.reduce<Translations>((result, row) => {
      result[row.locale] = result[row.locale] || {}
      result[row.locale][row.key] = row.message
      return result
    }, {})
  }
}

Etapa 2. Estenda o I18n e registre o carregador

Abra o arquivo providers/AppProvider.ts e registre o carregador dentro do método boot.

ts
// providers/AppProvider.ts

import { ApplicationContract } from '@ioc:Adonis/Core/Application'
import { DbLoader } from './DbLoader'

export default class AppProvider {
  constructor(protected app: ApplicationContract) {}

  public register() {
    // Registre suas próprias ligações
  }

  public async boot() {
    const I18n = this.app.container.resolveBinding('Adonis/Addons/I18n')
    const Db = this.app.container.resolveBinding('Adonis/Lucid/Database')

    I18n.extend('db', 'loader', (_, config) => {
      return new DbLoader(Db, config)
    })
  }

  public async ready() {
    // O aplicativo está pronto
  }

  public async shutdown() {
    // Limpeza, pois o aplicativo está fechando
  }
}

Etapa 3. Use o carregador db

Atualize o arquivo de configuração e adicione a chave do carregador db ao objeto loaders.

ts
{
  loaders: {
    fs: {},
    db: {
      enabled: true,
      table: 'translations'
    }
  }
}

Etapa 4. Crie a tabela de traduções

Use a migração a seguir para criar a tabela de traduções.

NOTA

Ao executar a migração, você terá que desabilitar o carregador db dentro do arquivo de configuração. Caso contrário, o carregador tentará ler as mensagens de uma tabela não existente.

ts
import BaseSchema from '@ioc:Adonis/Lucid/Schema'

export default class Translations extends BaseSchema {
  protected tableName = 'translations'

  public async up() {
    this.schema.createTable(this.tableName, (table) => {
      table.increments('id')
      table.string('locale', 8).notNullable()
      table.string('key').notNullable()
      table.text('message', 'longtext').notNullable()

      table.timestamp('created_at', { useTz: true })
      table.timestamp('updated_at', { useTz: true })

      table.unique(['locale', 'key'])
    })
  }

  public async down() {
    this.schema.dropTable(this.tableName)
  }
}

Leitura adicional

Certifique-se de ler o guia de referência da API para visualizar todas as propriedades e métodos disponíveis.