Internacionalização com Nuxt i18n: guia prático para apps em produção cover image

Internacionalização com Nuxt i18n: guia prático para apps em produção

Publicar um app Nuxt em um idioma é simples. Publicar em três — ou dez — é outra história. Você precisa de textos consistentes, URLs corretas, formatação sensível ao locale e um fluxo que não quebre toda vez que o marketing adiciona uma nova string. O módulo @nuxtjs/i18n é a resposta padrão no ecossistema Nuxt e, quando configurado com intenção desde o início, permanece sustentável conforme o produto cresce.

Este guia foca em padrões que funcionam em projetos reais: setup amigável ao TypeScript, arquivos de locale com carregamento lazy, rotas pensadas para SEO e convenções que alinham tradutores e desenvolvedores.

Por que usar Nuxt i18n em vez de reinventar a roda

Internacionalização vai além de trocar texto. Envolve detecção de locale, prefixos de rota, formatação de datas e números, cadeias de fallback e, em alguns casos, layouts RTL. Reimplementar isso sobre o Vue I18n significa resolver de novo problemas que o módulo já cobre: middleware de redirecionamento, composables como useI18n(), integração com o roteamento baseado em arquivos do Nuxt e helpers para metadados hreflang.

O módulo encapsula o vue-i18n e estende com comportamentos específicos do Nuxt. Por isso a maioria dos apps Nuxt em produção escolhe @nuxtjs/i18n em vez de configurar vue-i18n manualmente em um plugin.

Instalação e configuração do módulo

No Nuxt 3, instale o módulo e registre-o na config:

npm install @nuxtjs/i18n

// nuxt.config.ts
export default defineNuxtConfig({
  modules: ['@nuxtjs/i18n'],
  i18n: {
    locales: [
      { code: 'en', language: 'en-US', file: 'en.json', name: 'English' },
      { code: 'pt', language: 'pt-BR', file: 'pt.json', name: 'Português' }
    ],
    defaultLocale: 'en',
    lazy: true,
    langDir: 'locales',
    strategy: 'prefix_except_default',
    detectBrowserLanguage: {
      useCookie: true,
      cookieKey: 'i18n_redirected',
      redirectOn: 'root'
    }
  }
})

Algumas decisões nesse trecho impactam o longo prazo:

  • lazy: true carrega apenas o arquivo do locale ativo, mantendo o bundle inicial menor quando há muitos idiomas.

  • strategy: 'prefix_except_default' produz /about em inglês e /pt/about em português — padrão comum e amigável ao SEO quando o inglês é o mercado padrão.

  • language (tag BCP 47) alimenta atributos de SEO e hints do navegador; code é a chave curta usada em URLs e pastas.

Organizando os arquivos de tradução

Mantenha JSONs fáceis de buscar, mas aninhados o suficiente para refletir domínios da interface. Uma estrutura prática agrupa chaves por feature:

{
  "nav": {
    "home": "Início",
    "blog": "Blog",
    "contact": "Contato"
  },
  "home": {
    "heroTitle": "Desenvolva mais rápido com Nuxt",
    "heroCta": "Começar agora"
  },
  "validation": {
    "required": "Este campo é obrigatório",
    "email": "Informe um e-mail válido"
  }
}

Defina regras de nomenclatura cedo: feature.section.element supera chaves anônimas como text1. Tradutores precisam de contexto; desenvolvedores precisam refatorar sem quebrar copy. Para pluralização e interpolação, a sintaxe do vue-i18n se aplica diretamente:

"cart": {
  "items": "Nenhum item | um item | {count} itens"
},
"welcome": "Olá, {name}!"

Nos templates, use o composable:

<script setup lang="ts">
const { t, locale, locales } = useI18n()
</script>

<template>
  <h2>{{ t('home.heroTitle') }}</h2>
  <p>{{ t('welcome', { name: user.name }) }}</p>
</template>

Em projetos TypeScript, habilite mensagens tipadas para que chaves incorretas falhem no build em vez de aparecerem cruas em produção.

Estratégias de roteamento e paths localizados

O Nuxt i18n pode prefixar todos os locales, só os não padrão ou evitar prefixos. A escolha afeta analytics, favoritos e indexação.

  • prefix: todo locale tem prefixo (/en/about, /pt/about). Explícito e simétrico; bom quando nenhum idioma é realmente padrão.

  • prefix_except_default: o locale padrão mantém URLs limpas; os demais são prefixados. Popular em produtos com foco inicial nos EUA e mercados localizados depois.

  • no_prefix: o locale fica em cookies ou headers, não na URL. URLs mais simples, porém piores para compartilhamento e sinais de SEO.

Também é possível definir paths localizados por página quando slugs devem traduzir:

