Faça sua pergunta e obtenha um resumo do documento referenciando esta página e o provedor AI de sua escolha
Ao integrar o servidor MCP Intlayer ao seu assistente de IA, você pode recuperar todos os documentos diretamente de ChatGPT, DeepSeek, Cursor, VSCode, etc.
Ver a documentação do servidor MCPHistórico de versões
- Versão inicialv7.0.601/11/2025
O conteúdo desta página foi traduzido com uma IA.
Veja a última versão do conteúdo original em inglêsSe você tiver uma ideia para melhorar esta documentação, sinta-se à vontade para contribuir enviando uma pull request no GitHub.
Link do GitHub para a documentaçãoCopiar o Markdown do documento para a área de transferência
Como internacionalizar sua aplicação Next.js usando next-i18next em 2025
Índice
O que é next-i18next?
next-i18next é uma solução popular de internacionalização (i18n) para aplicações Next.js. Embora o pacote original next-i18next tenha sido projetado para o Pages Router, este guia mostra como implementar o i18next com o moderno App Router usando diretamente i18next e react-i18next.
Com essa abordagem, você pode:
- Organizar traduções usando namespaces (por exemplo, common.json, about.json) para uma melhor gestão de conteúdo.
- Carregar traduções de forma eficiente carregando apenas os namespaces necessários para cada página, reduzindo o tamanho do bundle.
- Suportar componentes tanto do servidor quanto do cliente com o devido tratamento de SSR e hidratação.
- Garantir suporte ao TypeScript com configuração de locale e chaves de tradução tipadas de forma segura.
- Otimizar para SEO com metadados apropriados, sitemap e internacionalização do robots.txt.
Como alternativa, você também pode consultar o guia next-intl, ou usar diretamente o Intlayer.
Veja a comparação em next-i18next vs next-intl vs Intlayer.
Práticas que você deve seguir
Antes de mergulharmos na implementação, aqui estão algumas práticas que você deve seguir:
- Defina os atributos lang e dir no HTML
- Defina os atributos HTML lang e dir
No seu layout, calcule dir usando getLocaleDirection(locale) e defina <html lang={locale} dir={dir}> para acessibilidade e SEO adequados. - Separe as mensagens por namespace
Organize os arquivos JSON por localidade e namespace (por exemplo, common.json, about.json) para carregar apenas o que você precisa. - Minimize o payload do cliente
Nas páginas, envie apenas os namespaces necessários para o NextIntlClientProvider (por exemplo, pick(messages, ['common', 'about'])). - Prefira páginas estáticas
Use páginas estáticas o máximo possível para melhor desempenho e SEO. - I18n em componentes de servidor
Componentes de servidor, como páginas ou todos os componentes que não são marcados como client, são estáticos e podem ser pré-renderizados em tempo de build. Portanto, teremos que passar as funções de tradução para eles como props. - Configure os tipos do TypeScript
- Para suas localidades, garanta a segurança de tipos em toda a sua aplicação.
- Proxy para redirecionamento
Use um proxy para lidar com a detecção de localidade e roteamento, redirecionando o usuário para a URL apropriada com prefixo de localidade. - Internacionalização dos seus metadados, sitemap, robots.txt
Internacionalize seus metadados, sitemap, robots.txt usando a função generateMetadata fornecida pelo Next.js para garantir uma melhor descoberta pelos motores de busca em todas as localidades. - Localize os Links
Localize os Links usando o componente Link para redirecionar o usuário para a URL apropriada com prefixo de localidade. É importante garantir a descoberta das suas páginas em todas as localidades. - Automatize testes e traduções
Automatizar testes e traduções ajuda a economizar tempo na manutenção da sua aplicação multilíngue.
Veja nossa documentação listando tudo o que você precisa saber sobre internacionalização e SEO: Internacionalização (i18n) com next-intl.
Guia Passo a Passo para Configurar o i18next em uma Aplicação Next.js
Veja Application Template no GitHub.
Aqui está a estrutura do projeto que vamos criar:
.├── i18n.config.ts└── src # Src é opcional ├── locales │ ├── en │ │ ├── common.json │ │ └── about.json │ └── fr │ ├── common.json │ └── about.json ├── types │ └── i18next.d.ts ├── app │ ├── proxy.ts │ ├── i18n │ │ └── server.ts │ └── [locale] │ ├── layout.tsx │ ├── (home) # / (Grupo de Rotas para não poluir todas as páginas com mensagens da home) │ │ ├── layout.tsx │ │ └── page.tsx │ └── about # /about │ ├── layout.tsx │ └── page.tsx └── components ├── I18nProvider.tsx ├── ClientComponent.tsx └── ServerComponent.tsxPasso 1: Instalar Dependências
Instale os pacotes necessários usando npm:
npm install i18next react-i18next i18next-resources-to-backend- i18next: O framework principal de internacionalização que gerencia o carregamento e a gestão das traduções.
- react-i18next: Bindings do React para i18next que fornecem hooks como useTranslation para componentes cliente.
- i18next-resources-to-backend: Um plugin que permite o carregamento dinâmico dos arquivos de tradução, permitindo carregar apenas os namespaces que você precisa.
Passo 2: Configure Seu Projeto
Crie um arquivo de configuração para definir os seus locales suportados, o locale padrão e funções auxiliares para a localização de URLs. Este arquivo serve como a fonte única de verdade para a sua configuração i18n e garante segurança de tipos em toda a sua aplicação.
Centralizar a configuração dos seus locales previne inconsistências e facilita a adição ou remoção de locales no futuro. As funções auxiliares garantem uma geração consistente de URLs para SEO e roteamento.
Copiar o código para a área de transferência
// Defina os locales suportados como um array const para segurança de tipos// A asserção 'as const' faz o TypeScript inferir tipos literais ao invés de string[]export const locales = ["en", "fr"] as const;// Extraia o tipo Locale do array de locales// Isso cria um tipo união: "en" | "fr"export type Locale = (typeof locales)[number];// Define o locale padrão usado quando nenhum locale é especificadoexport const defaultLocale: Locale = "en";// Idiomas da direita para a esquerda que precisam de tratamento especial na direção do textoexport const rtlLocales = ["ar", "he", "fa", "ur"] as const;// Verifica se um locale requer direção de texto RTL (da direita para a esquerda)// Usado para idiomas como Árabe, Hebraico, Persa e Urduexport const isRtl = (locale: string) => (rtlLocales as readonly string[]).includes(locale);// Gera um caminho localizado para um dado locale e caminho// Caminhos do locale padrão não possuem prefixo (ex: "/about" ao invés de "/en/about")// Outros locales são prefixados (ex: "/fr/about")export function localizedPath(locale: string, path: string) { return locale === defaultLocale ? path : `/${locale}${path}`;}// URL base para URLs absolutas (usada em sitemaps, metadados, etc.)const ORIGIN = "https://example.com";// Gera uma URL absoluta com prefixo de locale// Usado para metadados SEO, sitemaps e URLs canônicasexport function absoluteUrl(locale: string, path: string) { return `${ORIGIN}${localizedPath(locale, path)}`;}// Usado para definir o cookie de locale no navegadorexport function getCookie(locale: Locale) { return [ `NEXT_LOCALE=${locale}`, "Path=/", `Max-Age=${60 * 60 * 24 * 365}`, // 1 ano "SameSite=Lax", ].join("; ");}Passo 3: Centralizar Namespaces de Tradução
Crie uma fonte única de verdade para cada namespace que sua aplicação expõe. Reutilizar essa lista mantém o código do servidor, cliente e ferramentas sincronizados e desbloqueia a tipagem forte para os helpers de tradução.
Copiar o código para a área de transferência
export const namespaces = ["common", "about"] as const;export type Namespace = (typeof namespaces)[number];Passo 4: Tipar Fortemente as Chaves de Tradução com TypeScript
Aumente o i18next para apontar para seus arquivos de idioma canônicos (geralmente em inglês). O TypeScript então infere as chaves válidas por namespace, assim as chamadas para t() são verificadas de ponta a ponta.
Copiar o código para a área de transferência
import "i18next";declare module "i18next" { interface CustomTypeOptions { defaultNS: "common"; resources: { common: typeof import("@/locales/en/common.json"); about: typeof import("@/locales/en/about.json"); }; }}Dica: Armazene esta declaração em src/types (crie a pasta se ela não existir). O Next.js já inclui src no tsconfig.json, então a extensão é detectada automaticamente. Caso contrário, adicione o seguinte ao seu arquivo tsconfig.json:
Copiar o código para a área de transferência
{ "include": ["src/types/**/*.ts"],}Com isso configurado, você pode contar com autocompletar e verificações em tempo de compilação:
import { useTranslation, type TFunction } from "react-i18next";const { t } = useTranslation("about");// OK, tipado: t("counter.increment")// ERRO, erro de compilação: t("doesNotExist")export type AboutTranslator = TFunction<"about">;Passo 5: Configurar a Inicialização do i18n no Lado do Servidor
Crie uma função de inicialização do lado do servidor que carrega traduções para componentes do servidor. Esta função cria uma instância separada do i18next para renderização do lado do servidor, garantindo que as traduções sejam carregadas antes da renderização.
Componentes do servidor precisam de sua própria instância do i18next porque eles são executados em um contexto diferente dos componentes do cliente. Pré-carregar traduções no servidor evita o flash de conteúdo não traduzido e melhora o SEO ao garantir que os motores de busca vejam o conteúdo traduzido.
Copiar o código para a área de transferência
import { createInstance } from "i18next";import { initReactI18next } from "react-i18next/initReactI18next";import resourcesToBackend from "i18next-resources-to-backend";import { defaultLocale } from "@/i18n.config";import { namespaces, type Namespace } from "@/i18n.namespaces";// Configurar carregamento dinâmico de recursos para o i18next// Esta função importa dinamicamente arquivos JSON de tradução com base no locale e namespace// Exemplo: locale="fr", namespace="about" -> importa "@/locales/fr/about.json"const backend = resourcesToBackend( (locale: string, namespace: string) => import(`@/locales/${locale}/${namespace}.json`));const DEFAULT_NAMESPACES = [ namespaces[0],] as const satisfies readonly Namespace[];/** * Inicializa a instância do i18next para renderização no lado do servidor * * @returns Instância do i18next inicializada pronta para uso no servidor */export async function initI18next( locale: string, ns: readonly Namespace[] = DEFAULT_NAMESPACES) { // Cria uma nova instância do i18next (separada da instância do lado do cliente) const i18n = createInstance(); // Inicializa com integração React e carregador backend await i18n .use(initReactI18next) // Habilita suporte a hooks do React .use(backend) // Habilita carregamento dinâmico de recursos .init({ lng: locale, fallbackLng: defaultLocale, ns, // Carrega apenas os namespaces especificados para melhor desempenho defaultNS: "common", // Namespace padrão quando nenhum é especificado interpolation: { escapeValue: false }, // Não escapar HTML (React lida com proteção XSS) react: { useSuspense: false }, // Desabilita Suspense para compatibilidade SSR returnNull: false, // Retorna string vazia ao invés de null para chaves ausentes initImmediate: false, // Adiar a inicialização até que os recursos sejam carregados (SSR mais rápido) }); return i18n;}Passo 6: Criar o Provedor i18n no Lado do Cliente
Crie um componente provedor no cliente que envolva sua aplicação com o contexto do i18next. Este provedor recebe traduções pré-carregadas do servidor para evitar o flash de conteúdo não traduzido (FOUC) e evitar buscas duplicadas.
Componentes do cliente precisam de sua própria instância do i18next que roda no navegador. Ao aceitar recursos pré-carregados do servidor, garantimos uma hidratação suave e evitamos o flash de conteúdo. O provedor também gerencia mudanças de locale e carregamento dinâmico de namespaces.
Copiar o código para a área de transferência
"use client";import { useEffect, useState } from "react";import { I18nextProvider } from "react-i18next";import { createInstance, type ResourceLanguage } from "i18next";import { initReactI18next } from "react-i18next/initReactI18next";import resourcesToBackend from "i18next-resources-to-backend";import { defaultLocale } from "@/i18n.config";import { namespaces as allNamespaces, type Namespace } from "@/i18n.namespaces";// Configurar carregamento dinâmico de recursos para o lado do cliente// Mesmo padrão do lado do servidor, mas esta instância roda no navegadorconst backend = resourcesToBackend( (locale: string, namespace: string) => import(`@/locales/${locale}/${namespace}.json`));type Props = { locale: string; namespaces?: readonly Namespace[]; // Recursos pré-carregados do servidor (previne FOUC - Flash de Conteúdo Não Traduzido) // Formato: { namespace: translationBundle } resources?: Record<Namespace, ResourceLanguage>; children: React.ReactNode;};/** * Provedor i18n do lado cliente que envolve o app com o contexto i18next * Recebe recursos pré-carregados do servidor para evitar re-busca das traduções */export default function I18nProvider({ locale, namespaces = [allNamespaces[0]] as const, resources, children,}: Props) { // Cria a instância i18n uma vez usando o inicializador lazy do useState // Isso garante que a instância seja criada apenas uma vez, não a cada renderização const [i18n] = useState(() => { const i18nInstance = createInstance(); i18nInstance .use(initReactI18next) .use(backend) .init({ lng: locale, fallbackLng: defaultLocale, ns: namespaces, // Se recursos forem fornecidos (do servidor), use-os para evitar busca no cliente // Isso previne FOUC e melhora a performance no carregamento inicial resources: resources ? { [locale]: resources } : undefined, defaultNS: "common", interpolation: { escapeValue: false }, react: { useSuspense: false }, returnNull: false, // Impede que valores indefinidos sejam retornados }); return i18nInstance; }); // Atualiza o idioma quando a prop locale mudar useEffect(() => { i18n.changeLanguage(locale); }, [locale, i18n]); // Garante que todos os namespaces necessários sejam carregados no cliente // Usando join("|") como dependência para comparar arrays corretamente useEffect(() => { i18n.loadNamespaces(namespaces); }, [namespaces.join("|"), i18n]); // Fornece a instância i18n para todos os componentes filhos via contexto React return <I18nextProvider i18n={i18n}>{children}</I18nextProvider>;}Passo 7: Definir Rotas Dinâmicas por Locale
Configure o roteamento dinâmico para os locales criando um diretório [locale] na sua pasta app. Isso permite que o Next.js gerencie o roteamento baseado em locale, onde cada locale se torna um segmento da URL (ex.: /en/about, /fr/about).
Usar rotas dinâmicas permite que o Next.js gere páginas estáticas para todos os locales no momento da build, melhorando a performance e SEO. O componente layout define os atributos HTML lang e dir com base no locale, o que é crucial para acessibilidade e compreensão pelos motores de busca.
Copiar o código para a área de transferência
import type { ReactNode } from "react";import { locales, defaultLocale, isRtl, type Locale } from "@/i18n.config";// Desativar parâmetros dinâmicos - todos os locais devem ser conhecidos em tempo de build// Isso garante a geração estática para todas as rotas de localidadeexport const dynamicParams = false;/** * Gera parâmetros estáticos para todos os locais em tempo de build * Next.js irá pré-renderizar páginas para cada localidade retornada aqui * Exemplo: [{ locale: "en" }, { locale: "fr" }] */export function generateStaticParams() { return locales.map((locale) => ({ locale }));}/** * Componente de layout raiz que gerencia atributos HTML específicos do local * Define o atributo lang e a direção do texto (ltr/rtl) com base no local */export default function LocaleLayout({ children, params,}: { children: ReactNode; params: { locale: string };}) { /// Validar o locale a partir dos parâmetros da URL /// Se um locale inválido for fornecido, usar o locale padrão const locale: Locale = (locales as readonly string[]).includes(params.locale) ? (params.locale as any) : defaultLocale; /// Determinar a direção do texto com base no locale /// Idiomas RTL como o árabe precisam de dir="rtl" para renderização correta do texto const dir = isRtl(locale) ? "rtl" : "ltr"; return ( <html lang={locale} dir={dir}> <body>{children}</body> </html> );}Passo 8: Crie Seus Arquivos de Tradução
Crie arquivos JSON para cada locale e namespace. Essa estrutura permite organizar as traduções de forma lógica e carregar apenas o que você precisa para cada página.
Organizar traduções por namespace (por exemplo, common.json, about.json) permite a divisão de código e reduz o tamanho do bundle. Você carrega apenas as traduções necessárias para cada página, melhorando o desempenho.
Copiar o código para a área de transferência
{ "appTitle": "Next.js i18n App", "appDescription": "Example Next.js application with internationalization using i18next"}Copiar o código para a área de transferência
{ "appTitle": "Application Next.js i18n", "appDescription": "Exemple d'application Next.js avec internationalisation utilisant i18next"}Copiar o código para a área de transferência
{ "title": "Home", "description": "Home page description", "welcome": "Welcome", "greeting": "Hello, world!", "aboutPage": "About Page", "documentation": "Documentation"}Copiar o código para a área de transferência
{ "title": "Início", "description": "Descrição da página inicial", "welcome": "Bem-vindo", "greeting": "Olá, mundo!", "aboutPage": "Página Sobre", "documentation": "Documentação"}Copiar o código para a área de transferência
{ "title": "Sobre", "description": "Descrição da página Sobre", "counter": { "label": "Contador", "increment": "Incrementar", "description": "Clique no botão para aumentar o contador" }}Passo 9: Utilize Traduções nas Suas Páginas
Crie um componente de página que inicialize o i18next no servidor e passe as traduções tanto para os componentes do servidor quanto para os do cliente. Isso garante que as traduções sejam carregadas antes da renderização e evita o piscar de conteúdo.
A inicialização no lado do servidor carrega as traduções antes da página ser renderizada, melhorando o SEO e prevenindo o FOUC (Flash of Unstyled Content). Ao passar os recursos pré-carregados para o provedor do cliente, evitamos buscas duplicadas e garantimos uma hidratação suave.
Copiar o código para a área de transferência
import I18nProvider from "@/components/I18nProvider";import { initI18next } from "@/app/i18n/server";import type { Locale } from "@/i18n.config";import { namespaces as allNamespaces, type Namespace } from "@/i18n.namespaces";import type { ResourceLanguage } from "i18next";import ClientComponent from "@/components/ClientComponent";import ServerComponent from "@/components/ServerComponent";/** * Componente de página do servidor que gerencia a inicialização do i18n * Pré-carrega traduções no servidor e as passa para componentes cliente */export default async function AboutPage({ params: { locale },}: { params: { locale: Locale };}) { // Define quais namespaces de tradução esta página precisa // Reutiliza a lista centralizada para segurança de tipo e autocompletar const pageNamespaces = allNamespaces; // Inicializa o i18next no servidor com os namespaces necessários // Isso carrega arquivos JSON de tradução no lado do servidor const i18n = await initI18next(locale, pageNamespaces); // Obter uma função de tradução fixa para o namespace "about" // getFixedT fixa o namespace, então usa-se t("title") em vez de t("about:title") const tAbout = i18n.getFixedT(locale, "about"); // Extrair os pacotes de tradução da instância i18n // Esses dados são passados para o I18nProvider para hidratar o i18n no cliente // Evita FOUC (Flash of Untranslated Content) e evita buscas duplicadas const resources = Object.fromEntries( pageNamespaces.map((ns) => [ns, i18n.getResourceBundle(locale, ns)]) ) satisfies Record<Namespace, ResourceLanguage>; return ( <I18nProvider locale={locale} namespaces={pageNamespaces} resources={resources} > <main> <h1>{tAbout("title")}</h1> <ClientComponent /> <ServerComponent t={tAbout} locale={locale} count={0} /> </main> </I18nProvider> );}Passo 10: Usar Traduções em Componentes Cliente
Componentes cliente podem usar o hook useTranslation para acessar traduções. Este hook fornece acesso à função de tradução e à instância i18n, permitindo traduzir conteúdo e acessar informações de locale.
Componentes cliente precisam de hooks do React para acessar traduções. O hook useTranslation integra-se perfeitamente com o i18next e fornece atualizações reativas quando o locale muda.
Certifique-se de que a página/provider inclua apenas os namespaces que você precisa (ex: about).
Se você usar React < 19, memorize formatadores pesados como Intl.NumberFormat.
Copiar o código para a área de transferência
"use client";import { useState } from "react";import { useTranslation } from "react-i18next";/** * Exemplo de componente cliente usando hooks do React para traduções * Pode usar hooks como useState, useEffect e useTranslation */const ClientComponent = () => { // O hook useTranslation fornece acesso à função de tradução e à instância i18n // Especifica o namespace para carregar apenas as traduções do namespace "about" const { t, i18n } = useTranslation("about"); const [count, setCount] = useState(0); // Cria um formatador de números sensível à localidade // i18n.language fornece a localidade atual (ex: "en", "fr") // Intl.NumberFormat formata números conforme as convenções da localidade const numberFormat = new Intl.NumberFormat(i18n.language); return ( <div className="flex flex-col items-center gap-4"> {/* Formatar número usando formatação específica do locale */} <p className="text-5xl font-bold text-white m-0"> {numberFormat.format(count)} </p> <button type="button" className="flex h-12 w-full items-center justify-center gap-2 rounded-full bg-foreground px-5 text-background transition-colors hover:bg-[#383838] dark:hover:bg-[#ccc] md:w-[158px]" aria-label={t("counter.label")} onClick={() => setCount((c) => c + 1)} > {t("counter.increment")} </button> </div> );};export default ClientComponent;Passo 11: Usar Traduções em Componentes de Servidor
Componentes server não podem usar React hooks, então eles recebem traduções via props de seus componentes pai. Essa abordagem mantém os componentes server síncronos e permite que sejam aninhados dentro de componentes client.
Componentes server que podem ser aninhados sob limites client precisam ser síncronos. Ao passar strings traduzidas e informações de locale como props, evitamos operações assíncronas e garantimos uma renderização adequada.
Copiar o código para a área de transferência
import type { TFunction } from "i18next";type ServerComponentProps = { // Função de tradução passada do componente server pai // Componentes server não podem usar hooks, então as traduções vêm via props t: TFunction<"about">; locale: string; count: number;};/** * Exemplo de componente server - recebe traduções via props * Pode ser aninhado dentro de componentes client (componentes server assíncronos) * Não pode usar React hooks, então todos os dados devem vir das props ou operações assíncronas */const ServerComponent = ({ t, locale, count }: ServerComponentProps) => { // Formata número no servidor usando locale // Isso é executado no servidor durante SSR, melhorando o carregamento inicial da página const formatted = new Intl.NumberFormat(locale).format(count); return ( <div className="flex flex-col items-center gap-4"> <p className="text-5xl font-bold text-white m-0">{formatted}</p> {/* Usa a função de tradução passada como prop */} <div className="flex flex-col items-center gap-2"> <span className="text-xl font-semibold text-white"> {t("counter.label")} </span> <span className="text-sm opacity-80 italic"> {t("counter.description")} </span> </div> </div> );};export default ServerComponent;(Opcional) Passo 12: Alterar o idioma do seu conteúdo
Para alterar o idioma do seu conteúdo no Next.js, a forma recomendada é usar URLs prefixadas com o locale e links do Next.js. O exemplo abaixo lê o locale atual da rota, remove-o do pathname e renderiza um link para cada locale disponível.
Copiar o código para a área de transferência
"use client";import Link from "next/link";import { useParams, usePathname } from "next/navigation";import { useMemo } from "react";import { defaultLocale, getCookie, type Locale, locales } from "@/i18n.config";export default function LocaleSwitcher() { const params = useParams(); const pathname = usePathname(); const activeLocale = (params?.locale as Locale | undefined) ?? defaultLocale; const getLocaleLabel = (locale: Locale): string => { try { const displayNames = new Intl.DisplayNames([locale], { type: "language", }); return displayNames.of(locale) ?? locale.toUpperCase(); } catch { return locale.toUpperCase(); } }; const basePath = useMemo(() => { if (!pathname) return "/"; const segments = pathname.split("/").filter(Boolean); if (segments.length === 0) return "/"; const maybeLocale = segments[0] as Locale; if ((locales as readonly string[]).includes(maybeLocale)) { const rest = segments.slice(1).join("/"); return rest ? `/${rest}` : "/"; } return pathname; }, [pathname]); return ( <nav aria-label="Seletor de idioma"> {(locales as readonly Locale[]).map((locale) => { const isActive = locale === activeLocale; const href = locale === defaultLocale ? basePath : `/${locale}${basePath}`; return ( <Link key={locale} href={href} aria-current={isActive ? "page" : undefined} onClick={() => { document.cookie = getCookie(locale); }} > {getLocaleLabel(locale)} </Link> ); })} </nav> );}(Opcional) Passo 13: Construir um componente Link localizado
Reutilizar URLs localizadas em toda a sua aplicação mantém a navegação consistente e otimizada para SEO. Envolva o next/link em um pequeno helper que prefixa rotas internas com o locale ativo, enquanto deixa URLs externas intactas.
Copiar o código para a área de transferência
"use client";import NextLink, { type LinkProps } from "next/link";import { useParams } from "next/navigation";import type { ComponentProps, PropsWithChildren } from "react";import { defaultLocale, type Locale, locales, localizedPath,} from "@/i18n.config";const isExternal = (href: string) => /^https?:\/\//.test(href);type LocalizedLinkProps = PropsWithChildren< Omit<LinkProps, "href"> & Omit<ComponentProps<"a">, "href"> & { href: string; locale?: Locale }>;export default function LocalizedLink({ href, locale, children, ...props}: LocalizedLinkProps) { const params = useParams(); const fallback = (params?.locale as Locale | undefined) ?? defaultLocale; const normalizedLocale = (locales as readonly string[]).includes(fallback) ? ((locale ?? fallback) as Locale) : defaultLocale; const normalizedPath = href.startsWith("/") ? href : `/${href}`; const localizedHref = isExternal(href) ? href : localizedPath(normalizedLocale, normalizedPath); return ( <NextLink href={localizedHref} {...props}> {children} </NextLink> );}Dica: Como LocalizedLink é um substituto direto, faça a migração gradualmente trocando os imports e deixando o componente lidar com URLs específicas de locale.
(Opcional) Passo 14: Acessar o locale ativo dentro das Server Actions
Server Actions frequentemente precisam do locale atual para emails, logs ou integrações com terceiros. Combine o cookie de locale definido pelo seu proxy com o cabeçalho Accept-Language como fallback.
Copiar o código para a área de transferência
"use server";import { cookies, headers } from "next/headers";import { defaultLocale, locales, type Locale } from "@/i18n.config";const KNOWN_LOCALES = new Set(locales as readonly string[]);const normalize = (value: string | undefined): Locale | undefined => { if (!value) return undefined; const base = value.toLowerCase().split("-")[0]; return KNOWN_LOCALES.has(base) ? (base as Locale) : undefined;};export async function getCurrentLocale(): Promise<Locale> { const cookieLocale = normalize(cookies().get("NEXT_LOCALE")?.value); if (cookieLocale) return cookieLocale; const headerLocale = normalize(headers().get("accept-language")); return headerLocale ?? defaultLocale;}// Exemplo de uma ação no servidor que usa a localidade atualexport async function stuffFromServer(formData: FormData) { const locale = await getCurrentLocale(); // Use a localidade para efeitos colaterais localizados (emails, CRM, etc.) console.log(`Stuff from server with locale ${locale}`);}Como o helper depende dos cookies e headers do Next.js, ele funciona em Route Handlers, Server Actions e outros contextos exclusivos do servidor.
(Opcional) Passo 15: Internacionalize Seus Metadados
Traduzir o conteúdo é importante, mas o principal objetivo da internacionalização é tornar seu site mais visível para o mundo. I18n é uma alavanca incrível para melhorar a visibilidade do seu site por meio de SEO adequado.
Metadados devidamente internacionalizados ajudam os motores de busca a entender quais idiomas estão disponíveis em suas páginas. Isso inclui configurar meta tags hreflang, traduzir títulos e descrições, e garantir que URLs canônicas estejam corretamente definidas para cada localidade.
Aqui está uma lista de boas práticas relacionadas ao SEO multilíngue:
- Defina as meta tags hreflang na tag <head> para ajudar os motores de busca a entender quais idiomas estão disponíveis na página
- Liste todas as traduções das páginas no sitemap.xml usando o esquema XML http://www.w3.org/1999/xhtml
- Não se esqueça de excluir as páginas prefixadas do robots.txt (por exemplo, /dashboard, /fr/dashboard, /es/dashboard)
- Use um componente Link personalizado para redirecionar para a página mais localizada (por exemplo, em francês <a href="/fr/about">À propos</a>)
Os desenvolvedores frequentemente esquecem de referenciar corretamente suas páginas entre os diferentes locais. Vamos corrigir isso:
Copiar o código para a área de transferência
import type { Metadata } from "next";import { locales, defaultLocale, localizedPath, absoluteUrl,} from "@/i18n.config";/** * Gera metadados SEO para cada versão local da página * Esta função é executada para cada localidade no momento da build */export async function generateMetadata({ params,}: { params: { locale: string };}): Promise<Metadata> { const { locale } = params; // Importa dinamicamente o arquivo de tradução para esta localidade // Usado para obter o título e a descrição traduzidos para os metadados const messages = (await import(`@/locales/${locale}/about.json`)).default; // Cria o mapeamento hreflang para todas as localidades // Ajuda os motores de busca a entender as alternativas de idioma // Formato: { "en": "/about", "fr": "/fr/about" } const languages = Object.fromEntries( locales.map((locale) => [locale, localizedPath(locale, "/about")]) ); return { title: messages.title, description: messages.description, alternates: { // URL canônica para esta versão do locale canonical: absoluteUrl(locale, "/about"), // Alternativas de idioma para SEO (tags hreflang) // "x-default" especifica a versão padrão do locale languages: { ...languages, "x-default": absoluteUrl(defaultLocale, "/about"), }, }, };}export default async function AboutPage() { return <h1>Sobre</h1>;}(Opcional) Passo 16: Internacionalize seu Sitemap
Gere um sitemap que inclua todas as versões de locale das suas páginas. Isso ajuda os motores de busca a descobrir e indexar todas as versões linguísticas do seu conteúdo.
Um sitemap devidamente internacionalizado garante que os motores de busca possam encontrar e indexar todas as versões linguísticas das suas páginas. Isso melhora a visibilidade nos resultados de busca internacionais.
Copiar o código para a área de transferência
import type { MetadataRoute } from "next";import { defaultLocale, locales } from "@/i18n";const origin = "https://example.com";const formatterLocalizedPath = (locale: string, path: string) => locale === defaultLocale ? `${origin}${path}` : `${origin}/${locale}${path}`;/** * Obter um mapa de todos os locais e seus caminhos localizados * * Exemplo de saída: * { * "en": "https://example.com", * "fr": "https://example.com/fr", * "es": "https://example.com/es", * "x-default": "https://example.com" * } */const getLocalizedMap = (path: string) => Object.fromEntries([ ...locales.map((locale) => [locale, formatterLocalizedPath(locale, path)]), ["x-default", formatterLocalizedPath(defaultLocale, path)], ]);// Gerar sitemap com todas as variantes de locale para melhor SEO// O campo alternates informa aos motores de busca sobre as versões de idiomaexport default function sitemap(): MetadataRoute.Sitemap { return [ { url: formatterLocalizedPath(defaultLocale, "/"), lastModified: new Date(), changeFrequency: "monthly", priority: 1.0, alternates: { languages: getLocalizedMap("/") }, }, { url: formatterLocalizedPath(defaultLocale, "/about"), lastModified: new Date(), changeFrequency: "monthly", priority: 0.7, alternates: { languages: getLocalizedMap("/about") }, }, ];}(Opcional) Passo 17: Internacionalize seu robots.txt
Crie um arquivo robots.txt que gerencie corretamente todas as versões de locale das suas rotas protegidas. Isso garante que os motores de busca não indexem páginas de administração ou dashboard em nenhum idioma.
Configurar corretamente o robots.txt para todos os locales impede que motores de busca indexem páginas sensíveis em qualquer idioma. Isso é crucial para segurança e privacidade.
Copiar o código para a área de transferência
import type { MetadataRoute } from "next";import { defaultLocale, locales } from "@/i18n";const origin = "https://example.com";// Gera caminhos para todos os locales (ex: /admin, /fr/admin, /es/admin)const withAllLocales = (path: string) => [ path, ...locales .filter((locale) => locale !== defaultLocale) .map((locale) => `/${locale}${path}`),];const disallow = [...withAllLocales("/dashboard"), ...withAllLocales("/admin")];export default function robots(): MetadataRoute.Robots { return { rules: { userAgent: "*", allow: ["/"], disallow }, host: origin, sitemap: `${origin}/sitemap.xml`, };}(Opcional) Passo 18: Configurar Middleware para Roteamento de Locale
Crie um proxy para detectar automaticamente o locale preferido do usuário e redirecioná-lo para a URL apropriada com prefixo de locale. Isso melhora a experiência do usuário ao mostrar o conteúdo no idioma preferido.
O middleware garante que os usuários sejam redirecionados automaticamente para o idioma preferido ao visitarem seu site. Ele também salva a preferência do usuário em um cookie para visitas futuras.
Copiar o código para a área de transferência
import { NextResponse, type NextRequest } from "next/server";import { defaultLocale, locales } from "@/i18n.config";// Regex para corresponder a arquivos com extensões (ex.: .js, .css, .png)// Usado para excluir ativos estáticos do roteamento de localeconst PUBLIC_FILE = /\.[^/]+$/;/** * Extrai o locale do cabeçalho Accept-Language * Trata formatos como "fr-CA", "en-US", etc. * Retorna o locale padrão se o idioma do navegador não for suportado */const pickLocale = (accept: string | null) => { // Obtém a primeira preferência de idioma (ex.: "fr-CA" de "fr-CA,en-US;q=0.9") const raw = accept?.split(",")[0] ?? defaultLocale; // Extrai o código base do idioma (ex.: "fr" de "fr-CA") const base = raw.toLowerCase().split("-")[0]; // Verifica se suportamos este locale, caso contrário usa o padrão return (locales as readonly string[]).includes(base) ? base : defaultLocale;};/** * Proxy do Next.js para detecção e roteamento de locale * Executa em toda requisição antes da renderização da página * Redireciona automaticamente para URLs com prefixo de locale quando necessário */export function proxy(request: NextRequest) { const { pathname } = request.nextUrl; // Ignorar proxy para internos do Next.js, rotas API e arquivos estáticos // Estes não devem ter prefixo de locale if ( pathname.startsWith("/_next") || pathname.startsWith("/api") || pathname.startsWith("/static") || PUBLIC_FILE.test(pathname) ) { return; } // Verificar se a URL já possui prefixo de locale // Exemplo: "/fr/about" ou "/en" retornaria true const hasLocale = (locales as readonly string[]).some( (locale) => pathname === `/${locale}` || pathname.startsWith(`/${locale}/`) ); // Se não houver prefixo de localidade, detecta a localidade e redireciona if (!hasLocale) { // Tenta obter a localidade do cookie primeiro (preferência do usuário) const cookieLocale = request.cookies.get("NEXT_LOCALE")?.value; // Usa a localidade do cookie se for válida, caso contrário detecta pelos cabeçalhos do navegador const locale = cookieLocale && (locales as readonly string[]).includes(cookieLocale) ? cookieLocale : pickLocale(request.headers.get("accept-language")); // Clona a URL para modificar o pathname const url = request.nextUrl.clone(); // Adiciona o prefixo de localidade ao pathname // Trata o caminho raiz especialmente para evitar barra dupla url.pathname = `/${locale}${pathname === "/" ? "" : pathname}`; // Criar resposta de redirecionamento e definir cookie de localidade const res = NextResponse.redirect(url); res.cookies.set("NEXT_LOCALE", locale, { path: "/" }); return res; }}export const config = { matcher: [ // Corresponder a todos os caminhos exceto: // - Rotas de API (/api/*) // - Internos do Next.js (/_next/*) // - Arquivos estáticos (/static/*) // - Arquivos com extensões (.*\\..*) "/((?!api|_next|static|.*\\..*).*)", ],};(Opcional) Passo 19: Automatize Suas Traduções Usando o Intlayer
Intlayer é uma biblioteca gratuita e open-source projetada para auxiliar o processo de localização na sua aplicação. Enquanto o i18next gerencia o carregamento e a gestão das traduções, o Intlayer ajuda a automatizar o fluxo de trabalho das traduções.
Gerenciar traduções manualmente pode ser demorado e propenso a erros. O Intlayer automatiza o teste, a geração e o gerenciamento das traduções, economizando seu tempo e garantindo consistência em toda a sua aplicação.
O Intlayer permite que você:
Declare seu conteúdo onde quiser na sua codebase
O Intlayer permite declarar seu conteúdo onde quiser na sua codebase usando arquivos .content.{ts|js|json}. Isso possibilita uma melhor organização do seu conteúdo, garantindo melhor legibilidade e manutenção da sua codebase.Teste traduções faltantes
O Intlayer fornece funções de teste que podem ser integradas no seu pipeline de CI/CD ou nos seus testes unitários. Saiba mais sobre testar suas traduções.Automatize suas traduções, Intlayer fornece uma CLI e uma extensão para VSCode para automatizar suas traduções. Pode ser integrado ao seu pipeline de CI/CD. Saiba mais sobre automatizar suas traduções. Você pode usar sua própria chave de API e o provedor de IA de sua escolha. Também oferece traduções contextuais, veja preencher conteúdo.
- Conectar conteúdo externo
Automatize suas traduções,
Intlayer fornece uma CLI e uma extensão para VSCode para automatizar suas traduções. Pode ser integrado ao seu pipeline de CI/CD. Saiba mais sobre automatizar suas traduções.
Você pode usar sua própria chave de API e o provedor de IA de sua escolha. Também oferece traduções com contexto, veja preencher conteúdo.Conectar conteúdo externo
Intlayer permite conectar seu conteúdo a um sistema externo de gerenciamento de conteúdo (CMS). Para buscá-lo de forma otimizada e inseri-lo em seus recursos JSON. Saiba mais sobre busca de conteúdo externo.Editor visual
Intlayer oferece um editor visual gratuito para editar seu conteúdo usando um editor visual. Saiba mais sobre edição visual das suas traduções.
E mais. Para descobrir todos os recursos fornecidos pelo Intlayer, consulte a documentação Interesse do Intlayer.