Implementando internacionalização(i18n) com Gatsby.js

Nesse post explico como implementei a internacionalização(i18n) na tela "Sobre mim" deste blog

internacionalizacao

Neste primeiro post decidi escrever sobre como implementei internacionalização(i18n) no meu blog. Por enquanto só atribui essa feature à tela "Sobre mim", porém é bem simples de globaliza-la na aplicação, o maior trabalho, de fato, será traduzir os textos😅. A internacionalização permite sua aplicação se expandir e se diversificar no âmbito internacional, além de transmitir uma ótima experiência de usuário.

Existem diversas bibliotecas que auxiliam no processo de internacionalização em uma aplicação, porém a que eu vi que se adapta melhor ao Gatsby.js é a i18next.

Sem mais delongas, vamos a implementação!

1 - Instalação

Primeiro, você deve instalar os seguintes pacotes: i18next o principal para internacionalização e react-i18next que permite o funcionamento com e framework React.

$ npm install i18next react-i18next --save

// ou utilizando yarn:

$ yarn add i18next react-i18next

Agora vamos criar uma pasta i18n para armazenar os arquivos de configuração e wrapper do componente que vamos criar em seguida. Também, uma pasta vazia locales para adicionarmos arquivos JSON com textos traduzidos em diferentes idiomas.

A estrutura de arquivos deve ficar assim:

project
└───src
│   └───components
│   └───i18n
│       │   config.js
│       │   withTrans.js (nosso componente wrapper)
│   └───locales
│   └───pages
│	...

2 - Configuração

No arquivo config.js, configurei para ter os idiomas português(pt) e inglês(en) :

import i18next from 'i18next';

i18next.init({
    fallbackLng: 'pt',
    resources: {
        'pt': {
            translations: require('../locales/pt/translations.json')
        },
        en: {
            translations: require('../locales/en/translations.json')
        }
    },
    ns: ['translations'],
    defaultNS: 'translations',
    returnObjects: true,
    debug: process.env.NODE_ENV === 'development',
    interpolation: {
        escapeValue: false,
    },
    react: {
        wait: true,
    },
});

i18next.languages = ['pt', 'en'];

export default i18next;

Estou colocando os textos de um certo idioma apenas no arquivo translations.json, porém é possível separar os textos em mítiplos arquivos, apenas populando a array ns: [].

3 - Criando um componente wrapper passando a instância do i18next

Este componente é implementado no arquivo withTrans.js:

import React, { Component } from 'react';
import i18next from './config';
import { I18nextProvider, withTranslation } from 'react-i18next';

export function withTrans(WrappedComponent) {
    WrappedComponent = withTranslation()(WrappedComponent);

    return class extends Component {
      render() {
        return (
          <I18nextProvider i18n={i18next}>
            <WrappedComponent {...this.props} language={i18next.language} />
          </I18nextProvider>
        );
      }
    }
}

O código acima será usado como um componente de ordem superior (HOC, do inglês Higher-Order Component) para trabalhar com o componente About(usado na página "Sobre mim"). Para disponibilizar a configuração do i18next em todos os nossos componentes e páginas, precisamos envolver o componente de layout com o I18nextProvider.

A função withTranslation() fornecida pelo react-18next passa via props a função t e a instância de i18n para o WrappedComponent que no nosso caso é o About.

4 - Usando HOC no componente About e traduzindo texto

Vamos importar o componente wrapper que criamos, dentro do component About:

import React from "react"
import LanguageMenu from "../components/LanguageMenu"
import { withTrans } from "../i18n/withTrans"

const About = ({ children, t, i18n }) => {
  return (
    <div>
      <LanguageMenu />

      <h1>{t("header")}</h1>

      <p>{t("paragraph1")}</p>

      <p>{t("paragraph2")}</p>

      <p>{t("paragraph3")}</p>
    </div>
  )
}

export default withTrans(About)

O componente wrapper é um HOC para obter a função t e a instância i18n dentro do componente About. Isso pode nos ajudar a traduzir qualquer texto no cabeçalho e rodapé.

Eu uso a desestruturação de objetos para simplificar como obtemos os objetos dentro do componente About. Assim que obtivermos a função t, ela procurará as chaves do nosso namespace padrão "translations". Os arquivos JSON com os textos traduzidos devem ser estruturados da seguinte forma:

|____locales
| |____pt
| | |____translations.json
| |____en
| | |____translations.json

Exemplo de arquivo translations.json:

{
    "header": "Cabeçalho em português",
    "paragraph1":"Texto paragrafo 1 em português",
    "paragraph2": "Texto paragrafo 2 em português",
    "paragraph3": "Texto paragrafo 3 em português"
}

5 - Adicionando menu dropdown para o usuário selecionar entre diferentes linguagens

A última coisa a ser feita é fazer um dropdown para que usuários consigam selecionar o idioma de sua preferência.

Optei por utilizar o Material-UI, em um primeiro momento, para facilitar, porém irei criar meu próprio componente dropdown ou toggle posteriormente.

Para instalar o Material-UI, basta executar os seguintes comandos:

$ npm install @material-ui/core

// ou utilizando yarn:

$ yarn add @material-ui/core

Chamei o dropdown de LanguageMenu, ficará da seguinte forma:

import React, { useState } from "react"
import Select from '@material-ui/core/Select'
import MenuItem from '@material-ui/core/MenuItem'
import { useTranslation } from "react-i18next"

import ptLogo from "../../../assets/pt.png"
import enLogo from "../../../assets/en.png"

const LanguageMenu = (props) => {
  const { t, i18n } = useTranslation()
  
  const [values, setValues] = useState({
    language: 'pt'
  });

  function handleChange(event) {
    i18n.changeLanguage(event.target.value)

    setValues(oldValues => ({
      ...oldValues,
      [event.target.name]: event.target.value,
    }));
  }

  return(
    <Select
      value={values.language}
      onChange={(e) => handleChange(e)}
      disableUnderline
      inputProps={{
        name: 'language'
      }}
    >
      <MenuItem value={'en'}><img src={enLogo} alt="EN" /></MenuItem>
      <MenuItem value={'pt'}><img src={ptLogo} alt="PT" /></MenuItem>
    </Select>
  )
}

export default LanguageMenu

Utilizamos o hook useTranslation() da lib react-i18next para "pegar" a instância do i18n criada, assim conseguimos lidar com a troca de idioma selecionado no dropdown pelo usuário, utilizando a função handleChang().

A seguir a tela onde apliquei internacionalização aqui no blog. Dá uma conferida lá!

internacionalizacao-about

Essa foi uma breve implementação de internacionalização (i18n) com Gatsby, ainda vou aplicar este processo em todos os textos do blog. Se você estiver interessado no código completo do blog, pode encontrar aqui. Qualquer comentário ou feedback fique à vontade. Até a próxima!

Comentários