Internacionalizando a sua Aplicação

Internacionalizando sua Aplicação

Já pensou em ter uma aplicação feita com ReactJS em diversos idiomas?

Melhor ainda, já pensou se a sua aplicação fosse capaz de identificar o idioma do navegador do usuário, de modo a traduzir a sua própria aplicação de maneira automática?

E digo mais... imagine você criando uma funcionalidade na qual o seu próprio usuário, também seja capaz de alterar o idioma da aplicação? 🤯

Mindblowing, não é mesmo?

E ao contrário do que você pode estar pensando, esse processo de internacionalização é tão simples, mas tão simples, que o que "pesa" mesmo é o processo de tradução, e não a implementação em sí 😉

Hoje, você irá aprender a internacionalizar a sua aplicação feita com ReactJS, usando a famosa biblioteca i18n.

Vamos nessa?

Criando sua aplicação de testes

Como de costume, a primeira coisa que a gente sempre faz, é criar um novo projeto do zero com o ReactJS. No meu caso, eu criei um novo projeto chamado de internacionalizacao.

npx create-react-app internacionalizacao

Após isso, não se esqueça de fazer aquela limpeza do código, e também aquela organizada nas pastas do src (organização por funcionalidade) 😁

Instalando as bibliotecas necessárias

Com a sua aplicação já instalada, e as pastas já organizadas, vamos instalar as 3 bibliotecas necessárias, para que o nosso projeto consiga ter suporta a internacionalização, são elas:

  • i18next
  • react-i18next
  • i18next-browser-languagedetector

Abaixo você encontrará informações detalhadas sobre cada uma delas:

i18next: biblioteca de internacionalização (i18n) para JavaScript que fornece suporte para tradução de aplicativos. É flexível e pode ser usada tanto no lado do cliente quanto no lado do servidor.

react-i18next: é uma extensão do i18next especificamente projetada para integrar a funcionalidade de i18next com aplicações feitas com React.

i18next-browser-languagedetector: é um plugin para i18next que detecta a língua preferida do usuário no navegador de maneira automática.

Dito isto, vamos seguir com o processo de instalação dessas libs 😉

Com o terminal (prompt de comando) aberto na pasta raiz do seu projeto (internacionalizacao), execute o seguinte comando abaixo:

npm install i18next react-i18next i18next-browser-languagedetector

O comando acima, irá instalar todas as bibliotecas necessárias para que o serviço de internacionalização,  seja aceita na nossa aplicação.

Criando uma página de testes

Antes de começarmos, que tal criarmos uma página simples na qual terá seu idioma alterado futuramente?

Dentro da pasta src > components, vamos criar 3 componentes diferentes, um chamado Header que será o cabeçalho, outro chamado Body que será o corpo da página, e por último o componente chamado Footer que representará o rodapé da nossa página.

Header > index.jsx:

import './header.css';

const Header = () => {
    return (
        <header>
            <h1>Portal da Micilini</h1>
            <p>Seja bem vindo 😀</p>
        </header>
    );
}

export default Header;

Header > header.css:

header{
    width:100%;
    height:80px;
    background-color: #131313;
    display: flex;
    justify-content: center;
    align-items: center;
}

header h1{
    font-size:24px;
    color:#D56E55;
    padding-right:25px;
}

header p{
    font-size: 18px;
    color:white;
}

Body > index.jsx:

import './body.css';

const Body = () => {
    return(
        <section>
            <h2>Novos Conteúdos Mensais</h2>
            <p><a href="https://micilini.com/conteudos/reactjs" target="__blank">🔥 Jornada ReactJS</a></p>
            <p><a href="https://micilini.com/conteudos/typescript">✨ Jornada Typescript</a></p>
            <p><a href="https://micilini.com/conteudos/javascript">💫 Jornada Javascript</a></p>
            <p><a href="https://micilini.com/">⚡ Outras Jornadas</a></p>
        </section>
    );
}

export default Body;

Body > body.css:

section{
    width: 100%;
    height: calc(100vh - 80px);
    display: block;
    background-color: antiquewhite;
    text-align: center;
    padding:20px 0px;
}

section h2{
    width: 100%;
    height: auto;
    margin-bottom:25px;
}

