Alternar tema

Guia completo de internacionalização no Next.js: boas práticas com next-intl

Easton editorial illustration: build pipeline conveyor

No ano passado, assumi um projeto em Next.js que precisava oferecer suporte a vários idiomas. Quando vi todas aquelas configurações de i18n nos arquivos, fiquei um pouco perdido. Depois de passar um bom tempo lendo a documentação, descobri que o App Router e o antigo Pages Router seguem abordagens completamente diferentes para internacionalização. Levei uma semana para deixar o next-intl funcionando e, nesse processo, também tropecei em vários problemas.

Hoje quero falar sobre as opções de internacionalização no Next.js, especialmente sobre como usar o next-intl para implementar vários idiomas de forma elegante com o App Router.

Por que escolher o next-intl?

Talvez você esteja se perguntando: o Next.js já não oferece internacionalização? De fato, na época do Pages Router, o Next.js tinha suporte integrado a rotas i18n. No App Router, porém, esse recurso foi removido.

A recomendação oficial é usar uma biblioteca de terceiros. E o next-intl é uma das opções mais populares.

Vantagens do next-intl:

  • Suporte nativo ao App Router — foi pensado especificamente para o App Router e é muito prático de usar
  • Segurança de tipos — com TypeScript, é possível verificar os tipos dos textos traduzidos
  • Estratégias flexíveis de rotas — oferece várias formas de trocar o idioma, como subcaminhos, domínios e cookies
  • Recursos avançados — oferece pluralização, formatação de datas e números, rich text e muito mais
  • Ótimo desempenho — funciona bem com Server Components e oferece suporte à renderização estática

Em comparação com outras opções, a documentação do next-intl também é bastante clara, o que torna o aprendizado menos sofrido.

Configuração básica: começando do zero

1. Instale a dependência

O primeiro passo, claro, é instalar o next-intl:

npm install next-intl
# ou
pnpm add next-intl
# ou
yarn add next-intl

2. Crie os arquivos de tradução

Crie uma pasta messages na raiz do projeto — ela também pode se chamar locales ou ter outro nome — e depois crie um arquivo JSON para cada idioma:

messages/
├── en.json
├── zh.json
└── ja.json

messages/zh.json:

{
  "HomePage": {
    "title": "欢迎来到我的网站",
    "description": "这是一个支持多语言的 Next.js 应用"
  },
  "Navigation": {
    "home": "首页",
    "about": "关于",
    "contact": "联系我们"
  }
}

messages/en.json:

{
  "HomePage": {
    "title": "Welcome to My Website",
    "description": "This is a multilingual Next.js application"
  },
  "Navigation": {
    "home": "Home",
    "about": "About",
    "contact": "Contact Us"
  }
}

Essa estrutura aninhada não é obrigatória, mas percebi que agrupar as traduções por página ou componente facilita muito o gerenciamento.

3. Configure o i18n.ts

Crie i18n.ts — ou i18n/config.ts — para configurar os idiomas aceitos:

import { getRequestConfig } from 'next-intl/server';

export default getRequestConfig(async ({ locale }) => ({
  messages: (await import(`./messages/${locale}.json`)).default
}));

Essa configuração informa ao next-intl onde encontrar os arquivos de tradução. O parâmetro locale é extraído automaticamente da URL.

4. Crie o middleware

Crie middleware.ts na raiz do projeto. Ele é a peça central do processamento das rotas multilíngues:

import createMiddleware from 'next-intl/middleware';

export default createMiddleware({
  // Lista de idiomas aceitos
  locales: ['en', 'zh', 'ja'],

  // Idioma padrão
  defaultLocale: 'zh',

  // Define se o idioma padrão sempre aparece na URL
  localePrefix: 'as-needed'
});

export const config = {
  // Corresponde a todos os caminhos, exceto api, _next/static, _next/image e favicon.ico
  matcher: ['/', '/(zh|en|ja)/:path*', '/((?!api|_next|_next/static|_next/image|favicon.ico).*)']
};

Opções de localePrefix:

  • 'always' — todos os idiomas exibem o prefixo, inclusive o idioma padrão (/zh/about, /en/about)
  • 'as-needed' — o idioma padrão não exibe o prefixo (/about, /en/about)
  • 'never' — nenhum idioma exibe o prefixo; é preciso identificar o idioma de outra forma, como pelo domínio

