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 filesl10n.yaml # Configuration file1. Configuration
The internationalization is configured in l10n.yaml:
arb-dir: lib/l10n
template-arb-file: app_en.arb
output-localization-file: app_localizations.dart
suppress-warnings: trueKey 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):
{
"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:
// lib/l10n/app_de.arb
{
"appTitle": "Meine App",
"welcomeMessage": "Willkommen in unserer App!",
"loginButton": "Anmelden",
"signUpButton": "Registrieren",
"featurePageTitle": "Funktionen",
"createFeatureTooltip": "Neue Funktion erstellen"
}// 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:
# Generate localization files
flutter gen-l10n
# Or run pub get which also triggers generation
flutter pub getImportant: 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
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
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
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:
{
// 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
- Feature Prefix: Use feature name as prefix (
notesTitle,settingsTitle) - Action Suffix: Use action as suffix (
createNote,deleteNote) - Descriptive Names: Use clear, descriptive names (
noItemsMessage) - Consistent Casing: Use camelCase for keys
5. Advanced Features
Context and Descriptions
Add context and descriptions for translators:
{
"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
{
"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
{
"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
- Template First: Always add strings to the template file (
app_en.arb) first - Consistent Keys: Use consistent naming conventions across features
- Meaningful Names: Use descriptive key names that indicate usage
- Group Related: Group related strings together in the ARB files
Translation Workflow
- Add English First: Add new strings to
app_en.arb - Generate Code: Run
flutter pub getto generate Dart code - Use in Code: Implement the strings in your widgets
- Add Translations: Add translations to other language files
- Test Languages: Test your app with different languages
Code Usage
- Single Instance: Get
AppLocalizations.of(context)!once per widget - Null Safety: Use
!operator since localizations are always available - Parameter Safety: Ensure parameter types match ARB definitions
- Fallback Handling: Template language (English) is used as fallback
Performance
- Lazy Loading: Localizations are loaded lazily by default
- Caching: Generated localizations are cached automatically
- Bundle Size: Only include languages you actually support
7. Testing Localization
Widget Tests
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
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
- Missing Keys: Run
flutter pub getafter adding new keys - Type Errors: Ensure parameter types match ARB definitions
- Missing Translations: Template language is used as fallback
- Build Errors: Check ARB file syntax (valid JSON)
Debug Tips
- Check Generated Files: Look at generated
app_localizations.dart - Validate ARB: Ensure all ARB files have valid JSON syntax
- Parameter Matching: Verify parameter names match between ARB and code
- 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.