🇦🇹Internacionalização Própria

Sistema de Internacionalização (i18n)

Visão Geral

O sistema de internacionalização é baseado em arquivos JSON e uma classe centralizada AppI18n que gerencia as traduções. O sistema suporta textos simples, textos com parâmetros e pluralização.

Arquitetura do Sistema

Estrutura de Arquivos

assets/
  i18n/
    pt_BR.json          # Arquivo de traduções em português
    en_US.json          # Arquivo de traduções em inglês
    es_ES.json          # Arquivo de traduções em espanhol
lib/
  core/
    i18n/
      app_i18n.dart     # Classe principal de gerenciamento
      enums/
        language.dart   # Enum com idiomas suportados
tools/
  i18n_validator/       # Ferramenta de validação

Exemplo de organização do Arquivo JSON

Organização Hierárquica

O exemplo abaixo mostra a estrutura do arquivo pt_BR.json.

{
  "appName": "Flutter MVVM Leap",
  "app": {
    "common": {
      "hello": "Olá, {}!",
      "errors": {
        "generic": "Houve um problema",
        "connection": {
          "title": "Falha na conexão",
          "description": "Por favor, verifique sua conexão à internet."
        }
      },
      "activeFilters": {
        "one": "{} filtro ativo",
        "many": "{} filtros ativos"
      }
    },
    "features": {
      "example": {
        "title": "Título",
        "form": {
          "exampleLabel": "Exemplo",
          "exampleLabel2": "Exemplo 2",
          "exampleHint": "Dica do exemplo",
          "exampleHint2": "Dica do exemplo 2",
          "exampleButton": "Botão"
        }
      },
      "splash": {
        "title": "Splash"
      }
    },
    "accessibility": {
      "togglePassword": "Mostrar/Esconder senha"
    }
  }
}

Importante: Todos os arquivos de idioma (pt_BR.json, en_US.json, es_ES.json) devem ter as mesmas chaves na mesma estrutura. Apenas os valores devem ser traduzidos. Por exemplo:

  • pt_BR.json: "hello": "Olá, {}!"

  • en_US.json: "hello": "Hello, {}!"

  • es_ES.json: "hello": "¡Hola, {}!"

Convenções de Nomenclatura

  1. Caminhos: Use camelCase para chaves

  2. Hierarquia: Organize por módulo/funcionalidade

    • app.common: Textos compartilhados

    • app.features.{feature}: Textos específicos de funcionalidade

    • accessibility: Textos para acessibilidade

  3. Plurais: Use objetos com chaves one e many

  4. Parameters: Use {} apenas quando você pretende passar parâmetros para a string. Se colocar {} no JSON mas não passar o parâmetro correspondente, o placeholder {} permanecerá visível no texto final

Classe AppI18n

A classe AppI18n é responsável por:

  • Carregar arquivos de tradução

  • Fornecer métodos para buscar textos

  • Gerenciar mudança de idioma

  • Notificar mudanças via ChangeNotifier

Método init()

Inicializa o sistema de tradução:

Future<void> init() async {
  final language = await _getCurrentLanguage();
  final path = join('assets', 'i18n', '${language.locale}.json');
  final fileContent = await _rootBundle.loadString(path);
  translations = jsonDecode(fileContent);
  notifyListeners();
}

Processo:

  1. Detecta idioma atual do dispositivo ou usa preferência salva

  2. Carrega arquivo JSON correspondente

  3. Notifica listeners sobre mudança

Inicialização do Sistema

No AppWidget

class AppWidget extends StatefulWidget {
  @override
  State<AppWidget> createState() => _AppWidgetState();
}

class _AppWidgetState extends State<AppWidget> {
  final i18n = injector.get<AppI18n>();
  
  @override
  Widget build(BuildContext context) {
    return ChangeNotifierProvider.value(
      value: i18n,
      child: MaterialApp.router(
        supportedLocales: const [
          Locale('pt', 'BR'),
          Locale('en', 'US'),
          Locale('es', 'ES'),
        ],
        localizationsDelegates: const [
          GlobalMaterialLocalizations.delegate,
          GlobalCupertinoLocalizations.delegate,
          GlobalWidgetsLocalizations.delegate,
        ],
        // ... resto da configuração
      ),
    );
  }
}

Na Injeção de Dependência

// Em app_dependencies.dart
injector.registerLazySingleton<AppI18n>(
  () => AppI18n(
    injector.get(),
    injector.get(),
    injector.get(),
    injector.get(),
  ),
);

// Inicializar após registro
await injector.get<AppI18n>().init();

Métodos Principais

1. Método get(String path, {List<String>? params})

Busca uma tradução simples no arquivo JSON usando um caminho pontilhado.

Uso:

// No arquivo JSON: "app.common.hello": "Olá, {}!"
final text = i18n.get('app.common.hello', params: ['João']);
// Resultado: "Olá, João!"