Normalmente uso 'as-needed', porque é mais amigável para usuários de chinês e também deixa a URL mais limpa.

5. Ajuste a estrutura da pasta app

Esta é a etapa mais importante. Você precisa mover todas as rotas para dentro da rota dinâmica [locale]:

Estrutura anterior:

app/
├── page.tsx
├── about/
│   └── page.tsx
└── layout.tsx

Estrutura depois do ajuste:

app/
├── [locale]/
│   ├── page.tsx
│   ├── about/
│   │   └── page.tsx
│   └── layout.tsx
└── layout.tsx (opcional, para configurações globais)

6. Configure o layout raiz

Configure o idioma em app/[locale]/layout.tsx:

import { NextIntlClientProvider } from 'next-intl';
import { getMessages } from 'next-intl/server';
import { notFound } from 'next/navigation';

const locales = ['en', 'zh', 'ja'];

export function generateStaticParams() {
  return locales.map((locale) => ({ locale }));
}

export default async function LocaleLayout({
  children,
  params: { locale }
}: {
  children: React.ReactNode;
  params: { locale: string };
}) {
  // Verifica se o idioma é aceito
  if (!locales.includes(locale)) {
    notFound();
  }

  const messages = await getMessages();

  return (
    <html lang={locale}>
      <body>
        <NextIntlClientProvider messages={messages}>
          {children}
        </NextIntlClientProvider>
      </body>
    </html>
  );
}

Observe o generateStaticParams: se você usar geração estática, essa função informa ao Next.js para quais idiomas as páginas devem ser geradas.

Como usar traduções nos componentes

Depois de concluir a configuração, você já pode usar as traduções nos componentes.

Uso em um Server Component

import { useTranslations } from 'next-intl';

export default function HomePage() {
  const t = useTranslations('HomePage');

  return (
    <div>
      <h1>{t('title')}</h1>
      <p>{t('description')}</p>
    </div>
  );
}

O parâmetro de useTranslations é o namespace do arquivo de tradução, ou seja, a chave do nível mais externo. Se você não passar um parâmetro, pode usar o caminho completo, como t('HomePage.title').

Uso em um Client Component

Em um Client Component, o modo de uso é exatamente o mesmo:

'use client';

import { useTranslations } from 'next-intl';

export default function Navigation() {
  const t = useTranslations('Navigation');

  return (
    <nav>
      <a href="/">{t('home')}</a>
      <a href="/about">{t('about')}</a>
      <a href="/contact">{t('contact')}</a>
    </nav>
  );
}

Essa é uma das grandes vantagens do next-intl: o uso é consistente entre Server e Client Components.

Como processar rotas multilíngues

Como obter o idioma atual

import { useLocale } from 'next-intl';

export default function LanguageSwitcher() {
  const locale = useLocale();

  return <div>Current language: {locale}</div>;
}

Como criar um seletor de idioma

Esse é um recurso necessário em todo site internacionalizado:

'use client';

import { useLocale } from 'next-intl';
import { usePathname, useRouter } from 'next/navigation';

export default function LanguageSwitcher() {
  const locale = useLocale();
  const router = useRouter();
  const pathname = usePathname();

  const switchLanguage = (newLocale: string) => {
    // Substitui a parte do idioma no caminho
    const newPath = pathname.replace(`/${locale}`, `/${newLocale}`);
    router.push(newPath);
  };

  return (
    <select value={locale} onChange={(e) => switchLanguage(e.target.value)}>
      <option value="zh">中文</option>
      <option value="en">English</option>
      <option value="ja">日本語</option>
    </select>
  );
}

Mas essa solução tem um pequeno problema: quando o usuário está em uma página no idioma padrão — chinês, por exemplo —, a URL é /about; depois de mudar para inglês, ela deveria se tornar /en/about. Por isso, o código acima precisa de um ajuste:

const switchLanguage = (newLocale: string) => {
  // Remove o prefixo do idioma atual
  let path = pathname;
  if (pathname.startsWith(`/${locale}`)) {
    path = pathname.substring(locale.length + 1);
  }

  // Adiciona o prefixo do novo idioma, a menos que ele seja o padrão com as-needed
  const newPath = newLocale === 'zh' ? path : `/${newLocale}${path}`;
  router.push(newPath);
};

