Fala dev doido!
Hoje eu vou mostrar que nunca foi tão fácil traduzir um projeto INTEIRO em React que nem é hoje em dia. Mas antes vc precisa saber pra que isso seria importante. Confira o vídeo COMPLETO da aula AQUI
Quando a galera começa a programar, é comum que os textos e mensagens do código sejam em português (pt-BR). Traduzir o projeto para outros idiomas nunca é prioridade, sendo considerado complexo ou desnecessário.
Então porque seria relevante?
Depende da sua realidade. Aqui estão algumas razões pelas quais você deveria considerar esse processo:
A empresa precisa
Pode ser que a empresa onde vc trampa ou algum SaaS que vc tenha comece a operar em outro país e tenha essa necessidade. Um produto que tem essa funcionalidade possui um diferencial ENORME.
Candidatura em vaga internacional
Se você está aplicando para vagas internacionais, ter um portfólio com projetos já internacionalizados pode te dar um destaque fulminante. Mostra que você tá preparado para trampar em projeto global e não é preguiçoso como a maioria.
Aprendizado nunca é demais
Internacionalização não é apenas uma funcionalidade, mas também um aprendizado importante. É uma arma a mais que vc inclui no seu arsenal de skills e ferramentas.
Como era feito antigamente?
Tradução de projeto já é um problema antigo. A galera fazia aquele select no HTML com a bandeirinha do país pra pessoa selecionar e ia enchendo de if no código pra saber qual texto ia ser exibido.
Era muito negligenciado. Sites eram feitos em um único idioma, e traduções eram adicionadas de forma cagada. Se fosse no backend era pior ainda o negócio.
Com a globalização da internet, a demanda por software multilingue cresceu, trazendo ferramentas específicas para i18n. Soluções como GNU Gettext surgiram no backend, seguidas por libs como i18next e react-intl para o frontend. Eaí entra a dúvida...
i18next vs. react-intl: Qual Escolher?
-
i18next: Essa surgiu em 2011, era um pacote npm que funcionava tanto pra Node.js quanto pra SPA no client side. A comunidade foi adotando e finalmente fizeram em 2015 a versão pra React na lib react-i18next. Portanto como pontos positivos e negativos temos:
- Prós: Flexibilidade, tempo de estrada (desde 2011), vasto ecossistema (learn once translate everywhere) e fallback automático.
- Contras: Curva de aprendizado. Tem muita doc pra ler, mas nem tudo o que ta lá vc precisa.
-
react-intl: Parte do projeto FormatJS, segue padrões da API Internacional do JavaScript, garantindo compatibilidade com navegadores modernos.
- Prós: Alinhado aos padrões ECMAScript, simplicidade na integração.
- Contras: Menos flexível e com menos suporte a plugins.
E qual iremos usar?
i18next meus amigos! Recomendo sempre a leitura das docs pra iniciar, mas vamos pro guia do Doido!
Internacionalizando uma Aplicação React com i18next
- Instalação:
npm install i18next i18next-chained-backend i18next-http-backend i18next-resources-to-backend react-i18next next-i18next
-
Configuração: Crie um
i18n.js
para configurar o i18next.
import i18n from 'i18next';
import { initReactI18next } from 'react-i18next';
import Backend from 'i18next-http-backend';
import LanguageDetector from 'i18next-browser-languagedetector';
i18n
.use(Backend)
.use(LanguageDetector)
.use(initReactI18next)
.init({ fallbackLng: 'en', interpolation: { escapeValue: false } });
export default i18n;
-
Traduções: Crie arquivos de tradução em
locales/en/translation.json
elocales/pt/translation.json
.
{
"welcome": "Welcome to our application!",
"login": "Login"
}
-
Uso das Traduções: Use o hook
useTranslation
no React.
import React from 'react';
import { useTranslation } from 'react-i18next';
function App() {
const { t } = useTranslation();
return (
<div>
<h1>{t('welcome')}</h1>
<button>{t('login')}</button>
</div>
);
}
export default App;
- Mudança de Idioma: Permita que os usuários mudem o idioma.
import React from 'react';
import { useTranslation } from 'react-i18next';
function LanguageSwitcher() {
const { i18n } = useTranslation();
const changeLanguage = (lng) => i18n.changeLanguage(lng);
return (
<div>
<button onClick={() => changeLanguage('en')}>English</button>
<button onClick={() => changeLanguage('pt')}>Português</button>
</div>
);
}
export default LanguageSwitcher;
É só isso?
Claro que não, eu vou mostrar agora o que eu fiz no projeto do CrazyStack. Primeiro eu fiz uma config diferente no Nextjs pegando de um JSON estático que eu defini na pasta public do próprio projeto! Dá uma olhada:
import i18next from "i18next";
import ChainedBackend from "i18next-chained-backend";
import HttpBackend from "i18next-http-backend";
import resourcesToBackend from "i18next-resources-to-backend";
import { initReactI18next } from "react-i18next";
import { defaultTexts } from "./defaultTexts";
i18next
.use(ChainedBackend)
.use(initReactI18next)
.init({
lng: "pt-br",
fallbackLng: "pt-br",
interpolation: {
escapeValue: false,
},
compatibilityJSON: "v3",
react: {
//wait: true,//usar no react native
useSuspense: false,
},
backend: {
backends: [HttpBackend, resourcesToBackend(defaultTexts)],
backendOptions: [
{
loadPath: `${process.env.NEXT_PUBLIC_URL}/{{lng}}/{{ns}}.json`,
},
],
},
});
Depois fiz um context API pra salvar o idioma e acessar no projeto todo. Começando pelos imports
2. Importações
import { useTranslation } from "react-i18next";
import { createContext, useState, useContext } from "react";
-
useTranslation
: Hook doreact-i18next
para acessar funcionalidades de tradução. Isso aqui vc vai usar em praticamente todo componente JSX que tiver no projeto. -
createContext
,useState
,useContext
: Funções do React para criar e consumir contextos, além de gerenciar estado.
3. Criação do Contexto
const I18NContext = createContext({} as any);
Um context é criado para armazenar e fornecer os dados (como o idioma atual) através da DOM.
4. Verificação do Ambiente
export const isBrowser = typeof window !== "undefined";
Essa linha verifica se o código está sendo executado no navegador (em vez de no servidor), essencial para manipular recursos específicos do cliente, como localStorage
.
5. Componente I18nProvider
export const I18nProvider = ({ children }: any) => {
const { i18n } = useTranslation() || {};
const [currentLanguage, setCurrentLanguage] = useState(
formatLanguageFromi18N(i18n?.language)
);
const changeLanguage = (language) => {
setCurrentLanguage(language);
i18n?.changeLanguage?.(formatLanguageFromSelect(language));
localStorage.setItem("language", formatLanguageFromSelect(language));
};
return (
<I18NContext.Provider value={{ changeLanguage, currentLanguage, setCurrentLanguage }}>
{children}
</I18NContext.Provider>
);
};
Este componente é um provider que envolve a árvore de componentes React e fornece o estado atual do idioma e a função para alterá-lo.
-
useTranslation
: Recupera o objetoi18n
da bibliotecareact-i18next
, que contém informações sobre o idioma atual. -
currentLanguage
: Estado que armazena o idioma atual, inicializado com base no idioma detectado peloi18n
. -
changeLanguage
: Função para alterar o idioma, que também salva a escolha nolocalStorage
para persistência entre recargas da página.
6. Hook useI18n
export const useI18n = () => {
if (!isBrowser) {
return {
currentLanguage: "pt-br",
setCurrentLanguage: () => {},
changeLanguage: () => {},
};
}
return useContext(I18NContext);
};
Este hook facilita o acesso ao contexto de internacionalização em qualquer componente.
- Verifica se está no navegador (
isBrowser
). Se não estiver, retorna valores padrão para evitar erros no server side. - Se estiver no navegador, consome e retorna o contexto
I18NContext
.
7. Mapas de Conversão
const countryToLanguage = {
BR: "pt-br",
US: "en",
};
const languageToCountry = {
"pt-br": "BR",
en: "US",
};
Esses objetos mapeiam códigos de países para códigos de idiomas e vice-versa, facilitando a formatação dos códigos de idioma entre diferentes convenções.
8. Funções de Formatação
export const formatLanguageFromi18N = (language) => languageToCountry[language];
export const formatLanguageFromSelect = (language) => countryToLanguage[language];
Essas funções formatam os códigos de idioma conforme necessário. formatLanguageFromi18N
converte o código de idioma para o código do país, enquanto formatLanguageFromSelect
faz a conversão inversa.
Código completo
"use client";
import { useTranslation } from "react-i18next";
import { createContext, useState, useContext } from "react";
const I18NContext = createContext({} as any);
export const isBrowser = typeof window !== "undefined";
export const I18nProvider = ({ children }: any) => {
const { i18n } = useTranslation() || {};
const [currentLanguage, setCurrentLanguage] = useState(
formatLanguageFromi18N(i18n?.language)
);
const changeLanguage = (language) => {
setCurrentLanguage(language);
i18n?.changeLanguage?.(formatLanguageFromSelect(language));
localStorage.setItem("language", formatLanguageFromSelect(language));
};
return (
<I18NContext.Provider value={{ changeLanguage, currentLanguage, setCurrentLanguage }}>
{children}
</I18NContext.Provider>
);
};
export const useI18n = () => {
if (!isBrowser) {
return {
currentLanguage: "pt-br",
setCurrentLanguage: () => {},
changeLanguage: () => {},
};
}
return useContext(I18NContext);
};
const countryToLanguage = {
BR: "pt-br",
US: "en",
};
const languageToCountry = {
"pt-br": "BR",
en: "US",
};
export const formatLanguageFromi18N = (language) => languageToCountry[language];
export const formatLanguageFromSelect = (language) => countryToLanguage[language];
Depois eu mexi na Navbar
No código eu tenho um select de idioma utilizando um dropdown de países. Olha só:
"use client";
//@ts-nocheck
import { Header, Flex, Logo, Profile, NotificationsNav, SearchBar } from "@/shared/ui";
import { useBreakpointValue, Icon, IconButton, useMediaQuery } from "@chakra-ui/react";
import { RiMenuLine } from "react-icons/ri";
import { useAuth, useSidebarDrawer } from "@/shared/libs";
import { useEffect, useState } from "react";
import { CountryDropdown } from "react-country-region-selector";
import { theme } from "@/application/theme";
import { formatLanguageFromi18N, useI18n } from "@/application/providers/i18nProvider";
import { useTranslation } from "react-i18next";
export const NavBar = ({ showLogo = true }) => {
const { isAuthenticated } = useAuth() || {};
const { i18n } = useTranslation();
const { changeLanguage, setCurrentLanguage } = useI18n() || {};
const { onOpen = () => {}, onClose } = useSidebarDrawer() || {};
const isDesktopVersion = useBreakpointValue({ base: false, lg: true });
const [country, setCountry] = useState(formatLanguageFromi18N(i18n?.language));
useEffect(() => {
return () => {
onClose?.();
};
}, []);
const Dropdown = CountryDropdown as any;
useEffect(() => {
const language = localStorage.getItem("language");
if (language) {
setCountry(formatLanguageFromi18N(language));
setCurrentLanguage(language);
i18n?.changeLanguage?.(language);
}
}, []);
return (
<Header>
<Flex alignItems={"center"} w={"100%"}>
{isAuthenticated && !isDesktopVersion && (
<IconButton
aria-label="Open sidebar"
fontSize="24"
icon={<Icon as={RiMenuLine} />}
variant="unstyled"
onClick={onOpen}
mr="1"
mt={2}
/>
)}
<Logo marginBottom={0} />
{/* {isLargerThan560 && (
<SearchBar placeholder="Pesquise por nome..." name="search" width="auto" />
)} */}
{isAuthenticated && (
<Flex align="center" ml="auto">
{/* <NotificationsNav /> */}
<Dropdown
value={country}
onChange={(val) => {
setCountry(val);
changeLanguage(val);
}}
labelType="short"
valueType="short"
showDefaultOption
defaultOptionLabel="Selecione o idioma"
whitelist={["US", "BR"]}
style={{
backgroundColor: theme.colors.secondary[400],
padding: 10,
width: 60,
marginRight: 15,
borderRadius: 8,
}}
/>
<Profile showProfileData={isDesktopVersion} />
</Flex>
)}
</Flex>
</Header>
);
};
Importações e Setup Inicial:
-
useAuth
: Verifica se o usuário está autenticado. -
useBreakpointValue
: Determina se a versão desktop deve ser exibida com base no tamanho da tela. -
useState
: Define o estado inicial do país/língua (country
) usando a funçãoformatLanguageFromi18N
para formatar a língua atual doi18n
. -
useEffect
: Primeiro efeito limpa o sidebar ao desmontar o componente (onClose
). O segundo efeito verifica se o idioma está salvo nolocalStorage
e, caso esteja, atualiza o estadocountry
e muda o idioma na aplicação.
Dropdown de Idiomas:
- O dropdown é implementado usando o componente
CountryDropdown
da bibliotecareact-country-region-selector
, que é customizado para servir como um seletor de idioma. -
value={country}
: O valor selecionado no dropdown é controlado pelo estadocountry
. -
onChange={(val) => { ... }}
: Quando o valor do dropdown é alterado, o estadocountry
é atualizado, e a funçãochangeLanguage
é chamada para alterar o idioma da aplicação. -
whitelist={["US", "BR"]}
: Restringe as opções do dropdown a "US" (inglês) e "BR" (português). -
style={...}
: Estilização inline personalizada para o dropdown, utilizando cores e espaçamentos do tematheme
.
-
Comportamento do Seletor de Idioma:
- O dropdown permite que o usuário selecione o idioma preferido, e essa seleção é persistida no
localStorage
. - Ao mudar o idioma, o dropdown reflete essa mudança, e a aplicação é atualizada para usar o novo idioma selecionado. Para incluir o trecho de código que você forneceu na imagem no seu artigo, você pode seguir este formato:
- O dropdown permite que o usuário selecione o idioma preferido, e essa seleção é persistida no
E como mudar os textos?
De componente em componente eu fui fazendo o mesmo procedimento. O código abaixo mostra como substituir o texto estático por uma tradução dinâmica baseada na chave de localização:
import { Divider } from "@chakra-ui/react";
import { IoExitOutline } from "react-icons/io5";
import { useRouter } from "next/navigation";
import { useTranslation } from "react-i18next"; // Importando o hook useTranslation
type ProfileProps = {
showProfileData?: boolean;
};
export const Profile = ({ showProfileData }: ProfileProps) => {
const { t } = useTranslation(["PAGES"]); // Obtendo a função t para tradução
const { user, logout } = useAuth() || {};
const router = useRouter();
const { showUserMenu, setShowUserMenu } = useProfile();
return (
<Box>
{/* Outras partes do componente */}
<Flex>
<IoExitOutline />
<Text fontSize="sm">
{t("PAGES:HOME_PAGE.logout", { defaultValue: "Sair" })} // Chave de tradução com valor padrão
</Text>
</Flex>
</Box>
);
};
Neste exemplo, o hook useTranslation
é utilizado para carregar a chave de tradução PAGES:HOME_PAGE.logout
. Se a chave não for encontrada, o texto padrão "Sair"
será exibido.
Conclusão
A ideia pode ser aplicada em qualquer componente de texto estático. Basta usar a hook useTranslation.
Internacionalizar sua aplicação pode abrir portas para mercados globais, destacar seu portfólio e aprimorar suas habilidades. Escolher entre i18next e react-intl depende das necessidades específicas do seu projeto, mas ambos são excelentes opções para quem deseja começar.
Sugestão de cursos
Em 2022 eu criei o bootcamp CrazyStack. Nele, eu mostro 2 aplicações completas de um sistema de agendamentos online de serviços aplicando conceitos avançados como Design Patterns, Clean Architecture, Feature Sliced Design, SOLID, DDD, além de Testes unitários, de integração e E2E.
Na primeira aplicação, você aprenderá a construir uma API REST no ecossistema Node.js. Serão criados casos de uso envolvendo regras de negócio complexas como listagem de horários disponíveis, geração de pedidos a partir de agendamentos efetivados, sistema de fidelidade, comissões, pagamentos, avaliações dos clientes e muito mais. Tudo feito em TypeScript e utilizando o banco de dados não relacional MongoDB.
Na segunda aplicação, você aprenderá a construir no ecossistema React.js um painel de administração para visualizar gráficos e manipular os registros. Tudo feito com TypeScript e usando o framework Next.js. Além disso, será utilizada a biblioteca de componentes visuais Chakra UI aplicando o conceito de Atomic Design nos componentes criados. Para saber mais, acesse crazystack.com.br.