BowlerPlate
Features

Internationalization (i18n)

2/19/2026

Comprehensive guide for handling multiple languages and localization in the Flutter application.

The boilerplate uses the official Flutter internationalization (i18n) system, powered by the flutter_localizations package and the gen-l10n tool. This approach ensures high performance, type safety, and seamless integration with the Flutter ecosystem.

Overview

The localization system consists of three main parts:

  1. ARB Files: JSON-like files (.arb) containing key-value pairs for translations.
  2. Code Generation: The gen-l10n tool generates type-safe Dart classes from ARB files.
  3. State Management: Riverpod providers manage the current locale and persist user preferences.

Configuration

The localization behavior is configured in the l10n.yaml file located in the root of the Flutter project:

arb-dir: lib/l10n
template-arb-file: app_id.arb
output-localization-file: app_localizations.dart
output-class: AppLocalizations
output-dir: lib/l10n/generated
nullable-getter: false
  • arb-dir: Where your translation files live.
  • template-arb-file: The source of truth for all localization keys (in this case, Indonesian).
  • output-class: The name of the generated class used in code.

ARB Files

Translation files are located in lib/l10n/. The boilerplate currently supports:

  • app_id.arb: Bahasa Indonesia (Default)
  • app_en.arb: English

Example Entry

{
  "hello": "Halo!",
  "@hello": {
    "description": "Greeting shown on the home screen"
  }
}

Adding New Strings

  1. Open lib/l10n/app_id.arb (the template file).
  2. Add your new key and value.
  3. Add the corresponding value in lib/l10n/app_en.arb.

Automatic Generation

The project is configured with generate: true in pubspec.yaml. This means Flutter will automatically run gen-l10n whenever you build the app or save files in many IDEs. If you need to trigger it manually, use:

flutter gen-l10n

Formatting Dates and Numbers

For formatting dates, currencies, and numbers according to the user's locale, the boilerplate uses the intl package.

import 'package:intl/intl.dart';

// Formats date based on current locale
String formatDate(DateTime date, String locale) {
  return DateFormat.yMMMMd(locale).format(date);
}

// Inside a widget
Text(DateFormat.yMMMMd(Localizations.localeOf(context).toString()).format(DateTime.now()))

Usage in Code

To access localized strings, use the AppLocalizations class provided by the BuildContext.

Basic Usage

import 'package:mobile/l10n/generated/app_localizations.dart';

// ... inside build method
final l10n = AppLocalizations.of(context);

Text(l10n.hello)

With Placeholders

If your ARB entry has placeholders: "welcomeUser": "Welcome, {name}"

Usage:

Text(l10n.welcomeUser('John'))

State Management & Persistence

The application's locale is managed by Riverpod and is persisted both locally and on the server.

Persistence Flow

  1. User selects a language in the app settings.
  2. UserPreferencesNotifier updates the state and saves to SharedPreferences.
  3. The change is asynchronously synced to the backend server.
  4. On app restart, the locale is restored from SharedPreferences.

Changing Language Programmatically

You can use the languageNotifier or update through userPreferencesProvider:

// Using LanguageNotifier
ref.read(languageNotifierProvider.notifier).setLanguage('en');

// OR directly via UserPreferences
ref.read(userPreferencesProvider.notifier).setLanguage('id');

Adding a New Language

To add support for a new language (e.g., Japanese - ja):

  1. Create ARB File: Create lib/l10n/app_ja.arb and translate all keys from the template.
  2. Update SupportedLocales: Edit lib/providers/user_preferences_provider.dart:
    static const Locale japanese = Locale('ja');
    static const List<Locale> all = [indonesian, english, japanese];
    
    static const Map<String, String> names = {
      'id': 'Bahasa Indonesia',
      'en': 'English',
      'ja': '日本語',
    };
  3. UI Updates: The language selection dialog in lib/widgets/language_settings.dart automatically pulls from SupportedLocales.all, so it will show up there automatically.

Best Practices

  1. Descriptive Keys: Use semantic names like loginButtonLabel instead of generic ones like button1.
  2. Provide Descriptions: Use the @key syntax in the template ARB to provide context for translators.
  3. Avoid Hardcoding: Never hardcode user-facing strings in widgets. Always add them to ARB files.
  4. Handle Plurals: Use Flutter's built-in plural support in ARB files for complex quantities.

On this page