Flutter: Internacionalização e Localização

Um app para o mundo — a internacionalização permite que seu código fale a língua do usuário e use a moeda do usuário.

📋 Pré-requisitos: Você já deve estar familiarizado com

1. O Que Você Vai Aprender


2. Uma História Real de Caos na Expansão Global

(1) O Problema: Valores Fixos no Código Confundem Usuários Globais

O ShopApp do Bob só suporta Inglês e USD. Alice (uma usuária chinesa) vê "$9,999.00" e assume 9999 USD, quando deveria ser 9999 CNY. Charlie (um usuário japonês) vê "1,500" e não sabe se é mil e quinhentos ou quinze centos. Pior ainda, as descrições de produtos estão todas em Inglês, e usuários não-Ingleses têm 70% de taxa de rejeição.

(2) A Solução com ARB + Formatação ICU

O sistema l10n do Flutter usa arquivos ARB para gerenciar traduções, e a formatação ICU trata automaticamente das diferenças regionais em números/moeda/datas.

DART
import 'package:intl/intl.dart';

// ⚙️ Install dependencies: flutter pub add intl

// Locale-aware formatting
final price = NumberFormat.simpleCurrency(locale: 'zh_CN').format(9999);
// → ¥9,999.00 (CNY)

final price2 = NumberFormat.simpleCurrency(locale: 'en_US').format(1299.99);
// → $1,299.99 (USD)
TEXT
> Saída: Execute localmente com o Flutter SDK (Flutter 3.x / Dart 3.x). O servidor Piston não possui o Flutter instalado; use `flutter run` na sua máquina local para comparação. A UI/estado real pode variar ligeiramente entre plataformas.

(3) Benefício: Taxa de Rejeição Cai de 70% para 15%

Após implementar três idiomas + três moedas, as taxas de rejeição em regiões não-Inglesas caíram de 70% para 15%, e o mercado japonês viu um crescimento de 3x nos pedidos.


3. Fluxo de Dados i18n

100%
graph TD
    ARB[ARB Files] --> |l10n| GEN[Generated Dart]
    GEN --> MAT[MaterialApp.localizationsDelegates]
    MAT --> L10N[AppLocalizations]
    L10N --> EN[English: $1,299.99]
    L10N --> ZH[中文: ¥9,999.00]
    L10N --> JA[日本語: ¥150,000]
    subgraph Users
        Alice2[Alice: EN / USD]
        Bob2[Bob: ZH / CNY]
        Charlie[Charlie: JA / JPY]
    end
TEXT
> Saída: Execute localmente com o Flutter SDK (Flutter 3.x / Dart 3.x). O servidor Piston não possui o Flutter instalado; use `flutter run` na sua máquina local para comparação. A UI/estado real pode variar ligeiramente entre plataformas.

(1) Termos-Chave de Internacionalização

Termo Descrição Exemplo
i18n Internacionalização (18 letras omitidas) Suporte do framework
l10n Localização (10 letras omitidas) Traduções específicas
Locale Identificador de idioma + região en_US, zh_CN, ja_JP
ARB Application Resource Bundle Formato de arquivo de tradução
ICU International Components for Unicode Padrão de formatação

4. Fluxo de Trabalho com Arquivos ARB

(1) Configuração do Projeto

YAML
# l10n.yaml (project root)
arb-dir: lib/l10n
template-arb-file: app_en.arb
output-localization-file: app_localizations.dart
output-class: S
nullable-getter: false
YAML
# pubspec.yaml
flutter:
  generate: true

▶ Exemplo

TEXT
> Saída: Execute localmente com o Flutter SDK (Flutter 3.x / Dart 3.x). O servidor Piston não possui o Flutter instalado; use `flutter run` na sua máquina local para comparação. A UI/estado real pode variar ligeiramente entre plataformas.

: Arquivos de tradução ARB

JSON
// lib/l10n/app_en.arb (template)
{
  "appTitle": "ShopApp",
  "productCount": "{count, plural, =0{No products} =1{1 product} other{{count} products}}",
  "priceWithCurrency": "{price, select, USD{${price}} CNY{¥{price}} JPY{¥{price}}}",
  "welcomeMessage": "Welcome, {name}!",
  "lastUpdated": "Last updated: {date}",
  "@productCount": {
    "placeholders": { "count": { "type": "int" } }
  },
  "@priceWithCurrency": {
    "placeholders": { "price": { "type": "String" }, "currency": { "type": "String" } }
  },
  "@welcomeMessage": {
    "placeholders": { "name": { "type": "String" } }
  },
  "@lastUpdated": {
    "placeholders": { "date": { "type": "DateTime" } }
  }
}
JSON
// lib/l10n/app_zh.arb
{
  "appTitle": "ShopApp",
  "productCount": "{count, plural, =0{没有商品} other{{count} 件商品}}",
  "priceWithCurrency": "{price, select, USD{\\${price}} CNY{¥{price}} JPY{¥{price}}}",
  "welcomeMessage": "欢迎,{name}!",
  "lastUpdated": "最后更新:{date}"
}
JSON
// lib/l10n/app_ja.arb
{
  "appTitle": "ShopApp",
  "productCount": "{count, plural, other{{count}件の商品}}",
  "priceWithCurrency": "{price, select, USD{\\${price}} CNY{¥{price}} JPY{¥{price}}}",
  "welcomeMessage": "ようこそ、{name}さん!",
  "lastUpdated": "最終更新: {date}"
}

