← Back to Blog

Flutter Translations: Complete Guide to Managing Multi-Language Apps

fluttertranslationsworkflowlocalizationmanagementscaling

Flutter Translations: Complete Guide to Managing Multi-Language Apps

Master Flutter translations from setup to production. Learn the complete workflow for managing translations, organizing ARB files, and scaling your localization across dozens of languages.

Understanding Flutter Translations

Flutter translations allow your app to display text in the user's preferred language. The official approach uses ARB (Application Resource Bundle) files with the flutter_localizations package.

# pubspec.yaml
dependencies:
  flutter:
    sdk: flutter
  flutter_localizations:
    sdk: flutter
  intl: any

flutter:
  generate: true

Setting Up Your Translation Workflow

Step 1: Configure l10n.yaml

Create an l10n.yaml file in your project root:

arb-dir: lib/l10n
template-arb-file: app_en.arb
output-localization-file: app_localizations.dart
output-class: AppLocalizations
nullable-getter: false

Step 2: Create Your Template ARB File

The template file defines all translatable strings:

{
  "@@locale": "en",
  "appTitle": "My Flutter App",
  "@appTitle": {
    "description": "The title of the application"
  },
  "welcomeMessage": "Welcome to our app!",
  "@welcomeMessage": {
    "description": "Greeting shown on the home screen"
  },
  "itemCount": "{count, plural, =0{No items} =1{1 item} other{{count} items}}",
  "@itemCount": {
    "description": "Shows number of items in cart",
    "placeholders": {
      "count": {
        "type": "int",
        "example": "5"
      }
    }
  }
}

Step 3: Add Translation Files

Create additional ARB files for each language:

lib/l10n/app_es.arb (Spanish):

{
  "@@locale": "es",
  "appTitle": "Mi Aplicación Flutter",
  "welcomeMessage": "¡Bienvenido a nuestra aplicación!",
  "itemCount": "{count, plural, =0{Sin artículos} =1{1 artículo} other{{count} artículos}}"
}

lib/l10n/app_de.arb (German):

{
  "@@locale": "de",
  "appTitle": "Meine Flutter App",
  "welcomeMessage": "Willkommen in unserer App!",
  "itemCount": "{count, plural, =0{Keine Artikel} =1{1 Artikel} other{{count} Artikel}}"
}

Step 4: Configure MaterialApp

import 'package:flutter_localizations/flutter_localizations.dart';
import 'package:flutter_gen/gen_l10n/app_localizations.dart';

class MyApp extends StatelessWidget {
  @override
  Widget build(BuildContext context) {
    return MaterialApp(
      localizationsDelegates: AppLocalizations.localizationsDelegates,
      supportedLocales: AppLocalizations.supportedLocales,
      home: HomePage(),
    );
  }
}

Step 5: Use Translations

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

    return Scaffold(
      appBar: AppBar(title: Text(l10n.appTitle)),
      body: Column(
        children: [
          Text(l10n.welcomeMessage),
          Text(l10n.itemCount(5)),
        ],
      ),
    );
  }
}

Organizing Translations at Scale

File Structure for Large Projects

lib/
├── l10n/
│   ├── app_en.arb          # English (template)
│   ├── app_es.arb          # Spanish
│   ├── app_de.arb          # German
│   ├── app_fr.arb          # French
│   ├── app_ar.arb          # Arabic
│   ├── app_ja.arb          # Japanese
│   ├── app_zh.arb          # Chinese (Simplified)
│   └── app_pt.arb          # Portuguese

Categorizing Translation Keys

Use consistent naming conventions to organize your translations:

{
  "@@locale": "en",

  "// --- Common ---": "",
  "common_ok": "OK",
  "common_cancel": "Cancel",
  "common_save": "Save",
  "common_delete": "Delete",
  "common_loading": "Loading...",

  "// --- Auth ---": "",
  "auth_login": "Log In",
  "auth_logout": "Log Out",
  "auth_signup": "Sign Up",
  "auth_forgotPassword": "Forgot Password?",

  "// --- Home ---": "",
  "home_title": "Home",
  "home_welcomeBack": "Welcome back, {name}!",

  "// --- Settings ---": "",
  "settings_title": "Settings",
  "settings_language": "Language",
  "settings_notifications": "Notifications"
}

