Skip to content

Internationalization (i18n) Guide

This project uses Flutter's built-in internationalization support with ARB (Application Resource Bundle) files for managing translations across multiple languages.

Structure

lib/l10n/
├── app_en.arb                 # English (template)
├── app_de.arb                 # German
├── app_es.arb                 # Spanish
├── app_fr.arb                 # French
├── app_ar.arb                 # Arabic
├── app_hi.arb                 # Hindi
├── app_ja.arb                 # Japanese
├── app_bn.arb                 # Bengali
├── app_localizations.dart     # Generated localizations
└── app_localizations_*.dart   # Generated language files
l10n.yaml                      # Configuration file

1. Configuration

The internationalization is configured in l10n.yaml:

yaml
arb-dir: lib/l10n
template-arb-file: app_en.arb
output-localization-file: app_localizations.dart
suppress-warnings: true

Key Configuration Options

  • arb-dir: Directory containing ARB files
  • template-arb-file: Template file (usually English)
  • output-localization-file: Generated Dart file name
  • suppress-warnings: Reduces noise from missing translations

2. Adding New Strings

Step 1: Add to Template File

Add new keys to lib/l10n/app_en.arb (the template file):

json
{
  "appTitle": "My App",
  "welcomeMessage": "Welcome to our app!",
  "loginButton": "Log In",
  "signUpButton": "Sign Up",

  // Feature-specific strings
  "featurePageTitle": "Features",
  "createFeatureTooltip": "Create new feature",
  "noItemsMessage": "No items found",
  "createFirstItemMessage": "Tap + to create your first item",

  // Strings with parameters
  "welcomeUser": "Welcome, {userName}!",
  "@welcomeUser": {
    "description": "Welcome message with user name",
    "placeholders": {
      "userName": {
        "type": "String",
        "example": "John"
      }
    }
  },

  // Pluralization
  "itemCount": "{count, plural, =0{No items} =1{1 item} other{{count} items}}",
  "@itemCount": {
    "description": "Number of items",
    "placeholders": {
      "count": {
        "type": "int",
        "example": "5"
      }
    }
  },

  // Date formatting
  "lastUpdated": "Last updated: {date}",
  "@lastUpdated": {
    "description": "Last updated timestamp",
    "placeholders": {
      "date": {
        "type": "DateTime",
        "format": "yMd"
      }
    }
  }
}

Step 2: Add to Other Language Files

Add corresponding translations to other ARB files:

json
// lib/l10n/app_de.arb
{
  "appTitle": "Meine App",
  "welcomeMessage": "Willkommen in unserer App!",
  "loginButton": "Anmelden",
  "signUpButton": "Registrieren",
  "featurePageTitle": "Funktionen",
  "createFeatureTooltip": "Neue Funktion erstellen"
}
json
// lib/l10n/app_es.arb
{
  "appTitle": "Mi Aplicación",
  "welcomeMessage": "¡Bienvenido a nuestra aplicación!",
  "loginButton": "Iniciar Sesión",
  "signUpButton": "Registrarse",
  "featurePageTitle": "Características",
  "createFeatureTooltip": "Crear nueva característica"
}

Step 3: Generate Localization Files

After adding new keys, run the code generation command:

bash
# Generate localization files
flutter gen-l10n

# Or run pub get which also triggers generation
flutter pub get

Important: You must run flutter pub get after adding new keys to regenerate the localization files. The new keys won't be available in your code until you do this.

3. Using Localized Strings in Code

Basic Usage

dart
import 'package:flutter/material.dart';
import 'package:flutter_boilerplate/l10n/app_localizations.dart';

class MyWidget extends StatelessWidget {
  @override
  Widget build(BuildContext context) {
    // ✅ Get localizations instance
    final l10n = AppLocalizations.of(context)!;

    return Scaffold(
      appBar: AppBar(
        title: Text(l10n.appTitle),
      ),
      body: Column(
        children: [
          Text(l10n.welcomeMessage),
          ElevatedButton(
            onPressed: () {},
            child: Text(l10n.loginButton),
          ),
          IconButton(
            onPressed: () {},
            icon: const Icon(Icons.add),
            tooltip: l10n.createFeatureTooltip,
          ),
        ],
      ),
    );
  }
}

Strings with Parameters

dart
class UserProfileWidget extends StatelessWidget {
  final String userName;

  const UserProfileWidget({required this.userName, super.key});

  @override
  Widget build(BuildContext context) {
    final l10n = AppLocalizations.of(context)!;

    return Column(
      children: [
        // ✅ String with parameter
        Text(l10n.welcomeUser(userName)),

        // ✅ Date formatting
        Text(l10n.lastUpdated(DateTime.now())),
      ],
    );
  }
}

Pluralization

dart
class ItemListWidget extends StatelessWidget {
  final List<Item> items;

  const ItemListWidget({required this.items, super.key});

  @override
  Widget build(BuildContext context) {
    final l10n = AppLocalizations.of(context)!;

    return Column(
      children: [
        // ✅ Pluralization
        Text(l10n.itemCount(items.length)),

        if (items.isEmpty)
          Text(l10n.noItemsMessage)
        else
          ...items.map((item) => ItemWidget(item: item)),
      ],
    );
  }
}

4. String Categories and Organization

Organize by Feature

Group related strings together in ARB files:

json
{
  // App-wide strings
  "appTitle": "My App",
  "loading": "Loading...",
  "error": "Error",
  "retry": "Retry",

  // Authentication
  "loginTitle": "Log In",
  "loginButton": "Log In",
  "signUpButton": "Sign Up",
  "forgotPassword": "Forgot Password?",

  // Notes feature
  "notesTitle": "Notes",
  "createNote": "Create Note",
  "editNote": "Edit Note",
  "deleteNote": "Delete Note",
  "noteTitle": "Title",
  "noteContent": "Content",

  // Settings
  "settingsTitle": "Settings",
  "themeSettings": "Theme",
  "languageSettings": "Language",
  "notificationSettings": "Notifications",

  // Common actions
  "save": "Save",
  "cancel": "Cancel",
  "delete": "Delete",
  "edit": "Edit",
  "create": "Create",
  "update": "Update"
}

Naming Conventions

  1. Feature Prefix: Use feature name as prefix (notesTitle, settingsTitle)
  2. Action Suffix: Use action as suffix (createNote, deleteNote)
  3. Descriptive Names: Use clear, descriptive names (noItemsMessage)
  4. Consistent Casing: Use camelCase for keys

5. Advanced Features

Context and Descriptions

Add context and descriptions for translators:

json
{
  "loginButton": "Log In",
  "@loginButton": {
    "description": "Button text for user authentication"
  },

  "itemCount": "{count, plural, =0{No items} =1{1 item} other{{count} items}}",
  "@itemCount": {
    "description": "Shows the number of items in a list",
    "context": "Used in item lists and counters",
    "placeholders": {
      "count": {
        "type": "int",
        "example": "5"
      }
    }
  }
}

Gender and Select

json
{
  "userGreeting": "{gender, select, male{Welcome, Mr. {name}} female{Welcome, Ms. {name}} other{Welcome, {name}}}",
  "@userGreeting": {
    "placeholders": {
      "gender": {
        "type": "String"
      },
      "name": {
        "type": "String"
      }
    }
  }
}

Date and Number Formatting

json
{
  "currentDate": "Today is {date}",
  "@currentDate": {
    "placeholders": {
      "date": {
        "type": "DateTime",
        "format": "yMMMMd"
      }
    }
  },

  "price": "Price: {amount}",
  "@price": {
    "placeholders": {
      "amount": {
        "type": "double",
        "format": "currency",
        "optionalParameters": {
          "symbol": "$"
        }
      }
    }
  }
}

6. Best Practices

String Management

  1. Template First: Always add strings to the template file (app_en.arb) first
  2. Consistent Keys: Use consistent naming conventions across features
  3. Meaningful Names: Use descriptive key names that indicate usage
  4. Group Related: Group related strings together in the ARB files

Translation Workflow

  1. Add English First: Add new strings to app_en.arb
  2. Generate Code: Run flutter pub get to generate Dart code
  3. Use in Code: Implement the strings in your widgets
  4. Add Translations: Add translations to other language files
  5. Test Languages: Test your app with different languages

Code Usage

  1. Single Instance: Get AppLocalizations.of(context)! once per widget
  2. Null Safety: Use ! operator since localizations are always available
  3. Parameter Safety: Ensure parameter types match ARB definitions
  4. Fallback Handling: Template language (English) is used as fallback

Performance

  1. Lazy Loading: Localizations are loaded lazily by default
  2. Caching: Generated localizations are cached automatically
  3. Bundle Size: Only include languages you actually support

7. Testing Localization

Widget Tests

dart
void main() {
  group('LocalizedWidget', () {
    testWidgets('should display localized text', (tester) async {
      await tester.pumpWidget(
        MaterialApp(
          localizationsDelegates: AppLocalizations.localizationsDelegates,
          supportedLocales: AppLocalizations.supportedLocales,
          home: const MyWidget(),
        ),
      );

      // Test English (default)
      expect(find.text('Welcome to our app!'), findsOneWidget);
    });

    testWidgets('should display German text', (tester) async {
      await tester.pumpWidget(
        MaterialApp(
          localizationsDelegates: AppLocalizations.localizationsDelegates,
          supportedLocales: AppLocalizations.supportedLocales,
          locale: const Locale('de'),
          home: const MyWidget(),
        ),
      );

      // Test German
      expect(find.text('Willkommen in unserer App!'), findsOneWidget);
    });
  });
}

Integration Tests

dart
void main() {
  group('Localization Integration', () {
    testWidgets('should handle language switching', (tester) async {
      await tester.pumpWidget(MyApp());

      // Start with English
      expect(find.text('Settings'), findsOneWidget);

      // Switch to German
      await tester.tap(find.byIcon(Icons.language));
      await tester.pumpAndSettle();
      await tester.tap(find.text('Deutsch'));
      await tester.pumpAndSettle();

      // Verify German text
      expect(find.text('Einstellungen'), findsOneWidget);
    });
  });
}

8. Troubleshooting

Common Issues

  1. Missing Keys: Run flutter pub get after adding new keys
  2. Type Errors: Ensure parameter types match ARB definitions
  3. Missing Translations: Template language is used as fallback
  4. Build Errors: Check ARB file syntax (valid JSON)

Debug Tips

  1. Check Generated Files: Look at generated app_localizations.dart
  2. Validate ARB: Ensure all ARB files have valid JSON syntax
  3. Parameter Matching: Verify parameter names match between ARB and code
  4. Locale Support: Ensure target locales are in supportedLocales

The internationalization system provides a robust foundation for multi-language support. Always add strings to ARB files first, run flutter pub get to regenerate, then use the generated methods in your code.