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-l10nautomatically - ✅ 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.