5. Configuração do MaterialApp

▶ Exemplo

TEXT
> Saída: Execute localmente com o Flutter SDK (Flutter 3.x / Dart 3.x). O servidor Piston não possui o Flutter instalado; use `flutter run` na sua máquina local para comparação. A UI/estado real pode variar ligeiramente entre plataformas.

: Configuração l10n

DART
import 'package:flutter/material.dart';
import 'package:flutter_gen/gen_l10n/app_localizations.dart';
import 'package:flutter_riverpod/flutter_riverpod.dart';

// ⚙️ Install dependencies: flutter pub add flutter_riverpod
// ⚙️ Configure l10n: create l10n.yaml in project root, add flutter: generate: true to pubspec.yaml

// Custom class definition source:
// - localeProvider: see Section 7 LocaleNotifier in this lesson

class ShopApp extends ConsumerWidget {
  @override
  Widget build(BuildContext context, WidgetRef ref) {
    final locale = ref.watch(localeProvider);
    return MaterialApp(
      locale: locale,
      localizationsDelegates: AppLocalizations.localizationsDelegates,
      supportedLocales: AppLocalizations.supportedLocales,
      title: 'ShopApp',
      home: const HomePage(),
    );
  }
}
TEXT
> Saída: Execute localmente com o Flutter SDK (Flutter 3.x / Dart 3.x). O servidor Piston não possui o Flutter instalado; use `flutter run` na sua máquina local para comparação. A UI/estado real pode variar ligeiramente entre plataformas.

▶ Exemplo

TEXT
> Saída: Execute localmente com o Flutter SDK (Flutter 3.x / Dart 3.x). O servidor Piston não possui o Flutter instalado; use `flutter run` na sua máquina local para comparação. A UI/estado real pode variar ligeiramente entre plataformas.

: Usando strings traduzidas

DART
import 'package:flutter/material.dart';
import 'package:flutter_gen/gen_l10n/app_localizations.dart';

// ⚙️ Configure l10n: create l10n.yaml in project root, run flutter gen-l10n to generate code

// In any widget
final s = AppLocalizations.of(context)!;

Text(s.appTitle)                              // ShopApp
Text(s.productCount(42))                      // 42 products / 42 件商品
Text(s.welcomeMessage('Alice'))               // Welcome, Alice! / 欢迎,Alice!
TEXT
> Saída: Execute localmente com o Flutter SDK (Flutter 3.x / Dart 3.x). O servidor Piston não possui o Flutter instalado; use `flutter run` na sua máquina local para comparação. A UI/estado real pode variar ligeiramente entre plataformas.

6. Formatação ICU

▶ Exemplo

TEXT
> Saída: Execute localmente com o Flutter SDK (Flutter 3.x / Dart 3.x). O servidor Piston não possui o Flutter instalado; use `flutter run` na sua máquina local para comparação. A UI/estado real pode variar ligeiramente entre plataformas.

: Formatação de moeda

DART
import 'package:intl/intl.dart';
import 'package:intl/number_symbols_data.dart';

// ⚙️ Install dependencies: flutter pub add intl

class CurrencyFormatter {
  static String format(double amount, {String locale = 'en_US', String? currency}) {
    final format = NumberFormat.simpleCurrency(locale: locale, name: currency);
    return format.format(amount);
  }
}

// Usage
CurrencyFormatter.format(1299.99, locale: 'en_US')        // $1,299.99
CurrencyFormatter.format(9999.00, locale: 'zh_CN')        // ¥9,999.00
CurrencyFormatter.format(150000, locale: 'ja_JP')         // ¥150,000
CurrencyFormatter.format(99.99, locale: 'de_DE', currency: 'EUR')  // 99,99 €
TEXT
> Saída: Execute localmente com o Flutter SDK (Flutter 3.x / Dart 3.x). O servidor Piston não possui o Flutter instalado; use `flutter run` na sua máquina local para comparação. A UI/estado real pode variar ligeiramente entre plataformas.
Locale Formato Numérico Formato Monetário Formato de Data
en_US 1,299.99 $1,299.99 07/13/2026
zh_CN 1,299.99 ¥9,999.00 2026/07/13
ja_JP 1,299.99 ¥150,000 2026/07/13
de_DE 1.299,99 1.299,99 € 13.07.2026

