Skip to content

Internacionalização e localização

A internacionalização e a localização visam ajudar você a criar aplicativos da web para várias regiões e idiomas. O suporte para i18n (abreviação de internacionalização) é fornecido pelo pacote @adonisjs/i18n.

  • Localização é o processo de traduzir o texto do seu aplicativo para vários idiomas. Você deve escrever uma cópia para cada idioma e referenciá-los em modelos do Edge, mensagens de erro de validação ou usando a API i18n diretamente.

  • Internacionalização é o processo de formatar valores como data, hora, números de acordo com uma região ou país específico.

Instalação

Instale e configure o pacote usando o seguinte comando:

sh
node ace add @adonisjs/i18n
Veja as etapas executadas pelo comando add
  1. Instala o pacote @adonisjs/i18n usando o gerenciador de pacotes detectado.

  2. Registra o seguinte provedor de serviços dentro do arquivo adonisrc.ts.

    ts
    {
      providers: [
        // ...other providers
        () => import('@adonisjs/i18n/i18n_provider')
      ]
    }
  3. Cria o arquivo config/i18n.ts.

  4. Cria detect_user_locale_middleware dentro do diretório middleware.

  5. Registra o seguinte middleware dentro do arquivo start/kernel.ts.

    ts
    router.use([
      () => import('#middleware/detect_user_locale_middleware')
    ])

Configuração

A configuração do pacote i18n é armazenada no arquivo config/i18n.ts.

Veja também: Config stub

ts
import app from '@adonisjs/core/services/app'
import { defineConfig, formatters, loaders } from '@adonisjs/i18n'

const i18nConfig = defineConfig({
  defaultLocale: 'en',
  formatter: formatters.icu(),

  loaders: [
    loaders.fs({
      location: app.languageFilesPath()
    })
  ],
})

export default i18nConfig

formatter

Define o formato a ser usado para armazenar traduções. O AdonisJS suporta o formato de mensagem ICU.

O formato de mensagem ICU é um padrão amplamente aceito e suportado por muitos serviços de tradução como Crowdin e Lokalise.

Além disso, você pode adicionar formatadores de mensagem personalizados.

defaultLocale

O local padrão para o aplicativo. Traduções e formatação de valores retornarão a esta localidade quando seu aplicativo não suportar o idioma do usuário.

fallbackLocales

Um par de chave-valor que define uma coleção de localidades e suas localidades de fallback. Por exemplo, se seu aplicativo suportar espanhol, você pode defini-lo como um fallback para o idioma Catalin.

ts
export default defineConfig({
  formatter: formatters.icu(),
  defaultLocale: 'en',
  fallbackLocales: {
    ca: 'es' // mostrar conteúdo em espanhol quando um usuário fala catalão
  }
})

supportedLocales

Uma matriz de localidades suportadas por seu aplicativo.

ts
export default defineConfig({
  formatter: formatters.icu(),
  defaultLocale: 'en',
  supportedLocales: ['en', 'fr', 'it']
})

Se você não definir este valor, inferiremos supportedLocales das traduções. Por exemplo, se você tiver definido traduções para inglês, francês e espanhol, o valor de supportedLocales será ['en', 'es', 'fr']

loaders

Uma coleção de carregadores para usar no carregamento de traduções. Por padrão, suportamos apenas o carregador do sistema de arquivos. No entanto, você pode adicionar carregadores personalizados.

Armazenando traduções

As traduções são armazenadas dentro do diretório resources/lang, e você deve criar um subdiretório para cada idioma conforme o formato IETF language tag . Por exemplo:

resources
├── lang
│   ├── en
│   └── fr

Você pode definir traduções para uma região específica criando subdiretórios com o código da região. No exemplo a seguir, definimos traduções diferentes para Inglês (Global), Inglês (Estados Unidos) e Inglês (Reino Unido).

O AdonisJS retornará automaticamente para Inglês (Global) quando você tiver uma tradução ausente em um conjunto de traduções específicas da região.

Veja também: Código de idioma ISO

resources
├── lang
│   ├── en
│   ├── en-us
│   ├── en-uk

Formato de arquivos

As traduções devem ser armazenadas dentro de arquivos .json ou .yaml. Sinta-se à vontade para criar uma estrutura de diretório aninhada para melhor organização.

resources
├── lang
│   ├── en
│   │   └── messages.json
│   └── fr
│       └── messages.json

As traduções devem ser formatadas de acordo com a sintaxe de mensagem ICU.

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

{
  "greeting": "Hello world"
}
json
// resources/lang/fr/messages.json

{
  "greeting": "Bonjour le monde"
}

Resolvendo traduções

ts
import i18nManager from '@adonisjs/i18n/services/main'

// Instância I18n para inglês
const en = i18nManager.locale('en')

// Instância I18n para francês
const fr = i18nManager.locale('fr')

