> For the complete documentation index, see [llms.txt](https://docs.ipcheck.ing/llms.txt). Markdown versions of documentation pages are available by appending `.md` to page URLs; this page is available as [Markdown](https://docs.ipcheck.ing/developer/pt-br/development/i18n.md).

# i18n

Como as traduções funcionam: um registro central, pacotes que carregam sob demanda e uma tradução parcial disponível desde o primeiro dia.

Cada idioma que a interface oferece é uma linha em um registro central. O seletor de idioma, `<html lang>`, o mapa de carregamento preguiçoso, a correspondência com o idioma do navegador e a cadeia de fallback são todos derivados dele — nada sobre um idioma é codificado manualmente em outro lugar.

{% code title="common/locale-registry.js" %}

```js
export const LOCALES = [
  { code: 'en', nativeName: 'English', flag: 'us', apiTag: 'en', htmlLang: 'en', status: 'full' },
  // … uma linha por idioma registrado — o próprio arquivo é a lista atual …
  { code: 'es-MX', nativeName: 'Español (México)', flag: 'mx', apiTag: 'es', htmlLang: 'es-MX', status: 'beta' },
];
```

{% endcode %}

| Campo        | O que é                                                                                                                                                           |
| ------------ | ----------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| `code`       | O código da interface **e** o nome do arquivo do pacote (`frontend/locales/<code>.json`)                                                                          |
| `nativeName` | O nome que o seletor exibe, escrito como os próprios falantes o escrevem                                                                                          |
| `flag`       | Código de bandeira circular de duas letras para o ícone do seletor                                                                                                |
| `apiTag`     | A tag enviada às fontes de dados a montante para nomes de lugares — a tag mais próxima que elas publicam, não necessariamente o código da interface               |
| `htmlLang`   | A tag BCP-47 gravada em `<html lang>` — `zh` declara `zh-CN` para que os glifos Han permaneçam em Simplificado em sistemas japoneses, enquanto `zh-TW` se declara |
| `status`     | `full` ou `beta` — veja [Completo e beta](#full-and-beta)                                                                                                         |

A ordem do registro também é a ordem em que o seletor renderiza. O arquivo fica em `common/`; o front-end o importa por meio da ponte fina `frontend/utils/locale-registry.js`.

## Configuração

O app usa **vue-i18n** no modo Composition API. A instância é criada em `frontend/locales/i18n.js` com `legacy: false`, então registrada em `main.js`.

Em um componente, você extrai `t()` de `useI18n()`:

```vue
<script setup>
import { useI18n } from 'vue-i18n';
const { t } = useI18n();
</script>

<template>
  <p>{{ t('macchecker.Note') }}</p>
</template>
```

Nada visível ao usuário é codificado manualmente. Toda string passa por `t()`.

## Arquivos de localidade

Cada arquivo de localidade é um objeto JSON, com namespace por recurso. Os namespaces de nível superior incluem `nav`, `advancedtools`, `page`, `changelog`, e um por ferramenta — `macchecker`, `whois`, `dnsresolver`, `censorshipcheck`, e assim por diante.

O namespace de uma ferramenta normalmente corresponde ao seu slug no registro:

{% code title="frontend/locales/en.json" %}

```json
"macchecker": {
  "Title": "Consulta de MAC",
  "Note": "Consulte o fabricante de um endereço físico (endereço MAC)…",
  "Note2": "Digite um endereço físico para iniciar a consulta:",
  "Placeholder": "F0:2F:4B:01:0A:AA",
  "invalidMAC": "Endereço físico inválido",
  "fetchError": "Não foi possível buscar os resultados da consulta"
}
```

{% endcode %}

A descrição do cartão na página inicial fica separadamente, no namespace compartilhado, porque é para isso que o registro de ferramentas aponta `advancedtools` namespace `noteKey` para:

```json
"advancedtools": {
  "MacChecker": "Consulta de informações de um endereço físico"
}
```

**`en.json` é a referência.** Todo outro pacote carrega exatamente os mesmos caminhos de chaves — nem mais, nem menos. Apenas os valores diferem.

### Valores não traduzidos são `""`, não chaves ausentes

Um pacote é um esqueleto completo de `en.json`, e uma string que ninguém traduziu ainda permanece no arquivo como `""`. É isso que torna um diff de tradução legível: cada linha é uma `""` se tornando uma frase, e nada pode se esconder entre "ainda não traduzido" e "esquecido".

`""` é apenas um marcador de nível de código-fonte. Um plugin do Vite (`localeStripPlugin` em `vite.config.js`, apoiado por `stripUntranslated()` em `common/locale-pack.js`) passa cada pacote por uma etapa de remoção ao entrar no bundle, então em tempo de execução um valor não traduzido e uma chave ausente são a mesma coisa — um buraco para a cadeia de fallback preencher. Arrays são tudo ou nada: uma entrada vazia faz o array inteiro cair no fallback, porque remover um elemento deslocaria o restante.

## O carregamento é sob demanda

Os pacotes nunca são carregados juntos. Agrupá-los todos de forma antecipada custava cerca de 44 KB compactados em lixo na época em que eram quatro — toda localidade, exceto a ativa, em cada carregamento de página, e cada idioma adicionado desde então teria piorado isso.

Em vez disso `frontend/locales/i18n.js` descobre os pacotes por glob e deixa o registro decidir quais deles a interface oferece:

{% code title="frontend/locales/i18n.js" %}

```js
const localePacks = import.meta.glob('./*.json');
const localeLoaders = Object.fromEntries(
  LOCALE_CODES
    .filter((code) => localePacks[`./${code}.json`])
    .map((code) => [code, localePacks[`./${code}.json`]]),
);
```

{% endcode %}

Adicionar um arquivo de pacote, portanto, não basta para fazer um idioma aparecer, e uma linha no registro sem um pacote falha nos testes. Os dois precisam concordar.

A instância de i18n começa com **vazias** mensagens. `loadActiveLocaleMessages()` injeta toda a cadeia de fallback da localidade ativa (em paralelo, memorizada), e `main.js` aguarda isso antes da montagem — então a primeira renderização já sai traduzida. Trocar de idioma persiste a escolha e reinicia o app, o que significa que apenas uma localidade fica ativa por carregamento de página.

### A cadeia de fallback

`fallbackChain()` no registro é a definição única do caminho que uma string ausente percorre: **a localidade → seu idioma base, se o idioma base estiver registrado → `en`.**

* `fr` → `en`
* `zh-TW` → `zh` → `en`

o vue-i18n só consegue fazer fallback para mensagens que realmente estão na instância, e é por isso que `loadActiveLocaleMessages()` carrega toda a cadeia em vez de apenas a localidade ativa. O texto de privacidade e o checklist de segurança percorrem a mesma cadeia, mas um arquivo inteiro de cada vez.

Os avisos de chave ausente e de fallback são desativados de propósito: um pacote beta resolvendo pela cadeia é um estado suportado, não um erro.

### Como o idioma é escolhido

`setLanguage()` em `frontend/locales/i18n.js` resolve, nesta ordem:

1. **Preferência salva** em `localStorage`, se ela nomear uma localidade que tenha um pacote.
2. **`?hl=` parâmetro de consulta.**
3. **Idioma do navegador** (`navigator.language`).
4. **`en`.**

Os dois últimos passam por `matchLocale()`, que resolve uma tag BCP-47 em três etapas — **exata, depois o idioma base, depois qualquer localidade registrada da mesma família**, com a ordem do registro decidindo entre semelhantes. Então `?hl=zh-TW` corresponde exatamente, `?hl=zh-CN` não tem correspondência exata e cai no base `zh`, e um `pt-PT` navegador cai no `pt-BR` pacote por família. Uma preferência salva vence `?hl=`, então o parâmetro de consulta só ajuda um visitante que ainda não escolheu.

Um plugin do Vite em tempo de build (`localePreloadPlugin` em `vite.config.js`) reproduz essa ordem exata — incluindo a correspondência em três etapas e a cadeia de fallback — em um pequeno `<head>` script, e emite um `<link rel="modulepreload">` para cada pacote da cadeia enquanto o HTML ainda está sendo transmitido, de modo que a localidade seja baixada em paralelo com o bundle principal, e não depois dele. Um palpite errado só desperdiça um preload; a importação real ainda é quem decide.

Depois que as mensagens carregam, `updateMeta()` define `document.documentElement.lang` a partir do `htmlLang` e atualiza o `título`, `palavras-chave`, e `descrição` as meta tags de `page.*` chaves.

## Subpacotes

Dois conjuntos de dados são grandes o suficiente para ficar fora do pacote principal de localidade e carregar sob demanda apenas para a localidade ativa:

<table><thead><tr><th width="300">Subpacote</th><th>Carregado por</th></tr></thead><tbody><tr><td><code>frontend/locales/security-checklist/&#x3C;code>.json</code></td><td><code>SecurityChecklist.vue</code> — seu próprio glob, indexado pelo código da localidade; o conjunto de dados tem cerca de 30 KB compactados por idioma e só essa ferramenta o lê.</td></tr><tr><td><code>frontend/locales/privacy/&#x3C;code>.json</code></td><td><code>PrivacyPolicy.vue</code> — mesclado no i18n via <code>mergeLocaleMessage()</code> para que <code>t()</code> e <code>tm()</code> resolva o texto normalmente.</td></tr></tbody></table>

Ambos seguem o mesmo padrão: carregadores descobertos por glob, e uma localidade sem arquivo próprio usa o primeiro da sua cadeia de fallback que tiver um.

{% hint style="warning" %}
**Esses dois são entregues por inteiro ou não são entregues de forma alguma.** Ao contrário do pacote principal, eles carregam como um arquivo cada um, sem fallback por chave internamente, então um incompleto renderiza uma página meio em inglês. Os testes rejeitam um `""` em qualquer um dos dois.
{% endhint %}

{% hint style="info" %}
**Quando adicionar um subpacote:** um conjunto de dados grande, pertencente a exatamente uma visualização carregada sob demanda, e que de outra forma ficaria no bundle da primeira renderização de todo visitante. Strings normais da interface sempre vão no pacote principal.
{% endhint %}

## Completo e beta

`status` no registro decide o quanto se exige de um idioma.

| O que                                         | `full`                                        | `beta`                               |
| --------------------------------------------- | --------------------------------------------- | ------------------------------------ |
| Pacote principal                              | Cada valor traduzido — um `""` falha na build | Qualquer número de `""`              |
| Texto de privacidade · checklist de segurança | Ambos obrigatórios, ambos completos           | Opcional — mas completo, se presente |
| Histórico do changelog                        | Obrigatório para cada entrada                 | Isento                               |
| No seletor                                    | Simples                                       | Carrega um pequeno **Beta** selo     |

Toda localidade registrada é uma coisa ou outra, e `common/locale-registry.js` é a lista atual de quem possui qual status. Um idioma novo começa como `beta`; promovê-lo é uma decisão da manutenção, tomada quando o idioma está realmente completo e as mudanças de texto continuaram chegando nele.

{% hint style="danger" %}
**Qualquer mudança que exponha texto chega em toda `full` localidade na mesma alteração.** Não é um PR de acompanhamento, nem um TODO. Isso significa que uma nova chave vai para todos eles, uma string reescrita é reescrita em todos eles, uma chave excluída é excluída de todos eles, e o correspondente `changelog.json` registro leva todas as suas traduções.
{% endhint %}

cobertura parcial em uma localidade é fácil de passar despercebida na revisão. Não confie nisso; os testes abaixo é que realmente pegam isso. `full` Ferramentas

## Ferramentas

Três comandos, todos scripts Node simples em `scripts/`:

| Comando                | O que faz                                                                                                                                                                                                                                                                    |
| ---------------------- | ---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| `pnpm i18n-new <code>` | Gera a estrutura de um idioma: um`""` esqueleto completo de `en.json`, além de um `status: 'beta'` linha no registro. `--privacy` / `--checklist` gera a estrutura dos dois arquivos opcionais (ambos precisam que o idioma esteja registrado antes).                        |
| `pnpm i18n-status`     | Relatório de progresso por idioma e por conjunto de dados — percentual traduzido, as próximas chaves a fazer e valores byte a byte idênticos ao inglês. Nunca falha; `--locale` e `--limit` reduza o escopo.                                                                 |
| `pnpm i18n-sync`       | Realinha cada pacote com `en.json` após mudanças no inglês: adiciona novas chaves como `""`, remove chaves que o inglês não tem mais, restaura a ordem das chaves do inglês. Nunca sobrescreve uma tradução e, para os dois arquivos opcionais, apenas *relata* o que mudou. |

## Adicionando um idioma

É uma alteração de front-end, e uma **parcial** tradução é um PR bem-vindo — tudo que ficar como `""` faz fallback para o inglês, então um pacote com cem strings já é útil no primeiro dia e pode crescer ao longo de vários PRs.

```bash
pnpm i18n-new es-MX
```

Isso cria os dois arquivos de que um idioma precisa — `frontend/locales/es-MX.json` e uma linha em `common/locale-registry.js`. Nada mais: nenhuma alteração no backend, nenhuma configuração de build, nenhuma edição de componente.

Há duas regras de nomenclatura que vale conhecer antes de escolher um código: a **variante padrão de um idioma usa o código puro** (`zh` é Chinês Simplificado), e **variantes que chegam depois usam o código de região** (`zh-TW`, `pt-BR`), que então herdam o base como fallback.

{% content-ref url="/pages/0584874b785a3d37f5ae6f0c6392446b8c6b6e64" %}
[Traduzindo o MyIP](/developer/pt-br/contributing/translating.md)
{% endcontent-ref %}

O passo a passo completo para contribuintes — escolhendo `apiTag`, o que `pnpm test` impõe linha por linha, o que parecerá não traduzido não importa o quão completo seu pacote esteja — fica lá.

## O changelog

As notas de versão ficam em `frontend/data/changelog.json`, não nos arquivos de localidade. O arquivo é um array de blocos de versão, **do mais antigo para o mais novo** — o painel Sobre o renderiza invertido, então novas entradas são acrescentadas ao último bloco.

{% code title="frontend/data/changelog.json" %}

```json
{
  "version": "v7.2.0",
  "date": "Beta",
  "content": [
    {
      "type": "add",
      "change": {
        "en": "Reconstruímos o Censorship Check: veja onde um site é bloqueado no mundo todo",
        "zh": "Teste de censura completamente reformulado: confira onde um site é bloqueado no mundo todo",
        "zh-TW": "Teste de censura completamente reformulado: confira onde um site é bloqueado no mundo todo",
        "fr": "Reformulação completa do teste de censura: descubra onde um site é bloqueado no mundo",
        "ru": "A verificação de censura foi completamente reformulada: veja onde um site está bloqueado no mundo"
      }
    }
  ]
}
```

{% endcode %}

Regras de formato:

| Campo       | Regra                                                                                   |
| ----------- | --------------------------------------------------------------------------------------- |
| `version`   | Correspondência de string `vX.Y…`                                                       |
| `date`      | ISO `YYYY-MM-DD`, ou o placeholder `"Beta"`                                             |
| `conteúdo`  | Array não vazio de itens de alteração                                                   |
| `tipo`      | Exatamente um de `add`, `improve`, `fix`                                                |
| `alteração` | Uma string não vazia para **cada `full` localidade**; as localidades beta estão isentas |

Retraduzir mais de cem entradas do histórico é, de longe, o maior desestímulo a um primeiro PR de tradução, por isso os idiomas beta são dispensados disso — e por isso o preenchimento retroativo é uma das condições para promoção a `full`.

## Imposto por testes

Seis especificações cobrem partes do que foi dito acima, e elas são executadas em cada `pnpm test`.

| Especificação                   | Cobre                                                                                                                                                                                                                                                                                                                    |
| ------------------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ |
| `tests/locale-packs.test.js`    | A barreira rígida. O registro e os arquivos concordam; cada pacote tem exatamente `en`as chaves de`{count}`, `{name}`) são um subconjunto do inglês; `slug` e `prioridade` na lista de verificação permanecem sem tradução; nenhum `""` nos dois arquivos opcionais; nenhum `""` em nenhum lugar em um `full` localidade |
| `tests/locale-registry.test.js` | Formato da entrada e os mapeamentos compartilhados — `apiTag`, `htmlLang`, a cadeia de fallback, a correspondência de tags                                                                                                                                                                                               |
| `tests/locale-pack.test.js`     | A `""` a parte em tempo de execução da convenção: achatamento e o que a remoção em tempo de build faz com objetos e arrays                                                                                                                                                                                               |
| `tests/changelog.test.js`       | Formato do changelog e uma tradução para cada `full` localidade. Também que os arquivos de localidade não carregam mais `changelog.versions`, e ainda carregam o `changelog.Title` / `add` / `improve` / `fix` rótulos da interface                                                                                      |
| `tests/persona-i18n.test.js`    | O vocabulário do In-depth Persona Check — os IDs das verificações, veredictos, graus, eixos, motivos de não aplicabilidade e chaves de detalhes declaradas em `frontend/utils/persona/check-ids.js` — tem texto em cada `full` pacote, e não sobra nada                                                                  |
| `tests/index-html-i18n.test.js` | `index.html`as três cópias inline mantidas manualmente (frases da tela de inicialização, JSON-LD e o seletor de idioma) correspondem ao registro                                                                                                                                                                         |

Essa última vale a pena conhecer quando você adiciona um idioma: a tela de carregamento pré-Vue traz seu próprio pequeno conjunto de strings, porque ela é executada antes de o app e seu bundle existirem. Idiomas beta voltam para o inglês ali por design.

A especificação do changelog também protege uma divisão que vale lembrar: **o texto da nota de lançamento fica em `changelog.json`; os rótulos de badge e o título do painel ao redor permanecem nos arquivos de localidade** como elementos comuns da interface.

## O backend não participa

Quais idiomas a **interface** disponibiliza é uma responsabilidade do front-end. Quais idiomas os dados upstream **dados** vem é um conjunto separado, pertencente à origem — e nenhum handler valida `?lang` contra nada.

* `/api/maxmind` passa a tag bruta para `lookupMaxMind()`, que a normaliza para os idiomas que o banco de dados City embarcado realmente contém (`SUPPORTED_LANGS` em `common/maxmind-service.js`: `de`, `en`, `es`, `fr`, `ja`, `pt-BR`, `ru`, `zh-CN`) usando a mesma correspondência exata → base → família, com fallback para `en`.
* Os proxies para a API privada IPCheck.ing encaminham a tag do chamador sem alterações; esse upstream é dono da própria resolução.

A tag que o front-end envia é a do registro `apiTag`, e não a do código da interface — esse é o motivo de a coluna existir. `zh-TW` envia `zh-CN`, porque nenhum upstream publica nomes de lugares em chinês tradicional, então uma interface em chinês tradicional mostra os em chinês simplificado. Onde nem mesmo existe uma tag próxima, os nomes dos lugares são exibidos em inglês ao lado de uma interface totalmente traduzida, o que é esperado e não é bug. Nomes de países, datas e números não são afetados: eles vêm do próprio `Intl` dados e localizam para qualquer idioma.

## Próximo

* [Traduzindo o MyIP](/developer/pt-br/contributing/translating.md) — o guia para contribuidores de um idioma novo ou aprimorado.
* [Adicionando uma Nova Ferramenta](/developer/pt-br/development/adding-a-new-tool.md) — onde a etapa de i18n se encaixa em uma funcionalidade completa.
* [Testando](/developer/pt-br/development/testing.md) — o que mais `pnpm test` impõe.
* [Como Contribuir](/developer/pt-br/contributing/how-to-contribute.md) — expectativas para PRs.


---

# Agent Instructions
This documentation is published with GitBook. GitBook is the documentation platform designed so that both humans and AI agents can read, navigate, and reason over technical content effectively. Learn more at gitbook.com.

## Querying This Documentation
If you need additional information that is not directly available in this page, you can query the documentation dynamically by asking a question.

Perform an HTTP GET request on the current page URL with the `ask` query parameter, and the optional `goal` query parameter:

```
GET https://docs.ipcheck.ing/developer/pt-br/development/i18n.md?ask=<question>&goal=<endgoal>
```

`ask` is the immediate question: it should be specific, self-contained, and written in natural language.
`goal` is optional and describes the broader end goal you are ultimately trying to accomplish on behalf of the user. GitBook uses it to tailor the answer towards what is most useful for that goal.

The response will contain a direct answer to the question and relevant excerpts and sources from the documentation.

Use this mechanism when the answer is not explicitly present in the current page, you need clarification or additional context, or you want to retrieve related documentation sections.