O next-intl oferece uma versão aprimorada do componente Link, que processa automaticamente o prefixo do idioma:

import { Link } from '@/navigation'; // Requer configuração prévia

<Link href="/about">
  {t('about')}
</Link>

Configure navigation.ts:

import { createSharedPathnamesNavigation } from 'next-intl/navigation';

export const locales = ['en', 'zh', 'ja'] as const;
export const localePrefix = 'as-needed';

export const { Link, redirect, usePathname, useRouter } =
  createSharedPathnamesNavigation({ locales, localePrefix });

Os componentes exportados, como Link e useRouter, passam a processar automaticamente os caminhos de cada idioma.

Recursos avançados

1. Traduções com parâmetros

É comum precisar inserir variáveis em um texto traduzido. O next-intl permite fazer isso desta forma:

messages/zh.json:

{
  "welcome": "欢迎回来,{username}!",
  "items": "你有 {count} 个新消息"
}

Uso:

const t = useTranslations();

<p>{t('welcome', { username: 'John' })}</p>
<p>{t('items', { count: 5 })}</p>

2. Tratamento de plural

As regras de plural variam entre os idiomas, e o next-intl oferece t.rich para tratá-las:

messages/en.json:

{
  "messages": {
    "one": "You have {count} message",
    "other": "You have {count} messages"
  }
}

Uso:

t('messages', { count: 1 })  // "You have 1 message"
t('messages', { count: 5 })  // "You have 5 messages"

O chinês não distingue plural da mesma maneira, então você pode escrever assim:

messages/zh.json:

{
  "messages": "你有 {count} 条消息"
}

3. Formatação de datas e números

O next-intl oferece funções específicas de formatação:

import { useFormatter } from 'next-intl';

export default function DateExample() {
  const format = useFormatter();
  const now = new Date();

  return (
    <div>
      <p>{format.dateTime(now, { dateStyle: 'full' })}</p>
      {/* Chinês: 2025年12月25日星期三 */}
      {/* Inglês: Wednesday, December 25, 2025 */}

      <p>{format.number(1234567.89, { style: 'currency', currency: 'CNY' })}</p>
      {/* Chinês: ¥1,234,567.89 */}
      {/* Inglês: CN¥1,234,567.89 */}
    </div>
  );
}

4. Tradução com rich text

Às vezes, o conteúdo traduzido precisa incluir tags HTML ou componentes React:

messages/zh.json:

{
  "richText": "我同意<terms>服务条款</terms>和<privacy>隐私政策</privacy>"
}

Uso:

import { useTranslations } from 'next-intl';

export default function Agreement() {
  const t = useTranslations();

  return (
    <p>
      {t.rich('richText', {
        terms: (chunks) => <a href="/terms">{chunks}</a>,
        privacy: (chunks) => <a href="/privacy">{chunks}</a>
      })}
    </p>
  );
}

Boas práticas para gerenciar arquivos de tradução

À medida que o projeto cresce, os arquivos de tradução ficam cada vez mais difíceis de gerenciar. Estas são algumas dicas práticas:

1. Divida por módulo funcional

Não coloque todas as traduções em um único arquivo JSON enorme. Separe-as por página ou funcionalidade:

messages/
├── zh/
│   ├── common.json      # Traduções comuns, como botões e mensagens de erro
│   ├── home.json        # Página inicial
│   ├── about.json       # Página Sobre
│   └── auth.json        # Autenticação
├── en/
│   ├── common.json
│   ├── home.json
│   ├── about.json
│   └── auth.json

Depois, combine os arquivos em i18n.ts:

import { getRequestConfig } from 'next-intl/server';

export default getRequestConfig(async ({ locale }) => {
  const common = (await import(`./messages/${locale}/common.json`)).default;
  const home = (await import(`./messages/${locale}/home.json`)).default;
  const about = (await import(`./messages/${locale}/about.json`)).default;
  const auth = (await import(`./messages/${locale}/auth.json`)).default;

  return {
    messages: {
      common,
      home,
      about,
      auth
    }
  };
});

2. Use a verificação de tipos do TypeScript

Esse é um recurso que considero especialmente útil. Defina o tipo dos arquivos de tradução para evitar erros de digitação:

types/i18n.ts:

