blogue do 18ways

Como Adicionar Vários Idiomas a uma Aplicação Next.js

Como adicionar i18n multilíngue e localização a uma aplicação Next.js existente sem prejudicar o SEO nem reconstruir tudo de raiz.

I18n (internacionalização), l10n (localização), suporte multilingue… como lhe quiseres chamar, acabas por precisar de atualizar a tua app Next.js para suportar mais do que uma língua.

A dificuldade não está em traduzir uma frase. Está em adicionar vários idiomas a uma aplicação Next.js real sem prejudicar o SEO, complicar a sua base de código ou criar para si um problema de manutenção.

Se quiser saltar diretamente para o código, veja os exemplos GitHub do 18ways-next.

Configura primeiro a tua infraestrutura

Antes de mais, temos de instalar os nossos pacotes. Vamos configurar as bibliotecas 18ways, mas ferramentas como o i18next funcionam bem se o seu projecto for simples e tiver apenas conteúdo estático.

bash
npm install @18ways/next @18ways/react

Crie o seu ficheiro de configuração:

js
// 18ways.config.js
module.exports = {
  apiKey: 'pk_dummy_demo_token',
  baseLocale: 'en-GB',
  router: 'app', // 'app', or 'path' depending on which Next.js router you are using
};

Envolve a configuração do teu Next.js:

js
// next.config.js
const { withWays } = require('@18ways/next/config');
 
const nextConfig = {
  /*
   * your normal Next.js config here
   */ 
};
 
module.exports = withWays(nextConfig);

Adiciona um proxy raiz para que / possa redirecionar para a localidade certa:

js
// proxy.js
export { default, config } from '@18ways/next/proxy';

Depois, adiciona um layout localizado:

jsx
// app/layout.jsx
import './styles.css';
import { WaysRoot } from '@18ways/next/server';
 
export default function RootLayout({ children }) {
  return (
    <html lang="en">
      <body className="next-demo-body">
        <WaysRoot>{children}</WaysRoot>
      </body>
    </html>
  );
}

E pronto! Agora tem:

Traduz a tua primeira página

Agora já pode traduzir a sua primeira página. Ferramentas como o i18next vão exigir que extraia todo o seu texto para chaves de tradução e, depois, as referencie no seu código. O aspeto exato disto dependerá da biblioteca que tiver escolhido.

Se estiver a usar o 18ways, pode simplesmente envolver o texto que quer traduzir num componente <T>:

jsx
// src/components/MyExampleComponent.jsx
import { useT, T } from '@18ways/react';
 
export default function MyExampleComponent() {
  const t = useT();
 
  return (
    <div>
      <T>Hello world!</T>
      <img
        src="https://example.com/image.png"
        alt={t('Example image')}
      />
    </div>
  );
}

Provavelmente queres permitir que os utilizadores alterem a sua língua:

jsx
// src/components/Footer.jsx
import { T, LanguageSwitcher } from '@18ways/react';
 
export default function Footer() {
  return (
    <footer>
      <T>My footer content</T>
      <LanguageSwitcher />
    </footer>
  );
}

Cuidado com as armadilhas

Quando a infraestrutura está montada, há algumas formas habituais de estes projectos correrem mal.

Renderização no servidor

A renderização do lado do servidor (SSR) é o que permite à sua aplicação Next.js servir HTML pré-renderizado. Isto é vital tanto para SEO como para evitar que o utilizador veja flashes de conteúdo errado ou língua incorrecta.

Se estiveres a usar uma biblioteca como o i18next, tens de ter muito cuidado para que as tuas traduções sejam carregadas e preenchidas durante a renderização do lado do servidor. Podes testar isto verificando o view-source: da tua página, por exemplo view-source:http://localhost:3000/. Deves verificar isto também em produção, para garantir que funciona igualmente na tua build de produção.

Se estiveres a usar o 18ways, não te preocupes com isto. Fica tudo tratado por ti.

Chaves de tradução

Se estiver a usar o 18ways, não precisa de se preocupar com isto de todo. O 18ways não precisa de chaves de tradução; pode deixar o seu texto no sítio, como habitualmente.

Muitos sistemas de i18n obrigam-te a separar o código em chaves de tradução. É importante ter cuidado com a forma como nomeias estas chaves.

As chaves más são vagas:

js
// bad-keys.en-GB.js
module.exports = {
  title: 'Continue',
  button: 'Pay now',
  label: 'Home',
};

Essas chaves dizem quase nada a tradutores e programadores sobre onde o texto aparece.

Chaves melhores incluem contexto:

js
// better-keys.en-GB.js
module.exports = {
  'checkout.payment.primaryButton': 'Pay now',
  'checkout.payment.stepTitle': 'Complete your payment',
  'account.sidebar.homeLink': 'Home',
};

Evite também gerar chaves dinamicamente:

js
// bad-dynamic-keys.js
const key = `checkout.${status}.${buttonType}`;
const translatedText = t(key);

Isto vai partir as ferramentas do IDE que tentam tornar as chaves de tradução menos penosas. Também tornará extremamente difícil procurar e limpar chaves de tradução antigas.

js
// better-dynamic-keys.js
const keyMap = {
  success: {
    primary: t('checkout.success.primary'),
    default: t('checkout.success.default'),
  },
  error: {
    primary: t('checkout.error.primary'),
    default: t('checkout.error.default'),
  },
};
 
const translatedText = keyMap[status][buttonType];

Isto é menos DRY, mas é a melhor forma de evitar que as chaves de tradução se tornem ingovernáveis.

Ainda melhor é usar uma ferramenta como o 18ways; assim, nunca precisas de chaves de tradução de todo:

jsx
// app/[lang]/checkout/page.jsx
<T>Pay now</T>