// pages/about.vue
export default defineI18nRoute({
  paths: {
    en: '/about',
    pt: '/sobre'
  }
})

Slugs localizados melhoram a UX e podem ajudar relevância regional, mas exigem disciplina: cada página nova precisa de entradas por locale, ou você cai em rotas genéricas prefixadas.

Trocando idioma na interface

Um seletor de idioma deve alterar o locale e navegar para a rota equivalente. Use switchLocalePath() ou localePath() em vez de URLs fixas:

<NuxtLink
  v-for="item in locales"
  :key="item.code"
  :to="switchLocalePath(item.code)"
>
  {{ item.name }}
</NuxtLink>

Links hardcoded quebram ao renomear rotas ou adicionar slugs localizados. Os helpers mantêm a navegação alinhada à estratégia i18n automaticamente.

SEO, hreflang e paridade de conteúdo

SEO multilíngue falha quando páginas existem em um idioma mas não em outro, ou quando tags hreflang apontam para alternates errados. O Nuxt i18n expõe helpers de head e combina bem com useSeoMeta(). Para cada página indexável, garanta:

  • Toda versão de locale acessível em URL estável conforme sua estratégia.

  • Título e descrição traduzidos, não strings em inglês duplicadas com prefixo de locale.

  • Links canônicos e alternates refletindo as versões reais de idioma.

  • Páginas não traduzidas ocultas naquele locale ou com noindex até existir copy.

Paridade de conteúdo é decisão de produto, não só técnica. Engenharia pode ligar as rotas; stakeholders definem se tradução parcial é aceitável.

Formatando datas, números e moedas

Arquivos de tradução não são o lugar certo para datas formatadas. Use APIs Intl ou os formatos datetime e number do vue-i18n por locale:

i18n: {
  datetimeFormats: {
    en: { short: { year: 'numeric', month: 'short', day: 'numeric' } },
    pt: { short: { day: '2-digit', month: 'short', year: 'numeric' } }
  },
  numberFormats: {
    en: { currency: { style: 'currency', currency: 'USD' } },
    pt: { currency: { style: 'currency', currency: 'BRL' } }
  }
}

Referencie formatos nos templates com d() e n(). Centralizar formatos evita símbolos de moeda inconsistentes quando locale e país de cobrança divergem.

Dados de servidor e conteúdo de CMS

JSONs estáticos cobrem chrome da UI; sites com CMS costumam armazenar campos localizados como objetos aninhados (title.en, title.pt) ou entradas separadas por locale. Escolha um modelo e mantenha-o. No Nuxt, busque conteúdo específico do locale em useAsyncData com chave que inclua slug e locale para hidratação correta:

const { locale } = useI18n()
const { data: post } = await useAsyncData(
  () => `post-${slug}-${locale.value}`,
  () => $fetch(`/api/posts/${slug}?locale=${locale.value}`)
)

Chaves de cache devem incluir locale, ou você pode exibir texto em português sob URL em inglês após navegação no cliente.

Testes e checagens de CI que valem a pena

Deriva de tradução é silenciosa até alguém printar uma chave crua em produção. Guardas leves ajudam:

  • Compare árvores de chaves dos locales no CI e falhe em chaves ausentes.

  • Snapshot de páginas críticas por locale em testes end-to-end.

  • Build em staging com cada locale como padrão para pegar quebras de layout com textos mais longos.

Alemão e português brasileiro frequentemente expandem texto vinte a trinta por cento em relação ao inglês. Projete componentes que tolerem comprimentos variáveis em vez de labels com largura fixa.

Armadilhas comuns e como evitá-las

  • Misturar code e language tag: use code em rotas e pastas, language no atributo lang do HTML.

  • Traduzir em computed sem dependência do locale: garanta que mudanças reativas de locale disparem re-render.

  • Duplicar strings entre namespaces: frases compartilhadas ficam em um namespace common.

  • Carregar todos os locales eagerly: ative lazy loading ao passar de dois ou três arquivos de idioma.

  • Ignorar fallbackLocale: defina fallbacks sensatos para rollouts parciais não exibirem UI vazia.

Considerações finais

O Nuxt i18n funciona bem quando locales são tratados como superfícies de produto de primeira classe, não como planilha importada no fim do projeto. Invista cedo em estratégia de rotas, estrutura de arquivos, regras de SEO e chaves tipadas. O módulo cuida do mecânico — lazy loading, middleware, composables — para sua equipe focar em copy precisa e experiência consistente entre mercados.

Comece com dois locales, valide o fluxo com páginas reais e escale depois. Esse caminho incremental supera habilitar dez idiomas no lançamento e descobrir que CMS, URLs e QA nunca estiveram prontos.