import zh from '@/messages/zh.json';

type Messages = typeof zh;

declare global {
  interface IntlMessages extends Messages {}
}

Configure o TypeScript em tsconfig.json:

{
  "compilerOptions": {
    "types": ["./types/i18n"]
  }
}

Assim, ao usar t('xxx'), o TypeScript informa um erro se a chave não existir. É muito útil!

3. Extraia as traduções compartilhadas

Alguns textos comuns — como “salvar”, “cancelar” e “confirmar” — aparecem em muitos lugares. O ideal é gerenciá-los separadamente:

messages/zh/common.json:

{
  "actions": {
    "save": "保存",
    "cancel": "取消",
    "delete": "删除",
    "confirm": "确认",
    "edit": "编辑"
  },
  "status": {
    "success": "操作成功",
    "error": "操作失败",
    "loading": "加载中..."
  }
}

Uso:

const t = useTranslations('common.actions');
<button>{t('save')}</button>

4. Use ferramentas de gerenciamento de traduções

Quando o projeto cresce, manter os arquivos JSON manualmente pode se tornar trabalhoso. Você pode considerar estas ferramentas:

  • Tolgee — plataforma open source de gerenciamento de traduções com edição em tempo real
  • Localazy — fluxo automatizado de tradução
  • i18n Ally (extensão para VSCode) — gerencia traduções diretamente no editor

Hoje uso principalmente o i18n Ally, porque consigo ver o conteúdo traduzido enquanto escrevo o código e também fazer alterações com facilidade.

5. Trate traduções ausentes

Durante o desenvolvimento, é comum encontrar um idioma cuja tradução ainda não foi concluída. Você pode configurar um fallback:

// i18n.ts
export default getRequestConfig(async ({ locale }) => {
  const messages = (await import(`./messages/${locale}.json`)).default;
  const fallback = locale !== 'zh'
    ? (await import(`./messages/zh.json`)).default
    : {};

  return {
    messages: {
      ...fallback,
      ...messages
    }
  };
});

Assim, quando faltar uma tradução em inglês, o sistema recorrerá automaticamente ao chinês.

Otimização de desempenho

1. Divisão de código

Se os arquivos de tradução forem muito grandes, você pode carregá-los sob demanda:

// Carrega apenas quando necessário
export default function AdminPage() {
  const t = useTranslations('admin'); // Carrega apenas o namespace admin
  // ...
}

2. Geração estática

Para páginas que mudam pouco, a geração estática pode melhorar muito o desempenho:

// app/[locale]/about/page.tsx
export const dynamic = 'force-static';

export function generateStaticParams() {
  return [
    { locale: 'zh' },
    { locale: 'en' },
    { locale: 'ja' }
  ];
}

3. Pré-carregamento das traduções

Para o conteúdo que aparece na primeira tela, você pode pré-carregar o arquivo de tradução:

import { getTranslations } from 'next-intl/server';

// Pré-carrega no servidor
export default async function Home() {
  const t = await getTranslations('HomePage');

  return <h1>{t('title')}</h1>;
}

Problemas comuns e soluções

1. Troca de idioma em rotas dinâmicas

Ao trocar o idioma em uma rota dinâmica, como /blog/[slug], você precisa manter o slug:

const switchLanguage = (newLocale: string) => {
  const segments = pathname.split('/').filter(Boolean);
  // Remove o prefixo do idioma anterior
  if (['zh', 'en', 'ja'].includes(segments[0])) {
    segments.shift();
  }
  // Adiciona o prefixo do novo idioma, se necessário
  if (newLocale !== 'zh' || localePrefix === 'always') {
    segments.unshift(newLocale);
  }
  router.push('/' + segments.join('/'));
};

2. Otimização de SEO

O SEO de um site multilíngue exige atenção especial:

// app/[locale]/layout.tsx
export async function generateMetadata({ params: { locale } }) {
  const t = await getTranslations({ locale, namespace: 'metadata' });

  return {
    title: t('title'),
    description: t('description'),
    alternates: {
      canonical: `https://example.com/${locale}`,
      languages: {
        'zh-CN': 'https://example.com/zh',
        'en-US': 'https://example.com/en',
        'ja-JP': 'https://example.com/ja'
      }
    }
  };
}

3. Detecção de idioma

