Executando verificação de segurança...
5

Internacionalização com I18n em projetos NextJs

O conteúdo deste post foi inspirado na solução que fiz para o meu site


Neste artigo tentarei ser breve e ensinar de forma rápida e clara como adicionar internacionalização em um projeto NextJs, seguindo o exemplo utilizado em meu próprio website.

Para este tutorial, utilizaremos do app router sem a utilização de roteamento i18n (isto é, a tradução não estará amarrada à rota, exemplo: "example.com/en")

Além disso, estaremos utilizando de um cookie para que o website possa ser entregue na linguagem correta ao usuário, este cookie será definido no primeiro carregamento do usuário ao website e poderá ser redefinido posteriormente por um componente seletor.

Setup do projeto

Caso ainda não tenha feito, inicie seu projeto Next.Js com a configuração de App Router.

Em seguida, instale o pacote next-intl

npm  install  next-intl

Em seguida, criaremos a seguinte estrutura de arquivos

├── i18n
	├── dictionaries
		├── pt.json (1)
		└── ...
	├── locales.ts (2)
	├── request.ts (3)
	└── setLocale.ts (4)
├── next.config.mjs (5)
└── app
	├── layout.tsx (6)
    └── page.tsx (7)

i18n/dictionaries/pt.json

A pasta i18n guardará tudo referente à localização e internacionalização para facilitar a estruturação do projeto.

A pasta dictionaries guardará nossas traduções. Estes arquivos podem ser carregados de forma estática ou dinâmica. Para a simplicidade, estaremos adicionando como um arquivo.

i18n/dictionaries/pt.json

{
  "HomePage": {
    "title": "Olá mundo!"
  }
}

i18n/locales.ts

Para que possamos guardar nossas constantes sobre a localização. (Este também pode ser guardado de forma dinâmica ou mesmo se tornar uma variável de ambiente)

i18n/locales.ts

export const SUPPORTED_LOCALES = ['en', 'pt']; // Traduções disponívels
export const DEFAULT_LOCALE = 'en'; // Idioma padrão utilizado como fallback
export const LOCALE_COOKIE_NAME = 'NEXT_LOCALE'; // Chave do cookie que utilizaremos para receber a tradução do servidor

i18n/request.ts

O next-intl utilizará da request para definir como proceder com componentes de servidor que utilizam da internacionalização, o arquivo de request trata da lógica para determinar a linguagem. A nossa abordagem aqui será a de utilizar dos cookies para observar a linguagem a ser utilizada.

i18n/request.ts

import { getRequestConfig } from 'next-intl/server';
import { cookies } from 'next/headers';
import { DEFAULT_LOCALE, SUPPORTED_LOCALES } from './locales';

export default getRequestConfig(async () => {
  const cookieLocale = cookies().get('NEXT_LOCALE')?.value;

  // Determina o locale
  const locale = cookieLocale || DEFAULT_LOCALE;

  // Verifica se é suportado ou retorna o padrão
  const finalLocale = SUPPORTED_LOCALES.includes(locale)
    ? locale
    : DEFAULT_LOCALE;

  return {
    locale: finalLocale,
    messages: (await import(`./dictionaries/${finalLocale}.json`)).default,
  };
});

i18n/setLocale.ts

Este arquivo servirá de utilitário para que possamos definir os cookies do lado do servidor.

i18n/setLocale.ts

'use server';

import { cookies } from 'next/headers';
import { LOCALE_COOKIE_NAME } from './locales';

export async function setUserLocale(locale: string) {
  cookies().set(LOCALE_COOKIE_NAME, locale);
}

next.config.mjs

Agora, configure o plug-in que cria um alias para fornecer uma configuração i18n específica da solicitação aos componentes do servidor. (que criamos como i18n/request.ts)

next.config.mjs

import createNextIntlPlugin from 'next-intl/plugin';

const withNextIntl = createNextIntlPlugin();

/** @type {import('next').NextConfig} */
const nextConfig = {};

export default withNextIntl(nextConfig);

app/layout.tsx

O código de idioma fornecido em i18n/request.ts está disponível via getLocale e pode ser usado para configurar o idioma do documento. Além disso, podemos usar este local para passar a configuração de i18n/request.ts para componentes do cliente via NextIntlClientProvider.

app/layout.tsx

import { NextIntlClientProvider } from 'next-intl';
import { getLocale, getMessages } from 'next-intl/server';

export default async function RootLayout({
  children,
}: {
  children: React.ReactNode;
}) {
  const locale = await getLocale();

  // Providing all messages to the client
  // side is the easiest way to get started
  const messages = await getMessages();

  return (
    <html lang={locale}>
      <body>
        <NextIntlClientProvider messages={messages}>
          {children}
        </NextIntlClientProvider>
      </body>
    </html>
  );
}

app/page.tsx

Para utilizar suas traduções, basta usar o hook useTranslations em seus componentes e páginas!

app/page.tsx

import { useTranslations } from 'next-intl';

export default function HomePage() {
  const t = useTranslations('HomePage');
  return <h1>{t('title')}</h1>;
}

Alterando a linguagem no primeiro carregamento

Embora a partir do setup o seu projeto já funcione com a internacionalização, para que seja possível alterar a linguagem é necessário que criemos um componente client-side que possa ler a linguagem do navegador e alterar o cookie. Para isso, basta criar um componente da seguinte forma:

import { useEffect } from 'react';
import {
  SUPPORTED_LOCALES,
  DEFAULT_LOCALE,
  LOCALE_COOKIE_NAME,
} from '../../i18n/locales';
import { setUserLocale } from '../../i18n/setLocale';

export default function LocaleSetter() {
  useEffect(() => {
    // Verifica se o cookie de locale já existe
    if (document.cookie.includes(LOCALE_COOKIE_NAME)) return;

    // Remove o país do locale (e.g. pt-BR e pt-PT se torna pt)
    const navigatorLang = navigator.language.split('-')[0];
    let locale = SUPPORTED_LOCALES.includes(navigatorLang)
      ? navigatorLang
      : DEFAULT_LOCALE;

    // Altera cookie
    setUserLocale(locale);
  }, []);

  return null;
}

Assim, podemos incluir o LocaleSetter em nosso app/layout.tsx e nossa aplicação já contará com traduções definidas pelo idioma do navegador!

A partir daqui, é possível também criar um componente seletor e utilizar da função setUserLocale para que a linguagem seja redefinida!


Até a próxima ;D

Carregando publicação patrocinada...