Fix Flutter Dates Rendering in English for Every Locale
You added a {date} placeholder to your ARB files, ran flutter gen-l10n, and it compiled without a single warning. But no matter what locale the app runs in, every date comes out looking American: July 29, 2026 in German, 7/29/2026 in Arabic. The month names are English, the digits are Western, the field order is M/d/y. Your MaterialApp locale is clearly set to de or ar — so why does flutter DateFormat ignore the locale?
This is one of the most reported and least obvious papercuts in Flutter i18n. There are actually two distinct root causes behind the same symptom, and the fix is different for each. This post pins both, shows the exact ARB placeholder syntax, and proves the fix with a German/Arabic before-and-after.
The symptom, precisely
Here's a typical {date} placeholder in your template app_en.arb:
{
"@@locale": "en",
"lastSync": "Last sync: {date}",
"@lastSync": {
"placeholders": {
"date": {
"type": "DateTime",
"format": "yMMMMd"
}
}
}
}
And the German app_de.arb only translates the surrounding text — the placeholder metadata lives only in the template:
{
"@@locale": "de",
"lastSync": "Zuletzt synchronisiert: {date}"
}
gen-l10n turns format: "yMMMMd" into a named DateFormat skeleton constructor. The generated method looks roughly like this:
String lastSync(DateTime date) {
final intl.DateFormat dateDateFormat =
intl.DateFormat.yMMMMd(localeName);
final String dateString = dateDateFormat.format(date);
return 'Zuletzt synchronisiert: $dateString';
}
Notice localeName is passed. So the locale is not being dropped by your code. The problem is upstream of format().
Cause #1: locale date-symbol data was never loaded
The intl package's DateFormat needs each locale's date symbols (month names, weekday names, field order, number system) loaded before it can format anything but English. Those symbols do not ship pre-loaded. Until you initialize them, DateFormat.yMMMMd('de') silently falls back to en_US data — the exact "flutter localized date shows english" behavior you're seeing. In some code paths it throws LocaleDataException: Locale data has not been initialized, call initializeDateFormatting(<locale>) instead — same underlying gap.
The fix is one call in main(), before runApp:
import 'package:flutter/material.dart';
import 'package:flutter_localizations/flutter_localizations.dart';
import 'package:intl/date_symbol_data_local.dart';
import 'gen_l10n/app_localizations.dart';
Future<void> main() async {
WidgetsFlutterBinding.ensureInitialized();
await initializeDateFormatting(); // load symbols for ALL locales
runApp(const MyApp());
}
Calling initializeDateFormatting() with no arguments loads the symbol data for every locale intl knows about. That is the safest option, and it sidesteps the second trap below. It returns a Future, so main must be async and you must await it after WidgetsFlutterBinding.ensureInitialized().
Make sure flutter_localizations is wired into your app too, so the framework's own delegates agree on the active locale:
MaterialApp(
localizationsDelegates: AppLocalizations.localizationsDelegates,
supportedLocales: AppLocalizations.supportedLocales,
// localizationsDelegates already includes GlobalMaterialLocalizations
// when you generate them via AppLocalizations.
);
The consistent-locale-code trap
A subtle variant: some developers try to be economical and initialize only the locales they ship:
await initializeDateFormatting('de'); // only German
But the localeName that gen-l10n passes into DateFormat is derived from the active Locale, which may be the fully-qualified de_DE, not de. If you initialized 'de' but the app resolves to de_DE (or vice-versa), the lookup misses and you're right back to en_US. If you must scope initialization, use the exact same locale codes your supportedLocales produce — or just call initializeDateFormatting() with no arguments and stop guessing.
Cause #2: the gen-l10n custom-format codegen bug
The second cause bites specifically when you use a custom pattern string with isCustomDateFormat, expecting each locale to override the format:
"@lastSync": {
"placeholders": {
"date": {
"type": "DateTime",
"format": "dd.MM.yyyy",
"isCustomDateFormat": "true"
}
}
}
Older Flutter tooling had a real bug here: gen-l10n baked the template ARB's format string (and number format) into the generated code for every locale, ignoring per-locale format: overrides. So even a German ARB asking for dd.MM.yyyy would emit code using the English template's MM/dd/yyyy — dates in "English" order everywhere. This is tracked in flutter/flutter #116716 ("Custom date formats always use english locale"), #153457 ("date formats and number formats always use template arb"), and #162246 ("Wrong dateformat generated for languages other than english locale"), which is now marked fixed.
The fix for this one is not in your main() — it's tooling. Upgrade to a current stable Flutter that includes the fix, then re-run flutter gen-l10n and diff the generated app_localizations_de.dart to confirm the per-locale pattern actually appears. Check your version:
flutter --version
flutter gen-l10n # regenerate after upgrading
If you can't upgrade yet, prefer named skeletons (yMMMMd, yMd, Hms) over isCustomDateFormat custom patterns. Skeletons are inherently locale-aware — DateFormat.yMMMMd('de') renders 29. Juli 2026 on its own — so you avoid the buggy custom-format path entirely, and you get correct field order for free instead of hard-coding one country's convention.
Before and after: German and Arabic
With no initializeDateFormatting() call (Cause #1), every locale collapses to English:
en → Last sync: July 29, 2026
de → Zuletzt synchronisiert: July 29, 2026 ❌ English month
ar → آخر مزامنة: July 29, 2026 ❌ English + Western digits
After adding await initializeDateFormatting(); in main() and using the yMMMMd skeleton:
en → Last sync: July 29, 2026
de → Zuletzt synchronisiert: 29. Juli 2026 ✅ German order + month
ar → آخر مزامنة: ٢٩ يوليو ٢٠٢٦ ✅ Arabic month + Eastern digits
Arabic is the acid test: correct output means real Arabic month names and Eastern-Arabic numerals (٢٩ not 29). If you see Western digits, your symbol data still isn't loaded for ar — recheck the locale code you passed to initializeDateFormatting.
A 60-second checklist
main()isasync, callsWidgetsFlutterBinding.ensureInitialized(), thenawait initializeDateFormatting()beforerunApp.- You imported
package:intl/date_symbol_data_local.dart(notdate_symbol_data_custom.dart). - The locale code you initialize matches what
supportedLocalesresolves to — or you initialize all locales with no argument. localizationsDelegatesincludes the generated delegates plusGlobalMaterialLocalizations.delegate.- You're on a Flutter stable that includes the #162246 fix if you rely on
isCustomDateFormat. - You re-ran
flutter gen-l10nafter any ARB or SDK change.
Keep your ARB placeholders honest
Most "date shows English" bugs are really "my ARB metadata drifted between locales" bugs — a placeholder defined in the template but silently missing or malformed elsewhere. Editing raw ARB JSON by hand makes that drift invisible until runtime. FlutterLocalisation's ARB editor gives you a UI over your app_<locale>.arb files so placeholders and translations stay aligned across every language, and its ICU plural-syntax validation flags locales missing a plural category the language actually needs (like a dropped few/many for Arabic, Polish, or Russian) — the same class of cross-locale mistake that produces mysterious formatting bugs.
Manage all your locales in one place, from the free tier up through the paid plans. Try FlutterLocalisation free and stop shipping English dates to non-English users.