2. Método getPlural(String path, int count, {List<String>? params})

Busca traduções com pluralização baseada na quantidade.

Uso:

// Para 1 item
final text1 = i18n.getPlural('app.common.activeFilters', 1, params: ['1']);
// Resultado: "1 filtro ativo"

// Para múltiplos itens
final text2 = i18n.getPlural('app.common.activeFilters', 5, params: ['5']);
// Resultado: "5 filtros ativos"

Regras de Pluralização:

  • count == 1: usa a chave "one"

  • count != 1: usa a chave "many"

Integração com Widgets

Widgets Base

O sistema usa widgets base que automaticamente injetam AppI18n via Provider:

AppStatelessWidget

abstract class AppStatelessWidget extends StatelessWidget {
  Widget buildWidget(BuildContext context, AppI18n i18n, AppTheme theme);
  
  @override
  Widget build(BuildContext context) {
    final i18n = context.watch<AppI18n>();
    final theme = context.watch<AppTheme>();
    return buildWidget(context, i18n, theme);
  }
}

AppStatefulWidget

abstract class AppStatefulWidget<T extends StatefulWidget> extends State<T> {
  Widget buildWidget(BuildContext context, AppI18n i18n, AppTheme theme);
  
  @override
  Widget build(BuildContext context) {
    final i18n = context.watch<AppI18n>();
    final theme = context.watch<AppTheme>();
    return buildWidget(context, i18n, theme);
  }
}

Exemplo de Uso em Widget

class MyWidget extends AppStatelessWidget {
  @override
  Widget buildWidget(BuildContext context, AppI18n i18n, AppTheme theme) {
    return Scaffold(
      appBar: AppBar(
        title: Text(i18n.get('features.profile.title')),
      ),
      body: Column(
        children: [
          Text(i18n.get('app.common.hello', params: ['Usuário'])),
          Text(i18n.getPlural('app.common.activeFilters', 3, params: ['3'])),
        ],
      ),
    );
  }
}

Boas Práticas

1. Organização de Chaves

// ✅ Bom - hierárquico e específico
i18n.get('features.auth.login.signIn')
i18n.get('features.extract.balance.available')

// ❌ Ruim - muito genérico
i18n.get('button')
i18n.get('text1')

2. Uso de Parâmetros

// ✅ Bom - parâmetros ordenados
i18n.get('app.common.hello', params: [userName])

// ❌ Ruim - muitos parâmetros
i18n.get('complex.message', params: [p1, p2, p3, p4, p5])

3. Pluralização

// ✅ Bom - usar getPlural para quantidades
i18n.getPlural('app.common.activeFilters', count, params: [count.toString()])

// ❌ Ruim - lógica manual
count == 1 ? i18n.get('app.common.activeFilters.one', params: ['1']) : i18n.get('app.common.activeFilters.many', params: [count.toString()])

Troubleshooting

Problemas Comuns

  1. Texto não aparece: Verificar se a chave existe no JSON

  2. Parâmetros não substituídos: Verificar se params está sendo passado

  3. Pluralização não funciona: Verificar estrutura one/many no JSON

Sistema de Validação

A ferramenta em tools/i18n_validator/ verifica se todas as strings usadas no código existem nos arquivos de tradução.

Comando de execução

  • execute 'dart run tools/i18n_validator/main.dart' para validar strings usadas nos arquivos .dart

Como Funciona

1. Validadores

SingleTextValidator: Verifica uso de i18n.get()

// Regex: r"i18n[\.\s]*get\([\s]*\'([\.a-zA-Z0-9]*)\')"
// Encontra: i18n.get('app.common.hello')

PluralTextValidator: Verifica uso de i18n.getPlural()

// Regex: r"i18n[\.\s]*getPlural\([\s]*\'([\.a-zA-Z0-9]*)\')"
// Encontra: i18n.getPlural('app.common.activeFilters', count, params: [count.toString()])

2. Processo de Validação

  1. Busca Arquivos: Encontra todos os .dart em lib/ e .json em assets/i18n/

  2. Extrai Chaves: Usa regex para encontrar chamadas i18n.get() e i18n.getPlural()

  3. Valida Existência: Verifica se cada chave existe no JSON

  4. Valida Estrutura: Para plurais, verifica se tem one e many

Conclusão

Este sistema de internacionalização oferece uma estrutura robusta e escalável para aplicações Flutter. Com arquivos JSON hierárquicos, validação e integração transparente via Provider, garante consistência entre idiomas e facilita a manutenção. A separação clara entre lógica de tradução e interface, combinada com widgets base que injetam automaticamente as dependências, proporciona uma experiência de desenvolvimento fluida e organizada para aplicativos com suporte à diferentes idiomas.

Last updated