← Back to Blog

Flutter Localization: 10 Common Pitfalls and How to Avoid Them

flutterlocalizationbest-practicespitfallsdebugging

Flutter Localization: 10 Common Pitfalls and How to Avoid Them

You've set up Flutter localization, added ARB files, and generated code. But your app still shows untranslated text, crashes on locale changes, or displays broken layouts. Here are the 10 most common localization mistakes developers make—and how to fix them.

1. Hardcoded Strings in Widgets

The Problem

// ❌ Wrong
Text('Welcome to our app');
ElevatedButton(
  onPressed: () {},
  child: Text('Sign In'),
);

Hardcoded strings won't get translated and are easy to miss during code review.

The Solution

// ✅ Correct
Text(AppLocalizations.of(context)!.welcomeMessage);
ElevatedButton(
  onPressed: () {},
  child: Text(AppLocalizations.of(context)!.signInButton),
);

Use the grep command to find hardcoded strings:

# Find potential hardcoded strings
grep -r "Text('" lib/ --exclude-dir=gen_l10n

2. Not Handling Null Localization

The Problem

// ❌ Crashes if context doesn't have localization
final l10n = AppLocalizations.of(context);
Text(l10n.title); // Null pointer exception

The Solution

// ✅ Always use null-aware operators
final l10n = AppLocalizations.of(context)!;
Text(l10n.title);

// Or provide fallback
final l10n = AppLocalizations.of(context);
Text(l10n?.title ?? 'Default Title');

3. Forgetting to Add Localization Delegates

The Problem

// ❌ Missing delegates
MaterialApp(
  home: MyHomePage(),
);

Without delegates, localization won't work at all.

The Solution

// ✅ Include all required delegates
MaterialApp(
  localizationsDelegates: AppLocalizations.localizationsDelegates,
  supportedLocales: AppLocalizations.supportedLocales,
  home: MyHomePage(),
);

4. String Concatenation Instead of Placeholders

The Problem

// ❌ Wrong - doesn't work for all languages
Text('Hello ' + userName + '!');
Text('You have ' + itemCount.toString() + ' items');

Different languages have different word orders. This approach breaks in many languages.

The Solution

{
  "greeting": "Hello {name}!",
  "@greeting": {
    "placeholders": {
      "name": {"type": "String"}
    }
  },
  "itemCount": "You have {count} items",
  "@itemCount": {
    "placeholders": {
      "count": {"type": "int"}
    }
  }
}
// ✅ Correct
Text(l10n.greeting(userName));
Text(l10n.itemCount(itemCount));

5. Not Testing RTL Languages

The Problem

Your app looks perfect in English but breaks completely in Arabic or Hebrew because elements don't flip correctly.

The Solution

Always test with RTL locales:

// Force RTL testing
MaterialApp(
  locale: Locale('ar'), // Arabic
  localizationsDelegates: AppLocalizations.localizationsDelegates,
  supportedLocales: AppLocalizations.supportedLocales,
  home: MyHomePage(),
);

Use Directionality widget when needed:

Directionality(
  textDirection: TextDirection.rtl,
  child: MyWidget(),
);

6. Missing Translations in ARB Files

The Problem

You add a new key to app_en.arb but forget to add it to other locales. Build fails or shows missing translation errors.

The Solution

Create a validation script:

#!/bin/bash
# validate_translations.sh

template_keys=$(jq -r 'keys[]' assets/l10n/app_en.arb | grep -v "^@")

for file in assets/l10n/app_*.arb; do
    locale=$(basename "$file" .arb | sed 's/app_//')
    if [ "$locale" = "en" ]; then continue; fi

    locale_keys=$(jq -r 'keys[]' "$file" | grep -v "^@")
    missing=$(comm -23 <(echo "$template_keys" | sort) <(echo "$locale_keys" | sort))

    if [ -n "$missing" ]; then
        echo "❌ Missing in $locale: $missing"
        exit 1
    fi
done

echo "✅ All translations complete"

Run before every commit:

chmod +x validate_translations.sh
./validate_translations.sh

7. Using String Equality for Logic

The Problem

// ❌ Wrong - breaks when translations change
if (buttonText == 'Submit') {
  // do something
}