section p{
    font-size: 18px;
    color: #333333;
    margin-top:12px;
}

section p a{
    text-decoration: none;
}

Footer > index.jsx:

import './footer.css';

const Footer = () => {
    return(
        <footer>
            <p>© 2024 - Todos os direitos reservados</p>
        </footer>
    );
}

export default Footer;

Footer > footer.css:

footer{
    background-color: #333;
    color: white;
    text-align: center;
    padding: 10px;
    position: fixed;
    bottom: 0;
    width: 100%;
    z-index:2;
}

footer p{
    color:white;
}

Em seguida, vamos criar uma nova página dentro da pasta pages, chamada de Home.

Home > index.jsx:

import Header from '../../components/Header';
import Body from '../../components/Body';
import Footer from '../../components/Footer';

const Home = () => {
    return(
        <>
            <Header />
            <Body />
            <Footer />
        </>
    );
}

export default Home;

Por fim, não se esqueça de importar o componente Home dentro do App.js:

import Home from './pages/Home';

export default function App() {
    return (
        <>
            <Home />
        </>
    );
}

Rodando a sua aplicação (npm start), você terá um resultado similar a este:

Feito isso, vamos colocar a mão na massa, e começar a estruturar nossas pastas de idiomas 😋

Estruturando as pastas de idiomas

De acordo com tudo o que fizemos até agora, você terá uma estrutura de pastas bem similar a esta da ilustração abaixo:

Agora, dentro da pasta src do seu projeto, vamos criar uma pasta específica que vai conter toda a lógica de internacionalização que será usada pela nossa aplicação.

Para isso, você vai precisar criar uma nova pasta chamada de i18n:

Dentro dela, crie uma nova pasta chamada de locales, que será a pasta onde iremos armazenar todos os arquivos de traduções:

Feito isso, vamos criar o nosso primeiro arquivo, o arquivo de inicialização 🙂

Criando o arquivo de inicialização

Para que a sua aplicação possa reconhecer a linguagem do navegador do usuário, e localizar os arquivos de idiomas (traduções).

Você precisa criar um arquivo inicializador, que irá importar as bibliotecas necessarias, e fazer as devidas configurações iniciais (conectar tudo).

Portanto, dentro da pasta i18n que acabamos de criar, vamos criar um novo arquivo chamado de index.js, que representará nosso arquivo de configurações.

Dentro dele, insira a seguinte lógica:

i18n > index.js:

import i18n from 'i18next'
import LanguageDetector from 'i18next-browser-languagedetector'
import { initReactI18next } from 'react-i18next'

//Nossas traduções estão em arquivos separados dentro da pasta locales.
import translations from './locales'

// Configuração inicial do i18n
const i18nConfig = {
  resources: translations,  // Em 'resources' nós passamos nossas traduções (translations).
  fallbackLng: 'pt-BR',     // Em 'fallbackLng', nós informamos qual é o idioma padrão que será carregado, caso o browser não consiga detectar sozinho.
  defaultNS: 'translations' // O 'defaultNS' é o namespace padrão. 'translations' significa que nossas traduções estão no arquivo translations.json.
}

// Inicializa o i18n
i18n
  .use(LanguageDetector) // Usa o detector de idioma do seu browser
  .use(initReactI18next) // Usa o pacote do i18n específico para ReactJS
  .init(i18nConfig) // Usa nossas configurações Inciais (i18nConfig)

export default i18n;//Exporta o i18n para que possamos usá-lo em outros lugares do nosso app.

Acredito que os comentários existentes no código acima já são auto explicativos, não é mesmo? 😆

Mas para resumir, primeiro nós estamos importando as bibliotecas necessárias (i18next, i18next-browser-languagedetector, react-i18next), em seguida importando o arquivo responsável por agrupar as localizações que estão dentro da pasta locales (sim, alí dentro também haverá um outro arquivo chamado index.js).

De resto, estamos fazendo as configurações do i18n, e exportando esse módulo para que ele seja usado em outras partes da nossa aplicação.

Criando os arquivos de cada idioma

Neste projeto em questão, nós iremos dar suporte a somente dois idiomas principais, que são: Portugues do Brasil (pt-BR) e Inglês (en-US).

