🇦🇹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
Caminhos: Use camelCase para chaves
Hierarquia: Organize por módulo/funcionalidade
app.common
: Textos compartilhadosapp.features.{feature}
: Textos específicos de funcionalidadeaccessibility
: Textos para acessibilidade
Plurais: Use objetos com chaves
one
emany
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()
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:
Detecta idioma atual do dispositivo ou usa preferência salva
Carrega arquivo JSON correspondente
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})
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})
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
Texto não aparece: Verificar se a chave existe no JSON
Parâmetros não substituídos: Verificar se
params
está sendo passadoPluralizaçã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
Busca Arquivos: Encontra todos os
.dart
emlib/
e.json
emassets/i18n/
Extrai Chaves: Usa regex para encontrar chamadas
i18n.get()
ei18n.getPlural()
Valida Existência: Verifica se cada chave existe no JSON
Valida Estrutura: Para plurais, verifica se tem
one
emany
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