The Solution

// ✅ Use enums or constants
enum ButtonAction { submit, cancel, save }

if (buttonAction == ButtonAction.submit) {
  // do something
}

Translations are for display only, never for business logic.

8. Not Handling Date and Number Formatting

The Problem

// ❌ Wrong - always shows US format
Text('${DateTime.now()}'); // 2025-12-01 16:30:00.000
Text('${price}'); // 1234.56

The Solution

import 'package:intl/intl.dart';

// ✅ Locale-aware formatting
final locale = Localizations.localeOf(context);

// Date formatting
Text(DateFormat.yMMMd(locale.toString()).format(DateTime.now()));
// US: Dec 1, 2025
// FR: 1 déc. 2025
// JP: 2025年12月1日

// Number formatting
Text(NumberFormat.currency(
  locale: locale.toString(),
  symbol: '\$',
).format(price));
// US: $1,234.56
// FR: 1 234,56 $
// DE: 1.234,56 $

9. Rebuilding Entire App on Locale Change

The Problem

// ❌ Inefficient - rebuilds everything
setState(() {
  _locale = newLocale;
});

The Solution

Use proper locale management:

class MyApp extends StatefulWidget {
  static void setLocale(BuildContext context, Locale newLocale) {
    _MyAppState? state = context.findAncestorStateOfType<_MyAppState>();
    state?.setLocale(newLocale);
  }

  @override
  _MyAppState createState() => _MyAppState();
}

class _MyAppState extends State<MyApp> {
  Locale? _locale;

  setLocale(Locale locale) {
    setState(() {
      _locale = locale;
    });
  }

  @override
  Widget build(BuildContext context) {
    return MaterialApp(
      locale: _locale,
      localizationsDelegates: AppLocalizations.localizationsDelegates,
      supportedLocales: AppLocalizations.supportedLocales,
      home: MyHomePage(),
    );
  }
}

// Usage
MyApp.setLocale(context, Locale('es'));

10. Not Running flutter gen-l10n After Changes

The Problem

You update ARB files but forget to regenerate the Dart code. Your changes don't appear in the app.

The Solution

Add it to your development workflow:

# Run after every ARB change
flutter gen-l10n

# Or watch for changes (using entr or similar)
ls assets/l10n/*.arb | entr flutter gen-l10n

Add a pre-commit hook:

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

if git diff --cached --name-only | grep -q "\.arb$"; then
    echo "🔄 Regenerating localizations..."
    flutter gen-l10n
    git add .dart_tool/flutter_gen/gen_l10n/
fi

Bonus: Testing Checklist

Before releasing, verify:

  • All strings use AppLocalizations, no hardcoded text
  • All ARB files have matching keys
  • App tested in at least one RTL language
  • Date/number formatting uses locale-aware methods
  • Plural forms tested with 0, 1, 2, few, many
  • Long German text doesn't overflow
  • Screenshots taken for all supported locales
  • Locale switching works without crashes

Automated Detection

Use linting rules to catch issues early:

# analysis_options.yaml
linter:
  rules:
    - prefer_single_quotes
    - avoid_print

analyzer:
  errors:
    todo: ignore
  exclude:
    - '**/*.g.dart'
    - '**/*.gen.dart'
    - '.dart_tool/flutter_gen/**'

FlutterLocalisation Safety Features

FlutterLocalisation automatically prevents these pitfalls:

  • Missing key detection: Warns when keys don't match across locales
  • Placeholder validation: Ensures placeholders are used correctly
  • RTL preview: Visual preview of RTL layouts
  • Auto-generation: Runs flutter gen-l10n automatically
  • Translation memory: Prevents duplicate translations
  • Context hints: AI understands context to avoid wrong translations

Conclusion

Most localization bugs stem from treating translations as an afterthought. By following these patterns and using proper tooling, you can catch issues during development instead of in production.

The key is systematic testing, proper tooling, and treating localization as a first-class concern from day one. Don't wait for user reports—build it right the first time.


Prevent localization pitfalls automatically with FlutterLocalisation. Get real-time validation, automated testing, and AI-powered translation quality checks.