← Back to posts Cover image for Mastering Flutter Localization: From .arb Files to AI-Powered Translations

Mastering Flutter Localization: From .arb Files to AI-Powered Translations

· 5 min read
Weekly Digest

The Flutter news you actually need

No spam, ever. Unsubscribe in one click.

Chris
By Chris

Localizing a Flutter app is a critical step for reaching a global audience, but it often becomes a sprawling, tedious task. As your app grows, you find yourself wrestling with massive .arb files, struggling to invent clear key names, and manually coordinating translations across multiple languages. The process can feel more like bureaucratic management than development. The good news? With a solid foundation and modern tooling, you can transform localization from a chore into a streamlined, even automated, part of your workflow.

The Foundation: flutter_localizations and .arb Files

First, let’s ensure our base is correct. Flutter’s official localization system uses the intl package and Application Resource Bundle (.arb) files. A common mistake is not setting this up for scalability.

1. Add the dependencies:

dependencies:
  flutter:
    sdk: flutter
  flutter_localizations:
    sdk: flutter
  intl: ^0.19.0

d_dependencies:
  flutter_gen_runner: ^5.4.15
  build_runner: any

2. Configure l10n.yaml in your project root: This file controls the code generation.

arb-dir: lib/l10n
template-arb-file: app_en.arb
output-localization-file: app_localizations.dart
nullable-getter: false
use-deferred-loading: false

3. Create your .arb files in lib/l10n/. app_en.arb (your template locale):

{
  "@@locale": "en",
  "welcomeTitle": "Hello, {name}!",
  "@welcomeTitle": {
    "description": "A welcome message",
    "placeholders": {
      "name": {}
    }
  },
  "userProfile": "Profile"
}

app_es.arb:

{
  "@@locale": "es",
  "welcomeTitle": "¡Hola, {name}!",
  "userProfile": "Perfil"
}

4. Generate the code: Run flutter gen-l10n or dart run build_runner build. This creates the AppLocalizations class.

5. Use it in your app:

MaterialApp(
  localizationsDelegates: AppLocalizations.localizationsDelegates,
  supportedLocales: AppLocalizations.supportedLocales,
  home: Scaffold(
    appBar: AppBar(title: Text(AppLocalizations.of(context)!.userProfile)),
    body: Center(
      child: Text(
        AppLocalizations.of(context)!.welcomeTitle('Maria'),
      ),
    ),
  ),
);

The Real Pain Points & Solutions

With the basics running, the real challenges emerge.

1. Key Naming Chaos A key named title is meaningless. Does it belong to a button, a screen, a dialog? As your app grows, this becomes unmanageable.

  • Solution: Adopt a hierarchical, descriptive naming convention. Group keys by feature and purpose.
    • Bad: message, title, error
    • Good: homeScreen_welcomeHeader, loginButton_submitLabel, validation_error_emailEmpty

Your .arb files become self-documenting:

{
  "homeScreen_welcomeHeader": "Welcome Back",
  "loginButton_submitLabel": "Sign In"
}

2. Managing Growth and Adding New Strings Manually adding a new key to every .arb file (e.g., en, es, fr, de) is error-prone and slow.

3. Translation Itself Hiring professional translators for every iteration can be costly and slow for agile development.

Leveraging AI for a Supercharged Workflow

This is where modern AI tools can revolutionize the process. The goal isn’t to replace human translators for final copy, but to handle the bulk of the grunt work and initial translation.

Imagine a script that:

  1. Parses your template .arb file (e.g., app_en.arb).
  2. Identifies any new or modified keys since the last run.
  3. Sends only those new strings to an AI translation API (like OpenAI GPT, Gemini, or a dedicated model) for each target language.
  4. Automatically appends the translations to the respective .arb files.

Here’s a conceptual Dart script (bin/translate_arb.dart) to illustrate the logic:

import 'dart:convert';
import 'dart:io';
import 'package:http/http.dart' as http;