▶ Exemplo

TEXT
> Saída: Execute localmente com o Flutter SDK (Flutter 3.x / Dart 3.x). O servidor Piston não possui o Flutter instalado; use `flutter run` na sua máquina local para comparação. A UI/estado real pode variar ligeiramente entre plataformas.

: Formatação de data

DART
import 'package:intl/intl.dart';

// ⚙️ Install dependencies: flutter pub add intl

String formatDate(DateTime date, {String locale = 'en_US'}) {
  return DateFormat.yMMMd(locale).format(date);
}

// en_US: Jul 13, 2026
// zh_CN: 2026年7月13日
// ja_JP: 2026年7月13日
TEXT
> Saída: Execute localmente com o Flutter SDK (Flutter 3.x / Dart 3.x). O servidor Piston não possui o Flutter instalado; use `flutter run` na sua máquina local para comparação. A UI/estado real pode variar ligeiramente entre plataformas.

▶ Exemplo

TEXT
> Saída: Execute localmente com o Flutter SDK (Flutter 3.x / Dart 3.x). O servidor Piston não possui o Flutter instalado; use `flutter run` na sua máquina local para comparação. A UI/estado real pode variar ligeiramente entre plataformas.

: Formatação de plurais

DART
// In ARB file
"cartItemCount": "{count, plural, =0{Your cart is empty} =1{1 item in cart} other{{count} items in cart}}"

// Usage
Text(s.cartItemCount(0))    // Your cart is empty
Text(s.cartItemCount(1))    // 1 item in cart
Text(s.cartItemCount(5))    // 5 items in cart
TEXT
> Saída: Execute localmente com o Flutter SDK (Flutter 3.x / Dart 3.x). O servidor Piston não possui o Flutter instalado; use `flutter run` na sua máquina local para comparação. A UI/estado real pode variar ligeiramente entre plataformas.

7. Alternância Dinâmica de Idioma

▶ Exemplo

TEXT
> Saída: Execute localmente com o Flutter SDK (Flutter 3.x / Dart 3.x). O servidor Piston não possui o Flutter instalado; use `flutter run` na sua máquina local para comparação. A UI/estado real pode variar ligeiramente entre plataformas.

: Gerenciamento de Locale com Riverpod

DART
import 'package:flutter/material.dart';
import 'package:flutter_riverpod/flutter_riverpod.dart';
import 'package:riverpod_annotation/riverpod_annotation.dart';
import 'package:shared_preferences/shared_preferences.dart';
import 'package:intl/intl.dart';

// ⚙️ Install dependencies: flutter pub add flutter_riverpod riverpod_annotation shared_preferences intl
// ⚙️ Dev dependencies: flutter pub add --dev riverpod_generator build_runner

// Custom class definition source:
// - localeNotifierProvider/currencyProvider: see definitions below

@riverpod
class LocaleNotifier extends _$LocaleNotifier {
  @override
  Locale build() {
    // Load saved preference
    _loadSavedLocale();
    return const Locale('en');
  }

  Future<void> _loadSavedLocale() async {
    final prefs = await SharedPreferences.getInstance();
    final saved = prefs.getString('locale');
    if (saved != null) {
      state = Locale(saved);
    }
  }

  Future<void> setLocale(Locale locale) async {
    state = locale;
    final prefs = await SharedPreferences.getInstance();
    await prefs.setString('locale', locale.languageCode);
  }
}

// Settings page
class LanguageSettingsPage extends ConsumerWidget {
  static const _locales = [
    (Locale('en'), 'English', '🇺🇸'),
    (Locale('zh'), '中文', '🇨🇳'),
    (Locale('ja'), '日本語', '🇯🇵'),
  ];

  @override
  Widget build(BuildContext context, WidgetRef ref) {
    final current = ref.watch(localeNotifierProvider);
    return Scaffold(
      appBar: AppBar(title: const Text('Language')),
      body: ListView(children: _locales.map((item) {
        final (locale, name, flag) = item;
        return ListTile(
          leading: Text(flag, style: const TextStyle(fontSize: 24)),
          title: Text(name),
          trailing: current == locale ? const Icon(Icons.check, color: Colors.green) : null,
          onTap: () => ref.read(localeNotifierProvider.notifier).setLocale(locale),
        );
      }).toList()),
    );
  }
}
TEXT
> Saída: Execute localmente com o Flutter SDK (Flutter 3.x / Dart 3.x). O servidor Piston não possui o Flutter instalado; use `flutter run` na sua máquina local para comparação. A UI/estado real pode variar ligeiramente entre plataformas.

8. Exemplo Completo: Widget de Preço Localizado do ShopApp

