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

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);
};
Como usar o componente Link
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:
- Configuração principal: middleware +
i18n.ts+ pasta[locale] - Uso das traduções: o Hook
useTranslationsfunciona tanto em Server quanto em Client Components - Processamento das rotas: use o Link e o Router fornecidos pelo next-intl
- Gerenciamento dos arquivos: divida por módulo e use a verificação de tipos do TypeScript
- 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
- Documentação oficial do next-intl
- Guia de internacionalização do Next.js
- Extensão i18n Ally para VSCode
- Plataforma de gerenciamento de traduções Tolgee
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
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
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
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?
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?
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?
```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?
```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?
• 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?
```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
Guia completo Next.js
Se você chegou pela busca, o caminho mais rápido é ir para o post anterior ou próximo desta série.
Anterior
Login OAuth no Next.js: integração passo a passo com Google, GitHub e WeChat
Entenda o OAuth com uma analogia simples de retirada de encomenda e implemente login com Google, GitHub e WeChat no NextAuth.js, incluindo um guia completo de solução de erros.
Parte 7 de 26
Próximo
Next.js internacionalização e geração estática: guia prático de site multilíngue com SSG
Do erro de build à otimização de desempenho: passo a passo para implementar geração estática multilíngue sem armadilhas com App Router. Inclui exemplos completos, detalhes de generateStaticParams e técnicas para reduzir tempo de build.
Parte 9 de 26



Comentários
Entre com GitHub para comentar