Depois de ter uma instância da classe I18n, você pode usar o método .t para formatar uma tradução.

ts
const i18n = i18nManager.locale('en')
i18n.t('messages.greeting') // Hello world
ts
const i18n = i18nManager.locale('fr')
i18n.t('messages.greeting') // Bonjour le monde

Localidade de fallback

Cada instância tem um idioma de fallback pré-configurado com base na coleção config.fallbackLocales. O idioma de fallback é usado quando uma tradução está faltando para o idioma principal.

ts
export default defineConfig({
  fallbackLocales: {
    'de-CH': 'de',
    'fr-CH': 'fr'
  }
})
ts
const i18n = i18nManager.locale('de-CH')
i18n.fallbackLocale // de (usando coleção de fallback)
ts
const i18n = i18nManager.locale('fr-CH')
i18n.fallbackLocale // fr (usando coleção de fallback)
ts
const i18n = i18nManager.locale('en')
i18n.fallbackLocale // en (usando localidade padrão)

Traduções ausentes

Se uma tradução estiver ausente nos locais principal e de fallback, o método .t retornará uma string de erro formatada da seguinte forma.

ts
const i18n = i18nManager.locale('en')

i18n.t('messages.hero_title')
// translation missing: en, messages.hero_title

Você pode substituir esta mensagem por uma mensagem diferente ou uma string vazia definindo um valor de fallback como o segundo parâmetro.

ts
const fallbackValue = ''
i18n.t('messages.hero_title', fallbackValue)
// saída: ''

Você também pode calcular um valor de fallback globalmente por meio do arquivo de configuração. O método fallback recebe o caminho da tradução como o primeiro parâmetro e o código de localidade como o segundo parâmetro. Certifique-se de sempre retornar um valor de string.

ts
import { defineConfig } from '@adonisjs/i18n'

export default defineConfig({
  fallback: (identifier, locale) => {
    return ''
  },
})

Detectando a localidade do usuário durante uma solicitação HTTP

Durante a configuração inicial, criamos um arquivo detect_user_locale_middleware.ts dentro do diretório ./app/middleware. O middleware executa as seguintes ações.

Se esse middleware estiver ativo, você pode traduzir mensagens dentro de seus controladores e modelos Edge da seguinte forma.

ts
import { HttpContext } from '@adonisjs/core/http'

export default class PostsController {
  async store({ i18n, session }: HttpContext) {
    session.flash('success', {
      message: i18n.t('post.created')
    })
  }
}
edge
<h1> {{ t('messages.heroTitle') }} </h1>

Alterando o código de detecção do idioma do usuário

Como o detect_user_locale_middleware faz parte da base de código do seu aplicativo, você pode modificar o método getRequestLocale e usar lógica personalizada para encontrar o idioma do usuário.

Traduzindo mensagens de validação

O detect_user_locale_middleware se conecta ao Request validator e fornece mensagens de validação usando os arquivos de tradução.

ts
export default class DetectUserLocaleMiddleware {
  static {
    RequestValidator.messagesProvider = (ctx) => {
      return ctx.i18n.createMessagesProvider()
    }
  }
}

As traduções devem ser armazenadas dentro do arquivo validator.json sob a chave shared. As mensagens de validação podem ser definidas para a regra de validação ou a combinação field + rule.

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

{
  "shared": {
    "fields": {
      "first_name": "first name"
    },
    "messages": {
      "required": "Enter {field}",
      "username.required": "Choose a username for your account",
      "email": "The email must be valid"
    }
  }
}
json
// resources/lang/fr/validator.json

{
  "shared": {
    "fields": {
      "first_name": "Prénom"
    },
    "messages": {
      "required": "Remplisser le champ {field}",
      "username.required": "Choissisez un nom d'utilisateur pour votre compte",
      "email": "L'email doit être valide"
    }
  }
}

Usando traduções com VineJS diretamente

Durante uma solicitação HTTP, o detect_user_locale_middleware se conecta ao validador de solicitação e registra um provedor de mensagens personalizadas para procurar erros de validação em arquivos de tradução.

No entanto, se você usar o VineJS fora de uma solicitação HTTP, em comandos Ace ou trabalhos de fila, deverá registrar explicitamente um provedor de mensagens personalizadas ao chamar o método validator.validate.

ts
import { createJobValidator } from '#validators/jobs'
import i18nManager from '@adonisjs/i18n/services/main'

/**
 * Obtenha uma instância i18n para uma localidade específica
 */
const i18n = i18nManager.locale('fr')

await createJobValidator.validate(data, {
  /**
   * Registre um provedor de mensagens para usar traduções
   */
  messagesProvider: i18n.createMessagesProvider()
})

Formato de mensagem ICU

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, use três chaves dentro dos modelos Edge para renderizar HTML sem escapar dele.

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

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 exemplos de uso do 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 medium é o formato de 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, 2023

