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



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-intl para fornecer um framework de internacionalização; o middleware alterna automaticamente com base no idioma do navegador.

TS
// 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

BASH
npm install next-intl
100%
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

TS
// 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
  }
})
TS
// 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:

TEXT 📖 Somente leitura
Exporta: routing.
TS
// 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:

TEXT 📖 Somente leitura
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

100%
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)
TS
// 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).*)'] }
💡 Dica: A persistência por cookie garante que a seleção de idioma do usuário seja mantida ao alternar entre páginas. Quando o usuário alternar manualmente o idioma, atualize o valor do cookie.

▶ Exemplo: Componente de Troca de Idioma

Output:

TEXT 📖 Somente leitura
Renderiza a UI do componente middleware.
TSX
// 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:

TEXT 📖 Somente leitura
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

JSON
// 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"
  }
}
JSON
// 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

TSX
// 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>
  )
}
TSX
// 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>
  )
}
💻 Output (Português): Bem-vindo de volta, Alice 💻 Output (Árabe): مرحباً بعودتك، Alice

▶ Exemplo: Traduções contendo números complexos

TSX
// Dicionário
{
  "tasks": {
    "remaining": "{count, plural, =0 {Nenhuma tarefa} one {# tarefa} other {# tarefas}}"
  }
}
TSX
import { useTranslations } from 'next-intl'

export default function TaskCount({ count }: { count: number }) {
  const t = useTranslations('tasks')

  return <span>{t('remaining', { count })}</span>
}

Output:

TEXT 📖 Somente leitura
Renderiza a UI do componente TaskCount.
💻 Output (count=0): Nenhuma tarefa 💻 Output (count=1): 1 tarefa 💻 Output (count=5): 5 tarefas

💡 Dica: 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

100%
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

TSX
// 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:

TEXT 📖 Somente leitura
Renderiza a UI do componente LocaleLayout.
TSX
// 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:

TEXT 📖 Somente leitura
Renderiza: Página com cabeçalho "{t('title')}" e conteúdo associado.


7. Suporte a Layout RTL

(1) Diferenças Entre RTL e LTR

100%
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

TSX
// 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>
  )
}
CSS
/* 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:

TEXT 📖 Somente leitura
Estilos CSS aplicados para: .nav-items, .mr-2, .sidebar, .rtl, before, .main-content
TSX
// 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:

TEXT 📖 Somente leitura
Renderiza: Layout de dashboard com sidebar escura (links Dashboard/Projetos/Configurações) + área de conteúdo principal.
⚠️ Nota: RTL não é apenas 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

TSX
// 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>
  )
}
TSX
// 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>
  )
}
TEXT 📖 Somente leitura
# 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
TEXT 📖 Somente leitura
# Resultados de Saída (Árabe):
TaskFlow
[اختيار اللغة: العربية ▼]

مرحباً بعودتك، Alice
[12 أعضاء] [45 مشروعاً] [230 مهمة]

النشاطات الأخيرة
أليس قامت بإنشاء مشروع "إعادة تصميم الموقع"
بوب أكمل المهمة رقم 47
تشارلي انضم إلى المنظمة

❓ Perguntas Frequentes

P: Qual devo escolher, next-intl ou next-international? R: next-intl tem 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 usa next-intl.

P: Por que usar reescrita de URL em vez de redirecionamento no middleware? R: rewrite manté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. redirect altera 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 usar text-align: start/end em vez de left/right. Recomenda-se tratar isso uniformemente usando o seletor [dir="rtl"] no globals.css.

P: generateStaticParams gera um arquivo HTML separado para cada idioma? R: Sim. Por exemplo, /pt/blog/post-1, /en/blog/post-1 e /ar/blog/post-1 sã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 hooks t() e useTranslations() do next-intl analisam automaticamente esses templates.


📖 Resumo


📝 Exercícios

  1. Exercício Básico (⭐): Instale next-intl em um projeto existente, configure dicionários em português e inglês, implemente um componente de troca de idioma e use t() para renderizar o texto da barra de navegação na página.

  2. Exercício Avançado (⭐⭐): Implemente um blog SSG multilíngue completo: generateStaticParams pré-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.

  3. Desafio (⭐⭐⭐): Construa uma página de dashboard que suporte RTL: Configure para árabe e inglês, e use o prefixo rtl: e espelhamento flex-direction no 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 sob dir="rtl".

Web-Tutorial.com

Equipe Técnica Web-Tutorial

Uma plataforma de tutoriais mantida por diversos desenvolvedores. Cada tutorial é escrito e revisado por profissionais da área correspondente. Trabalhamos para manter nosso conteúdo preciso e confiável — se encontrar algum problema, avise-nos.

100%