Deteção de localidade

Deteção de localidade significa decidir que idioma um utilizador deve ver antes de o alterar explicitamente.

Normalmente, isso envolve alguma combinação de:

Em middleware normal do Next.js, isso muitas vezes tem este aspeto:

js
// middleware.js
 
/**
 * You don't need any of this if you're using 18ways 
 */
 
import { NextResponse } from 'next/server';
 
const acceptedLocales = ['en-GB', 'fr-FR'];
 
export function middleware(request) {
  const savedLocale =
    request.cookies.get('preferred-locale')?.value;
  const browserLocale =
    request.headers
      .get('accept-language')
      ?.split(',')[0] || 'en-GB';
 
  const locale = acceptedLocales.includes(savedLocale)
    ? savedLocale
    : acceptedLocales.includes(browserLocale)
      ? browserLocale
      : 'en-GB';
 
  if (request.nextUrl.pathname === '/') {
    return NextResponse.redirect(
      new URL(`/${locale}`, request.url)
    );
  }
 
  return NextResponse.next();
}

Algumas bibliotecas têm utilitários para tornar isto mais fácil, em graus variáveis. Com o 18ways, a deteção inicial e a camada de redirecionamento são tratadas por si.

Datas e primitivos específicos da localidade

Diferentes locais formatam datas, números e dinheiro de forma diferente.

Por exemplo:

Em JavaScript puro, tens de tratar disso tu mesmo com Intl:

js
// formatting-dates-and-currency.js
const myLocale = getCurrentLocale(); // depends on your lib
 
const someTimestamp = new Date('2026-04-13T09:00:00Z');
const dateLabel = new Intl.DateTimeFormat('fr-FR', {
  dateStyle: 'long',
}).format(someTimestamp);
 
const someMoney = {
  amount: 1999,
  currency: 'EUR',
};
const moneyLabel = new Intl.NumberFormat('de-DE', {
  style: 'currency',
  currency: someMoney.currency,
}).format(someMoney.amount);
 
const translatedText = t(
  'my.translation.key',
  { dateLabel, moneyLabel }
);

Se estiver a usar o 18ways, isto é tratado por si:

jsx
const someTimestamp = new Date('2026-04-13T09:00:00Z');
const someMoney = {
  amount: 1999,
  currency: 'EUR',
};
 
<T>My text with {{ someTimestamp }} and {{ someMoney }}</T>

Juntar strings

Não construa UI traduzida juntando fragmentos:

jsx
// bad-string-joining.jsx
const clickHereText = t('click.here');
const toGetStartedText = t('to.get.started')
<p><a href="#">{clickHereText}</a> {toGetStartedText}.</p>

Isto vai falhar em várias línguas.

Poderia dizer “👉👉Clique aqui👈👈 para começar” em inglês, mas em francês é mais natural dizer “Pour commencer, 👉👉cliquez ici👈👈”. Algumas línguas, como o japonês, vão até precisar de palavras antes e depois, como “始めるには👉👉こちら👈👈をクリックしてください”.

A estrutura da frase pode mudar, por isso dividi-la em partes torna a boa tradução muito mais difícil.

As frases completas traduzem melhor porque os tradutores podem reorganizar as palavras naturalmente e ver o significado como uma unidade completa.

Se estiver a traduzir JSX como isto:

jsx
// rich-text-message.jsx
<p>
  Something <strong>that we want to be bold</strong>
</p>

terá de consultar a sua biblioteca de i18n sobre como gerir isto, uma vez que é tratado de forma muito diferente por bibliotecas diferentes.

Se estiver a usar o 18ways, pode simplesmente traduzir todo o bloco JSX como habitualmente.

jsx
// rich-text-message.jsx
<p>
  <T>Something <strong>that we want to be bold</strong></T>
</p>

Variáveis

As variáveis permitem-te manter a frase intacta, inserindo ao mesmo tempo valores em tempo de execução.

A maioria das bibliotecas de i18n suporta isto de alguma forma. Com 18ways:

jsx
// app/[lang]/page.jsx
<T>Hello {{ name: 'Ada' }}</T>

Esse padrão funciona bem para:

A frase mantém-se legível, e o valor mantém-se explícito.

Se também precisares de traduzir a própria variável, certifica-te de a envolver em t(...):

jsx
const animal = t('dog');
<T>Favourite animal: {{ animal }}</T>

Plurais

As regras de plural variam consoante a língua. O inglês tem uma forma plural (1 year, 2 years, etc.). O polaco tem várias formas plurais (1 rok, 2 lata, 5 lat). O japonês não tem forma plural de todo (1 年, 2 年, 3 年).

A maioria das bibliotecas de i18n permite-lhe especificar sintaxe ao estilo ICU para lidar com plurais:

jsx
// app/[lang]/page.jsx
<T>
  {{
    unreadCount,
    format:
      'plural, =0{No unread messages} =1{One unread message} other{{unreadCount} unread messages}',
  }}
</T>

O acima funcionará em 18 ways, mas na maioria dos casos pode simplesmente fazer isto:

jsx
// app/[lang]/page.jsx
<T>{{ unreadCount }} unread messages</T>

O 18ways trata dos plurais por ti!

Comece já

Adicionar vários idiomas a uma aplicação Next.js não precisa de se tornar um projeto de reescrita.

Se não estiver a usar uma solução como o 18ways — certifique-se de que o seu routing e SSR estão corretos e, depois, avance para a migração do seu texto para chaves de tradução, tendo o cuidado de evitar os problemas comuns de i18n.

Se estiver a usar o 18ways, então isto é tudo tratado por si, e só precisa de começar a envolver o seu texto em blocos <T>!

Alterando idioma
Como Adicionar Vários Idiomas a uma App Next.js | Blog 18ways