Detecte automaticamente o idioma do usuário no primeiro acesso:

// middleware.ts
import createMiddleware from 'next-intl/middleware';
import { NextRequest } from 'next/server';

const intlMiddleware = createMiddleware({
  locales: ['en', 'zh', 'ja'],
  defaultLocale: 'zh',
  localeDetection: true // Ativa a detecção automática
});

export default function middleware(request: NextRequest) {
  return intlMiddleware(request);
}

O next-intl escolhe automaticamente o idioma com base no cabeçalho Accept-Language da requisição.

4. Como manter a preferência de idioma

Depois que o usuário escolhe um idioma, é melhor lembrar essa preferência:

// Usa um cookie para salvar a preferência
const switchLanguage = (newLocale: string) => {
  document.cookie = `NEXT_LOCALE=${newLocale}; path=/; max-age=31536000`;
  router.push(newPath);
};

O middleware do next-intl lê esse cookie automaticamente.

Caso prático: um projeto de internacionalização completo

Para encerrar, organizei o código principal de um pequeno projeto que desenvolvi antes para servir de referência.

Estrutura do projeto:

├── app/
│   ├── [locale]/
│   │   ├── layout.tsx
│   │   ├── page.tsx
│   │   └── blog/
│   │       └── [slug]/
│   │           └── page.tsx
├── components/
│   ├── LanguageSwitcher.tsx
│   └── Navigation.tsx
├── messages/
│   ├── zh/
│   │   ├── common.json
│   │   └── blog.json
│   ├── en/
│   │   ├── common.json
│   │   └── blog.json
├── i18n.ts
├── middleware.ts
└── navigation.ts

navigation.ts (configuração das rotas):

import { createSharedPathnamesNavigation } from 'next-intl/navigation';

export const locales = ['zh', 'en'] as const;
export const localePrefix = 'as-needed';

export const { Link, redirect, usePathname, useRouter } =
  createSharedPathnamesNavigation({ locales, localePrefix });

components/Navigation.tsx:

'use client';

import { Link } from '@/navigation';
import { useTranslations } from 'next-intl';
import LanguageSwitcher from './LanguageSwitcher';

export default function Navigation() {
  const t = useTranslations('common.navigation');

  return (
    <nav className="flex items-center justify-between p-4">
      <div className="flex gap-4">
        <Link href="/">{t('home')}</Link>
        <Link href="/blog">{t('blog')}</Link>
        <Link href="/about">{t('about')}</Link>
      </div>
      <LanguageSwitcher />
    </nav>
  );
}

Depois que esse projeto entrou no ar, a troca de idioma ficou muito fluida e não tive nenhum problema.

Conclusão

A internacionalização do Next.js realmente ficou um pouco mais complexa na era do App Router, mas, depois que você entende o next-intl, ela deixa de ser tão difícil:

  1. Configuração principal: middleware + i18n.ts + pasta [locale]
  2. Uso das traduções: o Hook useTranslations funciona tanto em Server quanto em Client Components
  3. Processamento das rotas: use o Link e o Router fornecidos pelo next-intl
  4. Gerenciamento dos arquivos: divida por módulo e use a verificação de tipos do TypeScript
  5. Otimização de desempenho: geração estática e carregamento sob demanda

Para ser sincero, no começo achei essa abordagem bastante confusa, principalmente a parte do middleware e das rotas dinâmicas. Mas, depois de implementá-la algumas vezes, ela se torna natural. Hoje, este é basicamente o padrão que uso em projetos internacionalizados.

Se você está desenvolvendo ou pretende desenvolver um projeto multilíngue, recomendo bastante experimentar o next-intl. Existe uma curva de aprendizado, mas, no longo prazo, vale o tempo investido.

Recursos de referência

Espero que este artigo ajude você a colocar a internacionalização do Next.js para funcionar!

Processo completo de configuração da internacionalização no Next.js

Todas as etapas, desde a instalação do next-intl até a configuração de rotas multilíngues e o gerenciamento dos arquivos de tradução

