Skip to content
This repository was archived by the owner on Jun 3, 2026. It is now read-only.
This repository was archived by the owner on Jun 3, 2026. It is now read-only.

Adicionar Internacionalização (i18n) #105

Description

@edvaldoszy

Contexto

O hcf-painel é uma SPA Vite + React 18 que utiliza Ant Design e React Router v4. Toda a interface está em Português Brasileiro hardcoded em aproximadamente 182 arquivos-fonte (uma mistura de componentes de classe .jsx legados e componentes funcionais .tsx mais novos). Não existe nenhuma infraestrutura de i18n atualmente.

Objetivo: Suportar pt-BR, es e en com URLs prefixadas por locale (ex: /pt/tombos, /es/tombos, /en/tombos).


Solução Proposta

Biblioteca

Utilizar react-i18next + i18next. É o padrão da indústria para SPAs React, suporta componentes de classe (HOC withTranslation), componentes funcionais (hook useTranslation), carregamento lazy de namespaces e tem suporte de primeira classe ao TypeScript.

Pacotes adicionais necessários:

  • i18next-browser-languagedetector — detecta o locale a partir da URL, localStorage ou configuração do navegador
  • i18next-http-backend — carrega os arquivos JSON de tradução sob demanda (evita empacotar todos os locales no bundle)

Estrutura dos Arquivos de Tradução

public/
  locales/
    pt/
      common.json
      navigation.json
      tombos.json
      taxonomia.json
      ...
    en/
      common.json
      navigation.json
      tombos.json
      ...
    es/
      common.json
      navigation.json
      tombos.json
      ...

Cada namespace corresponde a uma funcionalidade/tela. Exemplo de common.json:

{
  "loading": "Carregando...",
  "save": "Salvar",
  "cancel": "Cancelar",
  "error": {
    "generic": "Ocorreu um erro, tente novamente."
  }
}

Inicialização do i18n (src/i18n.ts)

Inicializar o i18next com o LanguageDetector configurado para ler o locale a partir do caminho da URL primeiro, com fallback para localStorage e depois para as configurações do navegador.

// src/i18n.ts (ilustrativo, não completo)
i18n
  .use(HttpBackend)
  .use(LanguageDetector)
  .use(initReactI18next)
  .init({
    fallbackLng: 'pt',
    supportedLngs: ['pt', 'en', 'es'],
    detection: {
      order: ['path', 'localStorage', 'navigator'],
    },
    backend: {
      loadPath: '/locales/{{lng}}/{{ns}}.json',
    },
    ns: ['common', 'navigation', 'tombos'],
    defaultNS: 'common',
  });

Roteamento Baseado em URL

Envolver todas as rotas existentes dentro de um prefixo /:locale. O segmento de locale na URL define o idioma ativo. Um redirecionamento de / para /<locale-detectado> deve ser adicionado.

Antes: /tombos, /taxonomia/familias
Depois: /pt/tombos, /en/tombos, /es/taxonomia/familias

Um componente wrapper LocaleRoute deve:

  1. Ler :locale da URL
  2. Validar contra ['pt', 'en', 'es'] — redirecionar para /pt/... se inválido
  3. Chamar i18n.changeLanguage(locale) e definir o locale do ConfigProvider do Ant Design correspondente
// LocaleRoute ilustrativo
function LocaleRoute() {
  const { locale } = useParams();
  const { i18n } = useTranslation();

  useEffect(() => {
    if (SUPPORTED_LOCALES.includes(locale)) {
      i18n.changeLanguage(locale);
    } else {
      navigate(`/pt${window.location.pathname}`);
    }
  }, [locale]);

  return <Outlet />; // ou <Switch> no RR v4
}

Locale do Ant Design

Conectar o locale ao ConfigProvider existente em src/index.tsx de forma dinâmica com base no idioma ativo:

const antdLocaleMap = { pt: ptBR, en: enUS, es: esES };

<ConfigProvider locale={antdLocaleMap[i18n.language] ?? ptBR}>

Extração de Strings nos Componentes

Componentes funcionais (padrão mais novo, ex: feature de login):

const { t } = useTranslation('common');
// Antes: <span>Carregando...</span>
// Depois: <span>{t('loading')}</span>

Componentes de classe (padrão legado, maioria das telas):

export default withTranslation('tombos')(ListaTombosScreen);

// Dentro do render():
const { t } = this.props;
// Antes: <Button>Salvar</Button>
// Depois: <Button>{t('save')}</Button>

Componente de Troca de Idioma

Um novo componente LanguageSwitcher no cabeçalho do layout principal que substitui o segmento de locale na URL atual ao trocar de idioma, sem recarregar a página.


Arquivos Afetados (não exaustivo)

  • src/index.tsx — adicionar I18nextProvider, atualizar ConfigProvider com locale dinâmico
  • src/App.tsx — adicionar prefixo /:locale em todas as rotas, adicionar wrapper LocaleRoute
  • src/layouts/MainLayout.jsx — extrair todos os rótulos de navegação, adicionar LanguageSwitcher
  • src/features/login/LoginLayout.tsx — extrair strings do formulário de login
  • src/pages/ — todas as ~50 telas (maior esforço; deve ser feito em lotes por namespace)
  • src/components/SimpleTableComponent.jsx — remover strings PT hardcoded; substituir por i18next + locale do antd
  • Todas as 7 telas de relatório com imports de dateLocale por tela — centralizar via ConfigProvider

Estratégia de Migração (Recomendada)

  1. Fase 1 — Infraestrutura: Instalar bibliotecas, criar src/i18n.ts, atualizar ConfigProvider, adicionar wrapper de roteamento por locale, criar arquivos de tradução vazios para os 3 idiomas
  2. Fase 2 — Layout e componentes compartilhados: MainLayout, SimpleTableComponent, mensagens de erro comuns, componente LanguageSwitcher
  3. Fase 3 — Telas de funcionalidades (em lotes): Login, tombos, taxonomia, locais, remessas, usuários — um namespace por PR
  4. Fase 4 — Tradução do conteúdo: Entregar os arquivos JSON de en e es para tradução humana ou automática; pt é preenchido primeiro como fonte primária de verdade

Critérios de Aceite

  • Navegar para /en/tombos renderiza a tela de tombos em inglês; /es/tombos em espanhol
  • Navegar para / redireciona para /<locale-preferido-do-navegador>/... (fallback: /pt/)
  • As strings internas do Ant Design (date picker, paginação de tabela, modais) correspondem ao locale ativo
  • O LanguageSwitcher atualiza a URL e re-renderiza as strings sem recarregar a página
  • Todas as chaves de tradução pt-BR estão presentes; os arquivos en e es estão estruturados (podem ter strings placeholder inicialmente)
  • Nenhuma string em Português permanece hardcoded fora dos arquivos JSON de tradução

Activity

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

Labels

No labels
No labels

Type

No type

Projects

  • Status
    done

Milestone

No milestone

Relationships

None yet

Development

No branches or pull requests

Issue actions