Você pode usar o formato time para formatar o valor como uma hora.

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

O 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 escrever texto multilinha em YAML é mais fácil.

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 --}}

A regra plural usa a sintaxe {key, plural, matches}. O matches é um valor literal correspondido 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 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, 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 com 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.

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 select. 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

O formato ordinal de seleção 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, 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 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 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.

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

Formatando valores

Os métodos abaixo usam a API Intl do Node.js, mas têm melhor desempenho. Veja benchmarks

formatNumber

Formate um valor numérico usando a classe Intl.NumberFormat. Você pode passar os seguintes argumentos.

  1. O valor a ser formatado.
  2. Um objeto options opcional.
ts
import i18nManager from '@adonisjs/i18n/services/main'

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

formatCurrency

Formate um valor numérico como uma moeda usando a classe Intl.NumberFormat. O método formatCurrency define implicitamente a opção style = currency.

ts
import i18nManager from '@adonisjs/i18n/services/main'

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

formatDate

Formate uma data ou um objeto luxon date-time usando a classe Intl.DateTimeFormat. Você pode passar os seguintes argumentos.

  1. O valor a ser formatado. Pode ser um objeto Date ou um objeto luxon DateTime.
  2. Um objeto opcional options.
ts
import i18nManager from '@adonisjs/i18n/services/main'
import { DateTime } from 'luxon'

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

// Formato luxon data hora instância
i18nManager
  .locale('en')
  .formatDate(DateTime.local(), {
    dateStyle: 'long'
  })

formatTime

Formate um valor de data para uma string de tempo usando a classe Intl.DateTimeFormat. O método formatTime define implicitamente a opção timeStyle = medium.

ts
import i18nManager from '@adonisjs/i18n/services/main'

i18nManager
  .locale('en')
  .formatTime(new Date())

formatRelativeTime

O método formatRelativeTime usa a classe Intl.RelativeTimeFormat para formatar um valor para uma string de representação de tempo relativa. O método aceita os seguintes argumentos.

ts
import { DateTime } from 'luxon'
import i18nManager from '@adonisjs/i18n/services/main'

const luxonDate = DateTime.local().plus({ hours: 2 })

i18nManager
  .locale('en')
  .formatRelativeTime(luxonDate, 'hours')

Defina o valor da unidade como auto para exibir a diferença na melhor unidade correspondente.

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

Encontre a categoria plural para um número usando a classe Intl.PluralRules. Você pode passar os seguintes argumentos.

  1. O valor numérico para o qual encontrar a categoria plural.
  2. Um objeto options opcional.
ts
import i18nManager from '@adonisjs/i18n/services/main'

i18nManager.i18nManager('en').formatPlural(0)
// other

i18nManager.i18nManager('en').formatPlural(1)
// one

i18nManager.i18nManager('en').formatPlural(2)
// other

formatList

Formate um array de strings para uma frase usando a classe Intl.ListFormat. Você pode passar os seguintes argumentos.

  1. O valor a ser formatado.
  2. Um objeto options opcional.
ts
import i18nManager from '@adonisjs/i18n/services/main'

i18nManager
  .locale('en')
  .formatList(['Me', 'myself', 'I'], { type: 'conjunction' })

// Me, myself and I
ts
import i18nManager from '@adonisjs/i18n/services/main'

i18nManager
  .locale('en')
  .formatList(['5 hours', '3 minutes'], { type: 'unit' })

// 5 hours, 3 minutes

formatDisplayNames

Formate os códigos currency, language, region e calendar para seus nomes de exibição usando a classe Intl.DisplayNames. Você pode passar os seguintes argumentos.

  1. O código a ser formatado. O valor de code varia dependendo do type de formatação.
  2. Options objeto.
ts
import i18nManager from '@adonisjs/i18n/services/main'

i18nManager
  .locale('en')
  .formatDisplayNames('INR', { type: 'currency' })

// Indian Rupee
ts
import i18nManager from '@adonisjs/i18n/services/main'

i18nManager
  .locale('en')
  .formatDisplayNames('en-US', { type: 'language' })

// American English

Configurando a extensão i18n Ally VSCode

A extensão i18n Ally para VSCode fornece um excelente fluxo de trabalho para armazenar, inspecionar e referenciar traduções com seu editor de código.

Para fazer a extensão funcionar perfeitamente com o AdonisJS, você deve criar os seguintes arquivos dentro do diretório .vscode da raiz do seu projeto.

sh
mkdir .vscode

touch .vscode/i18n-ally-custom-framework.yml
touch .vscode/settings.json

Copie/cole o seguinte conteúdo dentro do arquivo settings.json.

json
// .vscode/settings.json