⏱️ Estimated time: 2 hr

  1. 1

    Step 1: Instalar o next-intl e fazer a configuração básica

    Instale:
    ```bash
    npm install next-intl
    ```

    Crie os arquivos de tradução:
    ```
    messages/
    zh.json
    en.json
    ```

    Configure `middleware.ts`:
    ```ts
    import createMiddleware from 'next-intl/middleware'
    import { routing } from './i18n/routing'

    export default createMiddleware(routing)

    export const config = {
    matcher: ['/', '/(zh|en)/:path*']
    }
    ```

    Configure `app/[locale]/layout.tsx`:
    ```tsx
    import { NextIntlClientProvider } from 'next-intl'
    import { getMessages } from 'next-intl/server'

    export default async function LocaleLayout({
    children,
    params: { locale }
    }) {
    const messages = await getMessages()

    return (
    <html lang={locale}>
    <body>
    <NextIntlClientProvider messages={messages}>
    {children}
    </NextIntlClientProvider>
    </body>
    </html>
    )
    }
    ```

    Pontos principais:
    • Use a rota dinâmica `[locale]`
    • Disponibilize as traduções no layout
    • Configure o middleware para processar a troca de idioma
  2. 2

    Step 2: Configurar a estratégia de rotas multilíngues

    Opção 1: subcaminho (recomendada)
    • Formato da URL: /zh/about, /en/about
    • Configuração simples
    • Favorável ao SEO

    Opção 2: domínio
    • Formato da URL: zh.example.com, en.example.com
    • Exige a configuração de vários domínios
    • Abordagem mais profissional

    Opção 3: cookie
    • O idioma é trocado por meio de um cookie
    • A URL não contém o prefixo do idioma
    • Adequada para usuários de um único idioma

    Configure o subcaminho:
    ```ts
    // i18n/routing.ts
    export const routing = {
    locales: ['zh', 'en'],
    defaultLocale: 'zh'
    }
    ```

    Use assim:
    ```tsx
    import { useTranslations } from 'next-intl'

    export function Page() {
    const t = useTranslations('common')
    return <h1>{t('title')}</h1>
    }
    ```

    Ponto principal: escolha a estratégia de rotas adequada ao projeto; para a maioria dos projetos, subcaminhos são suficientes.
  3. 3

    Step 3: Gerenciar os arquivos de tradução

    Crie os arquivos de tradução:
    ```json
    // messages/zh.json
    {
    "common": {
    "title": "欢迎",
    "description": "这是一个多语言网站"
    },
    "nav": {
    "home": "首页",
    "about": "关于"
    }
    }
    ```

    Use as traduções:
    ```tsx
    import { useTranslations } from 'next-intl'

    export function Page() {
    const t = useTranslations('common')
    return (
    <div>
    <h1>{t('title')}</h1>
    <p>{t('description')}</p>
    </div>
    )
    }
    ```

    Segurança de tipos:
    ```ts
    // i18n/request.ts
    import { getRequestConfig } from 'next-intl/server'

    export default getRequestConfig(async ({ locale }) => ({
    messages: (await import(`../messages/${locale}.json`)).default
    }))
    ```

    Pontos principais:
    • Organize as traduções em uma estrutura aninhada
    • Use TypeScript para obter segurança de tipos
    • Melhore a experiência de desenvolvimento com a extensão i18n Ally para VSCode

FAQ

Por que o App Router precisa do next-intl?
Motivo: o App Router removeu o recurso de i18n integrado ao Pages Router.

Pages Router:
• Tem suporte integrado a rotas i18n
• O campo i18n é configurado em next.config.js
• Processa automaticamente a troca de idioma

App Router:
• Removeu o recurso de i18n integrado
• Exige o uso de uma biblioteca de terceiros
• O next-intl é uma das opções mais populares

Vantagens do next-intl:
• Suporte nativo ao App Router
• Segurança de tipos
• Estratégias flexíveis de rotas
• Recursos avançados, como pluralização e formatação de datas
• Ótimo desempenho

Recomendação: se você usa o App Router, dê preferência ao next-intl.
Quantas estratégias de rotas o next-intl oferece?
Há três estratégias de rotas:

Opção 1: subcaminho (recomendada)
• Formato da URL: /zh/about, /en/about
• Configuração simples
• Favorável ao SEO
• Adequada para a maioria dos projetos

Opção 2: domínio
• Formato da URL: zh.example.com, en.example.com
• Exige a configuração de vários domínios
• Abordagem mais profissional
• Adequada para projetos de grande porte