DART
import 'package:flutter/material.dart';
import 'package:flutter_riverpod/flutter_riverpod.dart';
import 'package:riverpod_annotation/riverpod_annotation.dart';
import 'package:shared_preferences/shared_preferences.dart';
import 'package:intl/intl.dart';

// ⚙️ Install dependencies: flutter pub add flutter_riverpod riverpod_annotation shared_preferences intl
// ⚙️ Dev dependencies: flutter pub add --dev riverpod_generator build_runner

// Custom class definition source:
// - localeNotifierProvider: see Section 7 LocaleNotifier in this lesson
// - currencyProvider: see CurrencyNotifier below

class LocalizedPrice extends ConsumerWidget {
  final double amount;
  final TextStyle? style;

  const LocalizedPrice({super.key, required this.amount, this.style});

  @override
  Widget build(BuildContext context, WidgetRef ref) {
    final locale = ref.watch(localeNotifierProvider);
    final currency = ref.watch(currencyProvider);
    final formatted = _formatPrice(amount, locale.languageCode, currency);
    return Text(formatted, style: style ?? const TextStyle(fontSize: 18, fontWeight: FontWeight.bold));
  }

  String _formatPrice(double amount, String languageCode, String currencyCode) {
    final locale = switch (languageCode) {
      'zh' => 'zh_CN',
      'ja' => 'ja_JP',
      _ => 'en_US',
    };
    return NumberFormat.simpleCurrency(locale: locale, name: currencyCode).format(amount);
  }
}

// Currency provider
@riverpod
class CurrencyNotifier extends _$CurrencyNotifier {
  @override
  String build() {
    _loadSaved();
    return 'USD';
  }

  Future<void> _loadSaved() async {
    final prefs = await SharedPreferences.getInstance();
    final saved = prefs.getString('currency');
    if (saved != null) state = saved;
  }

  Future<void> setCurrency(String code) async {
    state = code;
    final prefs = await SharedPreferences.getInstance();
    await prefs.setString('currency', code);
  }
}

// Usage in product card
LocalizedPrice(amount: product.price)  // Auto-formats based on user's locale & currency
TEXT
> Saída: Execute localmente com o Flutter SDK (Flutter 3.x / Dart 3.x). O servidor Piston não possui o Flutter instalado; use `flutter run` na sua máquina local para comparação. A UI/estado real pode variar ligeiramente entre plataformas.

❓ Perguntas Frequentes

P: Como as mudanças em arquivos ARB entram em vigor? R: Execute flutter gen-l10n ou simplesmente flutter run (que dispara a geração automaticamente).

P: O hot reload suporta alternância de idioma? R: Parcialmente. Mudanças de conteúdo ARB exigem hot restart (R), mas troca de Locale suporta hot reload (r).

P: Devo usar intl ou concatenação manual para formatação de moeda? R: Use NumberFormat.simpleCurrency do intl. Concatenação manual não consegue lidar com diferenças de separador de milhar e ponto decimal entre locales.

P: Como formatar "wan" chinês? R: O padrão ICU não tem unidade "wan" — números chineses ainda usam vírgulas de separador de milhar (9,999 não 0.9999). Formatação personalizada é necessária para exibição em "wan".

P: Como suportar idiomas RTL (Árabe)? R: O Flutter trata automaticamente o layout RTL (defina locale: Locale('ar')). Certifique-se de usar Directionality e start/end em vez de left/right.

P: Quem mantém os arquivos de tradução? R: Desenvolvedores mantêm o template ARB em Inglês; equipes de tradução ou IA traduzem os ARBs de outros idiomas. Recomenda-se usar plataformas de gerenciamento de tradução como Lokalise/Crowdin.


📖 Resumo


📝 Exercícios

  1. Básico (dificuldade ⭐): Configure l10n.yaml e arquivos ARB, implemente alternância bilíngue Chinês/Inglês com pelo menos 10 strings traduzidas.
  2. Intermediário (dificuldade ⭐⭐): Adicione suporte ao Japonês, implemente formatação de plurais ICU (contagem de itens do carrinho), e auto-adapte números com separador de milhar.
  3. Desafio (dificuldade ⭐⭐⭐): Implemente um sistema completo multi-idioma do ShopApp: trilíngue Chinês/Inglês/Japonês + trí-moeda USD/CNY/JPY + localização de formato de data + página de configurações para alternância + persistência com SharedPreferences.

← Aula Anterior | Próxima Aula →

Web-Tutorial.com

Equipe Técnica Web-Tutorial

Uma plataforma de tutoriais mantida por diversos desenvolvedores. Cada tutorial é escrito e revisado por profissionais da área correspondente. Trabalhamos para manter nosso conteúdo preciso e confiável — se encontrar algum problema, avise-nos.

100%