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

Metadata

Metadata

Assignees

Labels

No labels
No labels

Type

No type
No fields configured for issues without a type.

Projects

Status
to do

Milestone

No milestone

Relationships

None yet

Development

No branches or pull requests

Issue actions