Neste caso, nós devemos criar dois arquivos dentro da pasta locales, um chamado de pt-br.js e outro chamado de en-us.js:

Cada um desses arquivos, nada mais são do que estruturas de chave e valor (objetos simples), que representam módulos exportados (por meio do comando export), veremos o funcionamento deles abaixo:

i18n > locales > pt-br.js:

export default {
    translations: { // Você precisa criar um objeto com o mesmo valor que usamos no 'defaultNS'.
      header: {
        h1: 'Portal da Micilini',
        welcome: 'Seja bem vindo 😀'
      },
      body: {
        h2: 'Novos Conteúdos Mensais',
        jornada: 'Jornada',
        outras: 'Outras Jornadas'
      },
      footer: {
        copyright: 'Todos os direitos reservados'
      }
    }
}

i18n > locales > en-us.js:

export default {
    translations: { // You need to create an object with the same value we use in 'defaultNS'.
      header: {
        h1: 'Micilini Portal',
        welcome: 'Welcome 😀'
      },
      body: {
        h2: 'New Monthly Content',
        jornada: 'Journey',
        outras: 'Other Journeys'
      },
      footer: {
        copyright: 'All rights reserved'
      }
    }
}

Note que criamos um objeto que sempre começa com a chave translations, isso é necessário porque dentro do index.js (existente na pasta raiz de i18n), definimos que o primeiro atributo (resources) da variável i18nConfig era translations.

Nesse caso, a biblioteca do i18n sempre vai procurar pela chave translations, o que implica em você precisar utilizar esse termo no seu objeto de idiomas.

Para explificar isso, você pode preceber que a mesma estrutura se repete tanto no arquivo pt-br.js quanto no en-us.js.

Se por um acaso você quiser mudar o nome da chave (translations) para qualquer outra coisa, não se esqueça de definir o mesmo valor dentro do atributo resourcers da variável i18nConfig.

Note também, que a única diferença entre os arquivos pt-br.js e en-us.js, é apenas o valor armazenado por cada uma das chaves aninhadas, que obviamente está em idiomas diferentes 😉

Além disso, é recomendável que você mantenha a mesma chave para ambos os arquivos:

Ou seja, se existe a chave header no idioma português, também deverá existir uma chave header no idioma inglês, e assim por diante, como mostrado na ilustração acima.

Além disso, faça com que a nomenclatura das suas chaves façam sentido, ok? Não saia escrevendo nomes aleatórios por aí 😂

Criando o nosso wrapper de locales

Se você prestar atenção, o index.js que existe na pasta raiz de i18n, está importando um outro index.js que existe dentro da pasta locales:

//Nossas traduções estão em arquivos separados dentro da pasta locales.
import translations from './locales'

Mas como você já sabe, esse arquivo ainda NÃO EXISTE! E precisamos criá-lo.

i18n > locales > index.js:

// Esse arquivo é responsável por exportar as traduções que serão utilizadas no app.

import ptBrTranslations from './pt-br';
import enUsTranslations from './en-us';

//Observação: Você precisa usar as abreviaturas corretas dentro das chaves do export abaixo. Para mais informações acesse este link: https://support.mozilla.org/pt-BR/kb/abreviacao-de-localizacao

export default {
  'pt': ptBrTranslations,
  'pt-BR': ptBrTranslations,
  'en-US': enUsTranslations,
  'en': enUsTranslations
}

Esse arquivo representa um wrapper, ou seja, um arquivo que agrupa todos os outros arquivos que representam os idiomas suportados pela aplicação.

Tudo o que o arquivo faz, é importar cada uma dos idiomas e exportá-los seguindo uma abreviatura que o nosso navegador possa entender.

Para mais informações sobre as abreviaturas que deverão ser utilizadas, acesse este link: https://support.mozilla.org/pt-BR/kb/abreviacao-de-localizacao

Feito isso, a lógica de internacionalização já está totalmente criada 🤩

Importando nosso arquivo de inicialização no index.js da nossa aplicação

Para que tudo funcione como o esperado, você precisa importar o seu arquivo de inicialização (i18n > index.js) para dentro do index.js da sua aplicação, da seguinte forma:

import React from 'react';
import ReactDOM from 'react-dom/client';
import './index.css';
import App from './App';

import './i18n';//Importa o arquivo de configuração do i18n (Suporte a Internacionalização)

const root = ReactDOM.createRoot(document.getElementById('root'));
root.render(
  <React.StrictMode>
    <App />
  </React.StrictMode>
);

Isso fará com que o i18n possa ser acessado globalmente pela sua aplicação, visto que ele está sendo importado pelo index.js.

Além disso, só pelo simples fato de realizarmos essa importação, ja é mais do que o suficiente para a biblioteca do i18n localizar os arquivos de idiomas, e configurar tudo! 

Show, feito isso, vamos partir para a tarefa mais trabalhosa: traduzir a aplicação!

Criando nosso TranslatorService

Antes de traduzirmos a nossa aplicação, é uma boa prática criarmos um serviço, que será representado por um componente, cujo objetivo é retornar um texto simples (string) de acordo com as chaves do arquivo de idiomas que queremos acessar.

Sendo assim, dentro da pasta src, crie um nova pasta chamada de services, e dentro dela, crie um novo arquivo chamado de TranslatorService.js:

import { useTranslation } from 'react-i18next';//Basta apenas importar o Hook que realiza as traduções.

const TranslatorService = ({ path }) => {
  const { t } = useTranslation(); // Chama a função responsável por realizar as traduções

  // Aqui, nós estamos retornando a função passando como parametro o caminho (path) de onde está localizado o texto que desejamos traduzir.
  return t(path);
}

export default TranslatorService;

Para usar este arquivo, basta seguir a lógica abaixo:

import TranslatorService from '../services/TranslatorService';

....

<TranslatorService path="header.h1" />

No exemplo acima, estamos selecionando o texto "Portal da Micilini" (ou "Micilini Portal", caso esteja em inglês) que está dentro da chave translations.header.h1 (lembrando que a chave translations não é necessário informar):

Dessa forma o TranslatorService, chama a biblioteca do i18n, que por sua vez retorna o texto (string) de acordo com o caminho do objeto.

Traduzindo a nossa aplicação

Enfim chegou o momento de traduzirmos a nossa aplicação. Sendo assim, eu precisei fazer pequenas modificações nos meus 3 componentes: Header, Body e Footer, vejamos:

Header > index.jsx:

import './header.css';

//Importa o serviço de tradução
import TranslatorService from '../../services/TranslatorService';

const Header = () => {
    return (
        <header>
            <h1><TranslatorService path="header.h1"/></h1>
            <p><TranslatorService path="header.welcome"/></p>
        </header>
    );
}

export default Header;

Body > index.jsx:

import './body.css';

//Importa o serviço de tradução
import TranslatorService from '../../services/TranslatorService';

const Body = () => {
    return(
        <section>
            <h2><TranslatorService path="body.h2"/></h2>
            <p><a href="https://micilini.com/conteudos/reactjs" target="__blank">🔥 <TranslatorService path="body.jornada"/> ReactJS</a></p>
            <p><a href="https://micilini.com/conteudos/typescript">✨ <TranslatorService path="body.jornada"/> Typescript</a></p>
            <p><a href="https://micilini.com/conteudos/javascript">💫 <TranslatorService path="body.jornada"/> Javascript</a></p>
            <p><a href="https://micilini.com/">⚡ <TranslatorService path="body.outras"/></a></p>
        </section>
    );
}

export default Body;

Footer > index.jsx:

import './footer.css';

//Importa o serviço de tradução
import TranslatorService from '../../services/TranslatorService';

const Footer = () => {
    return(
        <footer>
            <p>© 2024 - <TranslatorService path="footer.copyright"/></p>
        </footer>
    );
}

export default Footer;

Note que em cada arquivo, utilizamos a estratégia do TranslatorService, ou seja, importando-o e usando-o quando necessário 🙂

No final, não teremos nada de muito diferente na nossa aplicação, pois ela ainda está com a mesma cara:

Mas a partir do momento que você mudar a linguagem do seu navegador, verá que o idioma também vai ser mudado 😉

Aplicando uma tradução direta

Haverá momentos em que você não conseguirá utilizar o componente/serviço TranslatorService, como é o caso de tentarmos utilizá-lo dentro de um atributo:

<input
  placeholder={<TranslatorService path="input.placeholder" />}
  value={valor}
  onChange={mudaValor}
/>

Em casos como esse, basta que você importe o useTranslation da biblioteca react-i18next, instâncie ele, e o use diretamente no atributo, por exemplo:

import { useTranslation } from 'react-i18next';//Basta apenas importar o Hook que realiza as traduções.

....

const { t } = useTranslation(); // Chama a função responsável por realizar as traduções

....

<input
  placeholder={t('input.placeholder')}
  value={valor}
  onChange={mudaValor}
/>

Recuperando o idioma selecionado

Também haverão momentos em que a sua aplicação irá precisar identificar o idioma que está sendo utilizado no momento.

Para obter este tipo de informação, você precisa importar o useTranslation do react-i18next, instânciá-lo de modo a obter o objeto i18n, para posteriormente acessar a chave language, observe:

import React from 'react';
import { useTranslation } from 'react-i18next';

function MyComponent() {
  const { i18n } = useTranslation();

  // Recupera o idioma atual
  const currentLanguage = i18n.language;

  return (
    <div>
      <p>Idioma atual: {currentLanguage}</p>
    </div>
  );
}

export default MyComponent;

Fácil, não?

Lembrando que o retorno segue o mesmo esquema de abreviaturas, acesse este link para saber mais: https://support.mozilla.org/pt-BR/kb/abreviacao-de-localizacao

Mudando a linguagem da sua aplicação

Já para mudar o idioma da sua aplicação, independentemente se o seu navegador está em inglês ou portugues. Você pode fazer isso da seguinte forma:

import React from 'react';
import { useTranslation } from 'react-i18next';

const LanguageSelector = () => {
  const { i18n } = useTranslation();

  const handleChange = (event) => {
    const selectedLanguage = event.target.value;
    i18n.changeLanguage(selectedLanguage);
  };

  return (
    <select onChange={handleChange} defaultValue={i18n.language}>
      <option value="pt-BR">Português do Brasil (pt-BR)</option>
      <option value="en-US">Inglês (en-US)</option>
    </select>
  );
};

export default LanguageSelector;

Observe que apesar de termos criado um componente chamado de LanguageSelector, para representar a mudança de idioma, o pulo do fato está no uso do objeto i18n, mais especificamente na chamada da função changeLanguage.

Observação: Você não precisa salvar as configurações do seu idioma selecionado em alguma base de dados, pois o próprio i18n, faz o uso do armazenamento do navegador para deixar isso salvo.

Mudando a linguagem do navegador (Google Chrome)

Agora para testarmos tudo o que fizemos, que tal alterarmos o idioma do nosso navegador para que possamos ver como ficou o resultado em inglês?

No meu caso, estou usando o navegador Google Chrome, e para mudar o idioma dele, siga os passos abaixo:

1° Passo) Abra uma nova aba do seu navegador e acesse a seguinte URL: chrome://settings/

2° Passo) No menu de definições a esquerda, selecione a opção idiomas:

3° Passo) Um card com seus idiomas preferidos irá se abrir, localize o inglês, clique no ícone dos 3 pontinhos na vertical, e selecione a opção "Apresentar o Google Chrome neste idioma".

4° Passo) Após isso, você vai precisar reiniciar o seu navegador, para isso, basta clicar no botão "Reiniciar".

E pronto, da próxima vez que você abrir a sua aplicação em ReactJS, verá que está tudo no idioma inglês:

Observação: Caso a sua aplicação ainda estiver em português, talvez esteja relacionado à problemas de cache do navegador, ou da sua aplicação. Qualquer coisa, basta abrir a sua aplicação em uma aba anônima para testar.

Arquivos da lição

Os arquivos dessa lição podem ser encontrados neste repositório do GitHub.

Conclusão

Nesta lição você aprendeu a internacionalizar a sua aplicação feita com ReactJS.

Até a próxima! 😉

Criadores de Conteúdo

Foto do William Lima
William Lima
Fundador da Micilini

Inventor nato, escreve conteudos de programação para o portal da micilini.

Torne-se um MIC 🤖

Mais de 100 mic's já estão conectados na plataforma.