Future<void> main() async {
  final templateArb = File('lib/l10n/app_en.arb');
  final targetLocales = ['es', 'fr', 'de'];

  final Map<String, dynamic> sourceData = json.decode(await templateArb.readAsString());

  // Filter for actual translatable strings (ignore metadata keys starting with '@')
  final newStrings = <String, String>{};
  sourceData.forEach((key, value) {
    if (!key.startsWith('@') && value is String) {
      newStrings[key] = value;
    }
  });

  for (final locale in targetLocales) {
    final targetFile = File('lib/l10n/app_$locale.arb');
    Map<String, dynamic> existingData = {};
    if (await targetFile.exists()) {
      existingData = json.decode(await targetFile.readAsString());
    }

    final stringsToTranslate = <String, String>{};
    for (final entry in newStrings.entries) {
      if (!existingData.containsKey(entry.key)) {
        stringsToTranslate[entry.key] = entry.value;
      }
    }

    if (stringsToTranslate.isNotEmpty) {
      print('Translating ${stringsToTranslate.length} strings to $locale...');
      final translated = await _batchTranslate(stringsToTranslate, 'en', locale);
      existingData.addAll(translated);
      await targetFile.writeAsString(json.encode(existingData));
    }
  }
  print('Translation update complete.');
}

// Placeholder for your AI/Translation API call.
Future<Map<String, dynamic>> _batchTranslate(
    Map<String, String> strings, String fromLang, String toLang) async {
  // This is a mock implementation.
  // In reality, you would call an API like OpenAI, Google Cloud Translation, etc.
  final translatedMap = <String, dynamic>{};
  for (final entry in strings.entries) {
    // Simulate an API call that returns a translation.
    translatedMap[entry.key] = _mockAITranslate(entry.value, toLang);
  }
  translatedMap['@@locale'] = toLang;
  return translatedMap;
}

String _mockAITranslate(String text, String toLang) {
  // In a real scenario, this is where you'd integrate with an LLM.
  // Example: "Hello!" -> "¡Hola!" for 'es'
  return '[AI_Translated($toLang): $text]';
}

Important: This script is a blueprint. You must replace _mockAITranslate with actual API calls, implement error handling, and respect rate limits. The core idea is powerful: automate the propagation of new strings.

The Final Workflow

  1. Develop: Add all new UI text directly to your template app_en.arb file with clear, hierarchical keys.
  2. Run AI Script: Execute your translation script to populate all other language files with AI-drafted translations.
  3. Review & Refine: Have a native speaker or professional translator review the AI-generated .arb files. They can edit the files directly—the structure is simple JSON.
  4. Generate: Run flutter gen-l10n to update your Dart bindings.

This approach tackles the major pain points head-on. It provides scalable key management, eliminates manual copy-pasting between files, and uses AI as a powerful first draft engine, saving immense time and cost.

This blog is produced with the assistance of AI by a human editor. Learn more

Related Posts

Cover image for Flutter App Size Optimization: Strategies to Shrink Your APK/IPA Below 10MB

Flutter App Size Optimization: Strategies to Shrink Your APK/IPA Below 10MB

Flutter apps are often criticized for their larger-than-native binary sizes. This post will dive into practical, actionable strategies to significantly reduce your Flutter app's footprint, including build configurations, asset optimization, and dependency management, demonstrating how to achieve app sizes under 10MB.

Cover image for Mastering In-App Subscriptions in Flutter: A Guide to Avoiding Common Pitfalls

Mastering In-App Subscriptions in Flutter: A Guide to Avoiding Common Pitfalls

Implementing in-app subscriptions in Flutter can be surprisingly complex, especially when dealing with cross-platform nuances, receipt validation, and managing different store configurations. This post will delve into the common challenges developers face, compare solutions like RevenueCat with custom implementations, and provide best practices for setting up and maintaining a robust subscription system.

Cover image for Taming Android Emulator RAM: A Developer's Guide to Reducing Memory Usage for Flutter

Taming Android Emulator RAM: A Developer's Guide to Reducing Memory Usage for Flutter

Android emulators are notorious for hogging RAM, often pushing developer machines into swap and slowing down Flutter development. This post will explore the reasons behind high emulator memory consumption and provide practical, actionable strategies and tools (including a custom-built solution) to significantly reduce RAM usage, ensuring a smoother development experience without sacrificing functionality.