Next.js: Internacionalização & Middleware
Última atualização: 2026-08-26
Internacionalização não é apenas sobre tradução — é um processo sistemático de engenharia envolvendo roteamento, detecção de idioma e layout RTL.
1. O Que Você Vai Aprender
next-intlInstalação e Integração do Framework de Internacionalização- Políticas de Roteamento com Middleware (Detecção de
Accept-Language+ Persistência por Cookie + Reescrita de URL) - Gestão de dicionários multilíngues (
en.json/ar.jsontradução estruturada) generateStaticParamsPré-gerar páginas estáticas multilíngues- Suporte a layout RTL (adaptação de direção de texto para árabe)
- Formatação Internacional de Datas, Números e Moeda
2. A História Real de uma Gerente de Produto Internacional
(1) Ponto de Dor: 47 avaliações negativas de usuários no Oriente Médio
Após o lançamento do TaskFlow da Alice, ele recebeu um grande número de reclamações de usuários na Arábia Saudita e nos Emirados Árabes Unidos:
"O dashboard ainda está em inglês, o formato de data 07/06/2026 não faz sentido para eles (a Arábia Saudita usa 06/07/2026), e o número 1.234,56 é exibido incorretamente nos países árabes. O pior de tudo — a página rola da direita para a esquerda, e todo o layout está invertido."
| Problema | Feedback do Usuário | Número de Usuários Afetados |
|---|---|---|
| Página não traduzida para árabe | "Não consigo entender o menu" | 47 avaliações negativas |
| Formato de data incorreto | "A data está invertida" | 32 reclamações |
| Layout não otimizado para RTL | "O botão está à direita" | 28 reclamações |
| Erro no Símbolo da Moeda | "Preço exibido em USD" | 15 reclamações |
(2) A Solução next-intl + Middleware
Use
next-intlpara fornecer um framework de internacionalização; o middleware alterna automaticamente com base no idioma do navegador.
// middleware.ts — Núcleo de Roteamento Internacional
import createMiddleware from 'next-intl/middleware'
export default createMiddleware({
locales: ['zh', 'en', 'ja', 'ar'],
defaultLocale: 'zh',
localePrefix: 'always' // URL sempre inclui o prefixo de região
})
export const config = {
matcher: ['/((?!_next|api|favicon.ico).*)']
}
(3) Resultado
| Dimensão | Antes da Reformulação | Depois da Reformulação |
|---|---|---|
| Idiomas Suportados | Apenas chinês | 4 idiomas: Chinês, Inglês, Japonês e Árabe |
| Detecção de Idioma | Seleção Manual | Detecção automática do idioma do navegador |
| Suporte RTL | ❌ Não Suportado | ✅ Layout Espelhado Completo |
| Data/Número | Formato chinês hard-coded | Formatação automática com API Intl |
| NPS dos Usuários Sauditas | 32 | 78 |
3. Instalando next-intl e Estrutura do Projeto
(1) Instalação e Estrutura de Diretórios
npm install next-intl
graph TB
A[next-intl] --> B[i18n/request.ts<br/>Configuração do Servidor]
A --> C[messages/<br/>Dicionário de Tradução]
C --> D[en.json<br/>Inglês]
C --> E[zh.json<br/>Chinês]
C --> F[ja.json<br/>Japonês]
C --> G[ar.json<br/>Árabe]
A --> H[middleware.ts<br/>Detecção de Idioma + Reescrita]
A --> I[app/[locale]/<br/>Segmentos de Roteamento Dinâmico]
style A fill:#cce5ff
style H fill:#d4edda
style I fill:#fff3cd
| Arquivo | Finalidade |
|---|---|
i18n/request.ts |
Ponto de entrada da configuração i18n no servidor |
messages/en.json |
Dicionário em Inglês |
messages/ar.json |
Dicionário em Árabe (com Marcação RTL) |
middleware.ts |
Detecção Automática de Idioma + Reescrita de URL |
app/[locale]/ |
Segmento de Roteamento Regional Dinâmico |
(2) Configuração Inicial
// i18n/request.ts — Configuração do Servidor next-intl
import { getRequestConfig } from 'next-intl/server'
import { hasLocale } from 'next-intl'
import { routing } from './routing'
export default getRequestConfig(async ({ requestLocale }) => {
const requested = await requestLocale
const locale = hasLocale(routing.locales, requested) ? requested : routing.defaultLocale
return {
locale,
messages: (await import(`../messages/${locale}.json`)).default
}
})
// i18n/routing.ts — Compartilhamento de Configurações de Roteador
import { defineRouting } from 'next-intl/routing'
export const routing = defineRouting({
locales: ['zh', 'en', 'ja', 'ar'],
defaultLocale: 'zh',
localeDetection: true,
localePrefix: 'as-needed' // Omitir o prefixo para o idioma padrão
})
▶ Exemplo: Configuração Completa de Middleware
Output:
Exporta: routing.
// middleware.ts — Detecção de Idioma + Persistência por Cookie
import createMiddleware from 'next-intl/middleware'
import { NextRequest, NextResponse } from 'next/server'
const intlMiddleware = createMiddleware({
locales: ['zh', 'en', 'ja', 'ar'],
defaultLocale: 'zh',
localeDetection: true,
localePrefix: 'always'
})
export default function middleware(req: NextRequest) {
const { pathname } = req.nextUrl
// API / _next Não processar
if (pathname.startsWith('/api') || pathname.startsWith('/_next') || pathname === '/favicon.ico') {
return NextResponse.next()
}
return intlMiddleware(req)
}
export const config = {
matcher: ['/((?!api|_next/static|_next/image|favicon.ico).*)']
}
Output:
Middleware processa requisições antes que elas cheguem aos handlers de rota.
4. Políticas de Roteamento com Middleware
(1) Três Modos de Roteamento
graph TB
A[Acesso do Usuário] --> B{Verificação do Middleware}
B --> C[Inspecionar Cookie<br/>next-intl-locale]
B --> D[Inspecionar Accept-Language<br/>Cabeçalho HTTP]
B --> E[Idioma Padrão]
C --> F[Seleção Feita → Opções de Uso]
D --> G[Primeira Visita → Detectar Idioma do Navegador]
E --> H[Nenhuma correspondência → Idioma Padrão]
F --> I[Reescrita de URL /{locale}/...]
G --> I
H --> I
I --> J[Gravar Cookie<br/>Opções de Persistência]
style B fill:#fff3cd
style I fill:#d4edda
style J fill:#cce5ff
| Modo de Roteamento | Formato da URL | localePrefix |
Cenários Aplicáveis |
|---|---|---|---|
| Prefixo Sempre | /en/about / /ar/about |
'always' |
Exibição igual para todos os idiomas |
| Prefixo Sob Demanda | /about (padrão) / /en/about |
'as-needed' |
Idioma Padrão Amigável para SEO |
| Nunca Prefixar | /about |
'never' |
Orientado por Cookie (não recomendado) |
(2) Detecção de Accept-Language + Persistência por Cookie
// middleware.ts — Controle Manual da Lógica de Detecção
import { NextRequest, NextResponse } from 'next/server'
import { routing } from './i18n/routing'
const COOKIE_NAME = 'NEXT_LOCALE'
export default function middleware(req: NextRequest) {
const { pathname } = req.nextUrl
const cookieLocale = req.cookies.get(COOKIE_NAME)?.value
const acceptLanguage = req.headers.get('Accept-Language')?.split(',')[0]?.split('-')[0]
const browserLocale = acceptLanguage && routing.locales.includes(acceptLanguage as any) ? acceptLanguage : null
// Correspondência de Idiomas
const matchedLocale = cookieLocale || browserLocale || routing.defaultLocale
// O caminho já tem um prefixo de idioma
const pathLocale = routing.locales.find((l) => pathname.startsWith(`/${l}/`) || pathname === `/${l}`)
if (pathLocale) return NextResponse.next()
// Reescrever URL e definir Cookie
const url = new URL(`/${matchedLocale}${pathname}`, req.url)
const res = NextResponse.rewrite(url)
res.cookies.set(COOKIE_NAME, matchedLocale, { maxAge: 60 * 60 * 24 * 365 })
return res
}
export const config = { matcher: ['/((?!api|_next|favicon.ico).*)'] }
▶ Exemplo: Componente de Troca de Idioma
Output:
Renderiza a UI do componente middleware.
// components/LocaleSwitcher.tsx
'use client'
import { useLocale } from 'next-intl'
import { usePathname, useRouter } from '@/i18n/routing'
import { useTransition } from 'react'
const locales = [
{ code: 'zh', label: '' },
{ code: 'en', label: 'English' },
{ code: 'ja', label: '' },
{ code: 'ar', label: 'العربية' }
]
export default function LocaleSwitcher() {
const locale = useLocale()
const router = useRouter()
const pathname = usePathname()
const [isPending, startTransition] = useTransition()
const switchLocale = (nextLocale: string) => {
startTransition(() => {
router.replace(pathname, { locale: nextLocale })
})
}
return (
<select
value={locale}
onChange={(e) => switchLocale(e.target.value)}
disabled={isPending}
style={{ padding: '0.25rem 0.5rem' }}
>
{locales.map((l) => (
<option key={l.code} value={l.code}>{l.label}</option>
))}
</select>
)
}
Output:
Renderiza a UI do componente ▶ Exemplo: Componente de Troca de Idioma conforme descrito na seção.
5. Gestão de Dicionários Multilíngues
(1) Estrutura do Arquivo de Dicionário
// messages/pt.json
{
"nav": {
"home": "Início",
"dashboard": "Dashboard",
"projects": "Projetos",
"settings": "Configurações",
"signIn": "Entrar",
"signOut": "Sair"
},
"home": {
"title": "TaskFlow - Plataforma de Colaboração em Equipe",
"subtitle": "Ajude mais de 10.000 equipes a colaborar com alta eficiência",
"cta": "Comece Gratuitamente"
},
"dashboard": {
"welcome": "Bem-vindo de volta, {name}",
"stats": {
"members": "{count} membros",
"projects": "{count} projetos",
"tasks": "{count} tarefas"
},
"activity": "Atividades Recentes"
},
"common": {
"loading": "Carregando...",
"error": "Ocorreu um erro",
"save": "Salvar",
"cancel": "Cancelar",
"delete": "Excluir",
"confirm": "Confirmar"
}
}
// messages/ar.json
{
"nav": {
"home": "الرئيسية",
"dashboard": "لوحة التحكم",
"projects": "المشاريع",
"settings": "الإعدادات",
"signIn": "تسجيل الدخول",
"signOut": "تسجيل الخروج"
},
"home": {
"title": "TaskFlow - منصة التعاون الجماعي",
"subtitle": "نساعد أكثر من 10,000 فريق على العمل بكفاءة",
"cta": "ابدأ مجاناً"
},
"dashboard": {
"welcome": "مرحباً بعودتك، {name}",
"stats": {
"members": "{count} أعضاء",
"projects": "{count} مشروعاً",
"tasks": "{count} مهمة"
},
"activity": "النشاطات الأخيرة"
},
"common": {
"loading": "جارٍ التحميل...",
"error": "حدث خطأ",
"save": "حفظ",
"cancel": "إلغاء",
"delete": "حذف",
"confirm": "تأكيد"
}
}
(2) Usando Traduções nos Componentes
// Server Component: Uso da função t()
import { getTranslations } from 'next-intl/server'
export default async function HomePage() {
const t = await getTranslations('home')
return (
<div>
<h1>{t('title')}</h1>
<p>{t('subtitle')}</p>
<button>{t('cta')}</button>
</div>
)
}
// Client Component: Hook useTranslations
'use client'
import { useTranslations } from 'next-intl'
export default function DashboardHeader({ name }: { name: string }) {
const t = useTranslations('dashboard')
return (
<h1>{t('welcome', { name })}</h1>
)
}
Bem-vindo de volta, Alice
💻 Output (Árabe): مرحباً بعودتك، Alice
▶ Exemplo: Traduções contendo números complexos
// Dicionário
{
"tasks": {
"remaining": "{count, plural, =0 {Nenhuma tarefa} one {# tarefa} other {# tarefas}}"
}
}
import { useTranslations } from 'next-intl'
export default function TaskCount({ count }: { count: number }) {
const t = useTranslations('tasks')
return <span>{t('remaining', { count })}</span>
}
Output:
Renderiza a UI do componente TaskCount.
Nenhuma tarefa
💻 Output (count=1): 1 tarefa
💻 Output (count=5): 5 tarefas
next-intl usa a sintaxe ICU MessageFormat e suporta recursos avançados como formas plurais, opções e formatação de números.
6. generateStaticParams: Pré-gerar páginas multilíngues
(1) Princípios de Pré-geração Estática
graph LR
A[generateStaticParams] --> B[Retornar lista de locale]
B --> C[zh: /zh/blog/post-1]
B --> D[en: /en/blog/post-1]
B --> E[ja: /ja/blog/post-1]
B --> F[ar: /ar/blog/post-1]
C --> G[Gerado durante o build: 4 arquivos HTML]
D --> G
E --> G
F --> G
style A fill:#cce5ff
style G fill:#d4edda
| Idioma | Arquivo HTML | Caminho |
|---|---|---|
| Chinês | out/zh/blog/post-1.html |
/zh/blog/post-1 |
| Inglês | out/en/blog/post-1.html |
/en/blog/post-1 |
| Japonês | out/ja/blog/post-1.html |
/ja/blog/post-1 |
| Árabe | out/ar/blog/post-1.html |
/ar/blog/post-1 |
(2) O parâmetro "locale" no layout raiz
// app/[locale]/layout.tsx — Layout Raiz recebe parâmetro locale
import { NextIntlClientProvider } from 'next-intl'
import { getMessages, getTranslations } from 'next-intl/server'
import { notFound } from 'next/navigation'
import { routing } from '@/i18n/routing'
type Props = { children: React.ReactNode; params: Promise<{ locale: string }> }
export default async function LocaleLayout({ children, params }: Props) {
const { locale } = await params
// Verificar se locale é válido
if (!routing.locales.includes(locale as any)) notFound()
const messages = await getMessages()
return (
<html lang={locale} dir={locale === 'ar' ? 'rtl' : 'ltr'}>
<body>
<NextIntlClientProvider messages={messages}>
{children}
</NextIntlClientProvider>
</body>
</html>
)
}
▶ Exemplo: Geração Estática de Posts de Blog Multilíngues
Output:
Renderiza a UI do componente LocaleLayout.
// app/[locale]/blog/[slug]/page.tsx — SSG Multilíngue
import { getTranslations } from 'next-intl/server'
import { routing } from '@/i18n/routing'
type Props = { params: Promise<{ locale: string; slug: string }> }
// Pré-gerar páginas de artigo para todos os idiomas
export async function generateStaticParams() {
const posts = await fetch('https://api.taskflow.io/blog/posts').then(r => r.json())
return routing.locales.flatMap((locale) =>
posts.map((post: { slug: string }) => ({ locale, slug: post.slug }))
)
}
export async function generateMetadata({ params }: Props) {
const { locale, slug } = await params
const post = await fetch(`https://api.taskflow.io/blog/${slug}`).then(r => r.json())
return {
title: locale === 'en' ? post.title : post.titleLocalized[locale],
alternates: {
languages: Object.fromEntries(
routing.locales.map((l) => [l, `https://taskflow.io/${l}/blog/${slug}`])
)
}
}
}
export default async function BlogPostPage({ params }: Props) {
const { locale, slug } = await params
const t = await getTranslations('blog')
return (
<article>
<h1>{t('title')}</h1>
<p>Locale: {locale} | Slug: {slug}</p>
</article>
)
}
Output:
Renderiza: Página com cabeçalho "{t('title')}" e conteúdo associado.
7. Suporte a Layout RTL
(1) Diferenças Entre RTL e LTR
graph TB
subgraph Layout LTR
A1[Logo<br/>Alinhado à esquerda] --> A2[Barra de Navegação<br/>Da esquerda para a direita]
A2 --> A3[Sidebar<br/>Alinhada à esquerda]
A3 --> A4[Conteúdo<br/>À direita]
end
subgraph Layout RTL
B1[Logo<br/>À direita] --> B2[Barra de Navegação<br/>Da direita para a esquerda]
B2 --> B3[Sidebar<br/>À direita]
B3 --> B4[Conteúdo<br/>Alinhado à esquerda]
end
style B1 fill:#d4edda
style B2 fill:#d4edda
style B3 fill:#d4edda
| Dimensão | LTR (Chinês/Inglês/Japonês) | RTL (Árabe) |
|---|---|---|
| Direção do Texto | Esquerda para Direita | Direita para Esquerda |
| Barra de Navegação | Logo à esquerda → Menu à direita | Logo à direita → Menu à esquerda |
| Sidebar | Esquerda | Direita |
| Ícone de seta | → Avançar |
← Avançar |
| Margem do card | margin-left: auto |
margin-right: auto |
| Botão de Envio do Formulário | Inferior Direito | Inferior Esquerdo |
(2) Suporte RTL no Tailwind CSS
// app/[locale]/layout.tsx — Verificação RTL e Nome de Classe CSS
export default async function LocaleLayout({ children, params }: {
children: React.ReactNode
params: Promise<{ locale: string }>
}) {
const { locale } = await params
const isRtl = locale === 'ar'
return (
<html lang={locale} dir={isRtl ? 'rtl' : 'ltr'}>
<body className={isRtl ? 'rtl' : 'ltr'}>
{children}
</body>
</html>
)
}
/* app/globals.css — Estilos Básicos RTL */
.ltr { direction: ltr; }
.rtl { direction: rtl; }
/* Espelhamento da Barra de Navegação */
.rtl .nav-items { flex-direction: row-reverse; }
.rtl .sidebar { right: 0; left: auto; }
.rtl .main-content { margin-right: 260px; margin-left: 0; }
/* Espelhamento de Ícones */
.rtl .icon-arrow::before { content: '←'; }
.ltr .icon-arrow::before { content: '→'; }
/* Inversão de Espaçamento */
.rtl .ml-auto { margin-right: auto; margin-left: 0; }
.rtl .mr-2 { margin-left: 0.5rem; margin-right: 0; }
▶ Exemplo: Layout de dashboard otimizado para RTL
Output:
Estilos CSS aplicados para: .nav-items, .mr-2, .sidebar, .rtl, before, .main-content
// components/DashboardLayout.tsx — Layouts Compatíveis com RTL
export default function DashboardLayout({
sidebar,
children,
locale
}: {
sidebar: React.ReactNode
children: React.ReactNode
locale: string
}) {
const isRtl = locale === 'ar'
return (
<div style={{
display: 'flex',
flexDirection: isRtl ? 'row-reverse' : 'row',
minHeight: '100vh'
}}>
{/* Sidebar: LTR à esquerda, RTL à direita */}
<aside style={{
width: 260,
borderRight: isRtl ? 'none' : '1px solid #e5e7eb',
borderLeft: isRtl ? '1px solid #e5e7eb' : 'none'
}}>
{sidebar}
</aside>
{/* Área de Conteúdo Principal */}
<main style={{
flex: 1,
padding: '2rem',
textAlign: isRtl ? 'right' : 'left'
}}>
{children}
</main>
</div>
)
}
Output:
Renderiza: Layout de dashboard com sidebar escura (links Dashboard/Projetos/Configurações) + área de conteúdo principal.
direction: rtl; você também precisa espelhar todas as direções flex, margins/padding, ícones de seta e alinhamento de texto.
8. Exemplo Completo: Site Oficial em Quatro Idiomas + Dashboard RTL
// app/[locale]/layout.tsx — Layout Raiz de Internacionalização
import { NextIntlClientProvider } from 'next-intl'
import { getMessages } from 'next-intl/server'
import { routing } from '@/i18n/routing'
import { notFound } from 'next/navigation'
import LocaleSwitcher from '@/components/LocaleSwitcher'
import type { Metadata } from 'next'
type Props = { children: React.ReactNode; params: Promise<{ locale: string }> }
export async function generateMetadata({ params }: Props): Promise<Metadata> {
const { locale } = await params
return {
title: 'TaskFlow',
alternates: {
canonical: `https://taskflow.io/${locale}`,
languages: Object.fromEntries(
routing.locales.map((l) => [l, `https://taskflow.io/${l}`])
)
}
}
}
export default async function LocaleLayout({ children, params }: Props) {
const { locale } = await params
if (!routing.locales.includes(locale as any)) notFound()
const messages = await getMessages()
const isRtl = locale === 'ar'
return (
<html lang={locale} dir={isRtl ? 'rtl' : 'ltr'}>
<head>
{/* RTL Carregando CSS RTL */}
{isRtl && <link rel="stylesheet" href="/rtl.css" />}
</head>
<body style={{ margin: 0, fontFamily: 'system-ui, sans-serif' }}>
<NextIntlClientProvider messages={messages}>
<nav style={{
display: 'flex',
justifyContent: 'space-between',
alignItems: 'center',
padding: '1rem 2rem',
borderBottom: '1px solid #e5e7eb',
flexDirection: isRtl ? 'row-reverse' : 'row'
}}>
<strong>TaskFlow</strong>
<LocaleSwitcher />
</nav>
<main style={{ padding: '2rem' }}>{children}</main>
</NextIntlClientProvider>
</body>
</html>
)
}
// app/[locale]/dashboard/page.tsx — Dashboard RTL
import { getTranslations } from 'next-intl/server'
type Props = { params: Promise<{ locale: string }> }
export default async function DashboardPage({ params }: Props) {
const { locale } = await params
const t = await getTranslations('dashboard')
const isRtl = locale === 'ar'
return (
<div>
<h1 style={{ textAlign: isRtl ? 'right' : 'left' }}>{t('welcome', { name: 'Alice' })}</h1>
<div style={{
display: 'flex',
gap: '1rem',
flexDirection: isRtl ? 'row-reverse' : 'row'
}}>
<div style={{ flex: 1, padding: '1.5rem', background: '#f0f9ff', borderRadius: 8 }}>
<h3>{t('stats.members', { count: 12 })}</h3>
</div>
<div style={{ flex: 1, padding: '1.5rem', background: '#fef3c7', borderRadius: 8 }}>
<h3>{t('stats.projects', { count: 45 })}</h3>
</div>
<div style={{ flex: 1, padding: '1.5rem', background: '#ecfdf5', borderRadius: 8 }}>
<h3>{t('stats.tasks', { count: 230 })}</h3>
</div>
</div>
<h2 style={{ textAlign: isRtl ? 'right' : 'left' }}>{t('activity')}</h2>
<div style={{
padding: '1rem',
border: '1px solid #e5e7eb',
borderRadius: 8,
textAlign: isRtl ? 'right' : 'left'
}}>
<p>Alice criou o projeto "Redesign do Website"</p>
<p>Bob concluiu a Tarefa #47</p>
<p>Charlie entrou na organização</p>
</div>
</div>
)
}
# Resultados de Saída (Português):
TaskFlow
[Selecionar idioma: Português ▼]
Bem-vindo de volta, Alice
[12 membros] [45 projetos] [230 tarefas]
Atividades Recentes
Alice criou o projeto "Redesign do Website"
Bob concluiu a Tarefa #47
Charlie entrou na organização
# Resultados de Saída (Árabe):
TaskFlow
[اختيار اللغة: العربية ▼]
مرحباً بعودتك، Alice
[12 أعضاء] [45 مشروعاً] [230 مهمة]
النشاطات الأخيرة
أليس قامت بإنشاء مشروع "إعادة تصميم الموقع"
بوب أكمل المهمة رقم 47
تشارلي انضم إلى المنظمة
❓ Perguntas Frequentes
P: Qual devo escolher,
next-intlounext-international? R:next-intltem uma comunidade maior (5k⭐) e mais recursos (plural, seleção, formatação de números e detecção RTL).next-internationalé mais leve. Este tutorial usanext-intl.
P: Por que usar reescrita de URL em vez de redirecionamento no middleware? R:
rewritemantém o conteúdo da barra de endereço do navegador inalterado enquanto mapeia internamente para/{locale}/path, o que é melhor para SEO e experiência do usuário.redirectaltera a URL, o que pode quebrar links compartilhados pelos usuários.
P: Que trabalho adicional é necessário para layouts RTL em árabe? R: Além de
direction: rtl, você deve espelhar a direção flex (flex-direction: row-reverse), ajustar a direção das margins e padding, inverter ícones de seta e usartext-align: start/endem vez deleft/right. Recomenda-se tratar isso uniformemente usando o seletor[dir="rtl"]noglobals.css.
P:
generateStaticParamsgera um arquivo HTML separado para cada idioma? R: Sim. Por exemplo,/pt/blog/post-1,/en/blog/post-1e/ar/blog/post-1são três arquivos HTML separados. Cada arquivo contém apenas o conteúdo do seu idioma correspondente, garantindo a velocidade de carregamento mais rápida.
P: Como os parâmetros variáveis devem ser tratados em um dicionário de tradução? R: Use a sintaxe ICU MessageFormat:
{name}para inserir variáveis,{count, plural, =0 {nenhuma} one {# tarefa} other {# tarefas}}para lidar com plurais. Os hookst()euseTranslations()donext-intlanalisam automaticamente esses templates.
📖 Resumo
next-intlFornece uma solução abrangente de internacionalização: Middleware (detecção de idioma) + gestão de dicionário + suporte RTL- O Middleware verifica em ordem de prioridade: Cookie → Accept-Language → idioma padrão; o resultado é gravado em um cookie para persistência
- O dicionário usa estruturas JSON aninhadas para gestão e suporta ICU MessageFormat para plurais, variáveis e seleções
generateStaticParamsPré-gera HTML separado para cada idioma; zero sobrecarga de runtime no modo SSG- O suporte RTL requer a propriedade
dir+ espelhamento CSS (flex direction / margin / padding / ícones) - Datas, números e moeda são formatados automaticamente usando a API
Intldo JavaScript, sem necessidade de bibliotecas adicionais
📝 Exercícios
-
Exercício Básico (⭐): Instale
next-intlem um projeto existente, configure dicionários em português e inglês, implemente um componente de troca de idioma e uset()para renderizar o texto da barra de navegação na página. -
Exercício Avançado (⭐⭐): Implemente um blog SSG multilíngue completo:
generateStaticParamspré-gera páginas de artigo para quatro idiomas; faça cada artigo carregar o dicionário do idioma correspondente durante o processo de build; e verifique a estrutura dos arquivos HTML gerados. -
Desafio (⭐⭐⭐): Construa uma página de dashboard que suporte RTL: Configure para árabe e inglês, e use o prefixo
rtl:e espelhamentoflex-directionno Tailwind CSS para implementar adaptação completa de layout para sidebar, navegação e cards de estatísticas. Verifique se todos os elementos estão corretamente espelhados sobdir="rtl".