Namespace Pattern

For very large apps, consider a namespace pattern:

{
  "@@locale": "en",

  "nav.home": "Home",
  "nav.profile": "Profile",
  "nav.settings": "Settings",

  "auth.login.title": "Welcome Back",
  "auth.login.email": "Email Address",
  "auth.login.password": "Password",
  "auth.login.submit": "Sign In",

  "profile.edit.title": "Edit Profile",
  "profile.edit.name": "Display Name",
  "profile.edit.bio": "Bio"
}

Managing Translation Quality

Adding Context for Translators

Always include descriptions and examples:

{
  "greeting": "Hey {name}!",
  "@greeting": {
    "description": "Informal greeting shown to returning users on the home screen",
    "placeholders": {
      "name": {
        "type": "String",
        "example": "Sarah"
      }
    }
  },

  "orderStatus": "Your order is {status}",
  "@orderStatus": {
    "description": "Status can be: Processing, Shipped, Delivered, Cancelled",
    "placeholders": {
      "status": {
        "type": "String",
        "example": "Shipped"
      }
    }
  }
}

Character Limits

Specify limits for UI constraints:

{
  "buttonText": "Submit",
  "@buttonText": {
    "description": "Primary action button. MAX 10 CHARACTERS to fit button width"
  },

  "tabLabel": "Home",
  "@tabLabel": {
    "description": "Bottom navigation tab. MAX 6 CHARACTERS"
  }
}

Handling Untranslatable Content

Some content should not be translated:

// Don't put these in ARB files:
// - Brand names: "Flutter", "Google"
// - Technical terms that shouldn't change
// - User-generated content
// - Dynamic server content

// Keep in ARB:
// - UI labels and buttons
// - Error messages
// - Instructional text
// - Static content

Translation Workflow Strategies

Strategy 1: Developer-First

  1. Developers add English strings as they code
  2. Run flutter gen-l10n to generate code
  3. Export ARB files for translation
  4. Import translated files back
  5. Review and merge

Strategy 2: Design-First

  1. Copy/UX team defines all strings upfront
  2. Create master translation sheet
  3. Developers reference the sheet
  4. Translations happen in parallel
  5. All languages ship together

Strategy 3: Continuous Translation

  1. English strings added continuously
  2. Translation platform syncs daily
  3. Human translators work on new strings
  4. Automated imports to codebase
  5. Ship when translation coverage hits threshold

Common Translation Challenges

Challenge 1: Missing Translations

Detect missing translations in CI:

// test/translation_test.dart
void main() {
  test('all locales have complete translations', () {
    final en = loadArb('lib/l10n/app_en.arb');
    final es = loadArb('lib/l10n/app_es.arb');

    final enKeys = en.keys.where((k) => !k.startsWith('@'));
    final esKeys = es.keys.where((k) => !k.startsWith('@'));

    final missing = enKeys.toSet().difference(esKeys.toSet());
    expect(missing, isEmpty, reason: 'Spanish missing keys: $missing');
  });
}

Challenge 2: Text Expansion

German and other languages can be 30-40% longer than English:

// Handle text overflow gracefully
Text(
  l10n.longMessage,
  overflow: TextOverflow.ellipsis,
  maxLines: 2,
)

// Use flexible layouts
Flexible(
  child: Text(l10n.buttonLabel),
)

Challenge 3: Concatenation Problems

Never concatenate translated strings:

// BAD - word order varies by language
final message = l10n.hello + ' ' + l10n.world;

// GOOD - single translation with placeholders
final message = l10n.helloWorld;  // "Hello, World!" or "Hola, Mundo!"

Challenge 4: Hardcoded Strings