Opção 3: cookie
• O idioma é trocado por meio de um cookie
• A URL não contém o prefixo do idioma
• Adequada para usuários de um único idioma
• Configuração mais complexa

Como escolher:
• Maioria dos projetos → subcaminho
• Projetos de grande porte → domínio
• Requisitos especiais → cookie

Ponto principal: escolha a estratégia de rotas adequada ao projeto; para a maioria dos projetos, subcaminhos são suficientes.
Como configurar o next-intl?
Instale:
```bash
npm install next-intl
```

Crie os arquivos de tradução:
```
messages/
zh.json
en.json
```

Configure `middleware.ts`:
```ts
import createMiddleware from 'next-intl/middleware'
import { routing } from './i18n/routing'

export default createMiddleware(routing)

export const config = {
matcher: ['/', '/(zh|en)/:path*']
}
```

Configure `app/[locale]/layout.tsx`:
```tsx
import { NextIntlClientProvider } from 'next-intl'
import { getMessages } from 'next-intl/server'

export default async function LocaleLayout({
children,
params: { locale }
}) {
const messages = await getMessages()

return (
<html lang={locale}>
<body>
<NextIntlClientProvider messages={messages}>
{children}
</NextIntlClientProvider>
</body>
</html>
)
}
```

Pontos principais:
• Use a rota dinâmica `[locale]`
• Disponibilize as traduções no layout
• Configure o middleware para processar a troca de idioma
Como gerenciar os arquivos de tradução?
Crie os arquivos de tradução:
```json
// messages/zh.json
{
"common": {
"title": "欢迎",
"description": "这是一个多语言网站"
},
"nav": {
"home": "首页",
"about": "关于"
}
}
```

Use as traduções:
```tsx
import { useTranslations } from 'next-intl'

export function Page() {
const t = useTranslations('common')
return (
<div>
<h1>{t('title')}</h1>
<p>{t('description')}</p>
</div>
)
}
```

Segurança de tipos:
```ts
// i18n/request.ts
import { getRequestConfig } from 'next-intl/server'

export default getRequestConfig(async ({ locale }) => ({
messages: (await import(`../messages/${locale}.json`)).default
}))
```

Pontos principais:
• Organize as traduções em uma estrutura aninhada
• Use TypeScript para obter segurança de tipos
• Melhore a experiência de desenvolvimento com a extensão i18n Ally para VSCode

Recomendação: organize os arquivos de tradução por módulo funcional para evitar que um único arquivo fique grande demais.
Quais recursos o next-intl oferece?
Recursos principais:
• Tradução de texto com a função t
• Tratamento de plural
• Formatação de datas
• Formatação de números
• Suporte a rich text

Exemplo de uso:
```tsx
import { useTranslations, useFormatter } from 'next-intl'

export function Page() {
const t = useTranslations('common')
const format = useFormatter()

return (
<div>
<h1>{t('title')}</h1>
<p>{format.dateTime(new Date(), { dateStyle: 'long' })}</p>
<p>{format.number(1234.56, { style: 'currency', currency: 'USD' })}</p>
</div>
)
}
```

Vantagens:
• Muitos recursos
• Segurança de tipos
• Ótimo desempenho
• Boa integração com Server Components

Recomendação: aproveite bem os recursos do next-intl para melhorar a experiência do usuário.
Como implementar a troca de idioma?
Use o componente Link:
```tsx
import { Link } from '@/i18n/navigation'

<Link href="/about" locale="en">
English
</Link>
<Link href="/about" locale="zh">
中文
</Link>
```

Use `useRouter`:
```tsx
'use client'
import { useRouter, usePathname } from '@/i18n/navigation'

export function LanguageSwitcher() {
const router = useRouter()
const pathname = usePathname()

const switchLanguage = (locale: string) => {
router.replace(pathname, { locale })
}

return (
<button onClick={() => switchLanguage('en')}>
English
</button>
)
}
```

Pontos principais:
• Use o Link e o useRouter fornecidos pelo next-intl
• Mantenha o caminho atual e altere apenas o idioma
• Preserve uma boa experiência para o usuário

Recomendação: adicione o seletor de idioma à barra de navegação ou ao rodapé.

13 min de leitura · Publicado em: 25 dez 2025 · Atualizado em: 8 set 2026

Comentários

Entre com GitHub para comentar

Easton BlogEaston Blog