{
  "i18n-ally.localesPaths": [
    "resources/lang"
  ],
  "i18n-ally.keystyle": "nested",
  "i18n-ally.namespace": true,
  "i18n-ally.editor.preferEditor": true,
  "i18n-ally.refactor.templates": [
    {
      "templates": [
        "{{ t('{key}'{args}) }}"
      ],
      "include": [
        "**/*.edge",
      ],
    },
  ]
}

Copie/cole o seguinte conteúdo dentro do arquivo .vscode/i18n-ally-custom-framework.yml.

yaml
// .vscode/i18n-ally-custom-framework.yml

languageIds:
  - edge
usageMatchRegex:
  - "[^\\w\\d]t\\(['\"`]({key})['\"`]"
sortKeys: true

Ouvindo o evento de traduções ausentes

Você pode ouvir o evento i18n:missing:translation para ser notificado sobre as traduções ausentes no seu aplicativo.

ts
import emitter from '@adonisjs/core/services/emitter'

emitter.on('i18n:missing:translation', function (event) {
  console.log(event.identifier)
  console.log(event.hasFallback)
  console.log(event.locale)
})

Forçar o recarregamento de traduções

O pacote @adonisjs/i18n lê os arquivos de tradução ao inicializar o aplicativo e os armazena na memória para acesso rápido.

No entanto, se você modificar os arquivos de tradução enquanto seu aplicativo estiver em execução, poderá usar o método reloadTranslations para atualizar o cache na memória.

ts
import i18nManager from '@adonisjs/i18n/services/main'

await i18nManager.reloadTranslations()

Criando um carregador de tradução personalizado

Um carregador de traduções é responsável por carregar traduções de um armazenamento persistente. Nós enviamos com um carregador de sistema de arquivos e fornecemos uma API para registrar carregadores personalizados.

Um carregador deve implementar a interface TranslationsLoaderContract e definir o método load que retorna um objeto com par chave-valor. A chave é o código de localidade e o valor é um objeto simples com uma lista de traduções.

ts
import type {
  LoaderFactory,
  TranslationsLoaderContract,
} from '@adonisjs/i18n/types'

/**
 * Tipo para a configuração
 */
export type DbLoaderConfig = {
  connection: string
  tableName: string
}

/**
 * Implementação do carregador
 */
export class DbLoader implements TranslationsLoaderContract {
  constructor(public config: DbLoaderConfig) {
  }

  async load() {
    return {
      en: {
        'messages.greeting': 'Hello world',
      },
      fr: {
        'messages.greeting': 'Bonjour le monde',
      }
    }
  }
}

/**
 * Função de fábrica para referenciar o carregador
 * dentro do arquivo de configuração.
 */
export function dbLoader(config: DbLoaderConfig): LoaderFactory {
  return () => {
    return new DbLoader(config)
  }
}

No exemplo de código acima, exportamos os seguintes valores.

  • DbLoaderConfig: tipo TypeScript para a configuração que você deseja aceitar.
  • DbLoader: a implementação dos carregadores como uma classe. Ele deve aderir à interface TranslationsLoaderContract.
  • dbLoader: finalmente, uma função de fábrica para referenciar o carregador dentro do arquivo de configuração.

Usando o carregador

Depois que o carregador for criado, você pode referenciá-lo dentro do arquivo de configuração usando a função de fábrica dbLoader.

ts
import { defineConfig } from '@adonisjs/i18n'
import { dbLoader } from 'my-custom-package'

const i18nConfig = defineConfig({
  loaders: [
    dbLoader({
      connection: 'pg',
      tableName: 'translations'
    })
  ]
})

Criando um formatador de tradução personalizado

Os formatadores de tradução são responsáveis ​​por formatar as traduções de acordo com um formato específico. Nós enviamos com uma implementação para a sintaxe de mensagem ICU e fornecemos APIs adicionais para registrar formatadores personalizados.

Um formatador deve implementar a interface TranslationsFormatterContract e definir o método format para formatar uma mensagem de tradução.

ts
import type {
  FormatterFactory,
  TranslationsLoaderContract,
} from '@adonisjs/i18n/types'

/**
 * Implementação do formatador
 */
export class FluentFormatter implements TranslationsFormatterContract {
  format(
    message: string,
    locale: string,
    data?: Record<string, any>
  ): string {
    // retorna valor formatado
  }
}

/**
 * Função de fábrica para referenciar o formatador
 * dentro do arquivo de configuração.
 */
export function fluentFormatter(): FormatterFactory {
  return () => {
    return new FluentFormatter()
  }
}

Usando o formatador

Depois que o formatador for criado, você pode referenciá-lo dentro do arquivo de configuração usando a função de fábrica fluentFormatter.

ts
import { defineConfig } from '@adonisjs/i18n'
import { fluentFormatter } from 'my-custom-package'

const i18nConfig = defineConfig({
  formatter: fluentFormatter()
})