Find hardcoded strings with this regex:

# Find potential hardcoded strings in Dart files
grep -rn "Text(['\"]" lib/ --include="*.dart"
grep -rn "title: ['\"]" lib/ --include="*.dart"

Automating Translation Workflows

Git Hooks for Validation

#!/bin/sh
# .git/hooks/pre-commit

# Check for missing ARB keys
dart run scripts/check_arb_keys.dart
if [ $? -ne 0 ]; then
  echo "Error: Missing translation keys detected"
  exit 1
fi

CI Pipeline Integration

# .github/workflows/translations.yml
name: Translation Check

on: [push, pull_request]

jobs:
  check-translations:
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@v3

      - name: Setup Flutter
        uses: subosito/flutter-action@v2

      - name: Generate localizations
        run: flutter gen-l10n

      - name: Check for missing keys
        run: dart run scripts/check_translations.dart

      - name: Verify no hardcoded strings
        run: dart run scripts/find_hardcoded_strings.dart

Translation Memory

Keep track of previous translations to ensure consistency:

{
  "// Translation memory - do not translate these differently": "",
  "brand_name": "FlutterLocalisation",
  "product_feature_sync": "Cloud Sync",
  "product_feature_export": "ARB Export"
}

Scaling to 20+ Languages

Priority Order for Languages

Based on typical Flutter app demographics:

  1. Tier 1 (Launch): English, Spanish, Chinese (Simplified)
  2. Tier 2 (Early): German, French, Japanese, Portuguese
  3. Tier 3 (Growth): Arabic, Hindi, Korean, Italian, Russian
  4. Tier 4 (Expansion): Turkish, Dutch, Polish, Vietnamese, Thai

Managing Many Languages

// locale_config.dart
class LocaleConfig {
  static const List<LocaleInfo> supportedLocales = [
    LocaleInfo('en', 'English', '🇺🇸'),
    LocaleInfo('es', 'Español', '🇪🇸'),
    LocaleInfo('de', 'Deutsch', '🇩🇪'),
    LocaleInfo('fr', 'Français', '🇫🇷'),
    LocaleInfo('ja', '日本語', '🇯🇵'),
    LocaleInfo('zh', '中文', '🇨🇳'),
    LocaleInfo('ar', 'العربية', '🇸🇦'),
    LocaleInfo('pt', 'Português', '🇧🇷'),
  ];
}

class LocaleInfo {
  final String code;
  final String nativeName;
  final String flag;

  const LocaleInfo(this.code, this.nativeName, this.flag);
}

Partial Translation Strategy

Ship with partial translations for new languages:

MaterialApp(
  localizationsDelegates: [
    AppLocalizations.delegate,
    // Fallback to English for missing translations
    GlobalMaterialLocalizations.delegate,
  ],
  localeResolutionCallback: (locale, supportedLocales) {
    // Check translation coverage
    final coverage = getTranslationCoverage(locale);
    if (coverage < 0.8) {
      // Fall back to English if less than 80% translated
      return const Locale('en');
    }
    return locale;
  },
)

Simplify with FlutterLocalisation

Managing translations across multiple languages is complex. FlutterLocalisation.com streamlines your workflow:

  • Visual ARB editor - Edit all languages side-by-side
  • AI translations - Generate quality translations for 80+ languages instantly
  • Missing key detection - See untranslated strings at a glance
  • Export ready - Download ARB files ready for your Flutter project
  • Team collaboration - Multiple translators can work simultaneously

Stop juggling JSON files manually. Try FlutterLocalisation free and scale your app globally.

Summary

Effective Flutter translation management requires:

  1. Proper setup - Configure l10n.yaml and MaterialApp correctly
  2. Organization - Use consistent naming and file structure
  3. Context - Provide descriptions for translators
  4. Automation - Validate translations in CI/CD
  5. Scalability - Plan for language expansion from the start

With the right workflow, you can maintain high-quality translations across dozens of languages without slowing down development.