Fix Flutter Custom Date Formats Stuck in English
You set isCustomDateFormat: true in your ARB file, ran flutter gen-l10n, and your dates look formatted — but every month and weekday name still prints in English. Montag shows up as Monday, mars as March, and Arabic dates render Latin month names. Switching the app locale changes nothing.
This is not your bug. It is a well-documented gen-l10n behavior tracked in flutter/flutter #116716 ("Custom date formats always use english locale") and #162246 ("Wrong dateformat generated for languages other than english locale"). Below is exactly what's happening and three copy-paste fixes that respect the active language.
What actually goes wrong
gen-l10n handles a DateTime placeholder in two very different ways depending on one flag.
Without isCustomDateFormat, the format value must be a named skeleton like yMMMMEEEEd. gen-l10n emits a named constructor and passes the locale:
// Generated when format is a skeleton — locale-aware, correct.
final intl.DateFormat springStartDateDateFormat =
intl.DateFormat.yMMMMEEEEd(localeName);
Because DateFormat.yMMMMEEEEd('de') pulls month and weekday symbols from the de locale data, you get Montag, 3. März 2026. This path works.
With isCustomDateFormat: "true", the format value is treated as a raw ICU pattern string (EEEE, d MMMM). The catch: gen-l10n reads that custom pattern from the template ARB (app_en.arb) for every generated locale, and historically did not forward the locale into the constructor. Any per-locale format override you put in app_de.arb is ignored, and the textual parts fall back to English:
// app_en.arb (template)
{
"today": "{date}",
"@today": {
"placeholders": {
"date": {
"type": "DateTime",
"format": "EEEE, d MMMM",
"isCustomDateFormat": "true"
}
}
}
}
// app_de.arb — this override is silently dropped
{
"today": "{date}",
"@today": {
"placeholders": {
"date": { "type": "DateTime", "format": "EEEE, d. MMMM", "isCustomDateFormat": "true" }
}
}
}
Result in a German build: Monday, 3 March instead of Montag, 3. März. That is the core of flutter custom date format wrong locale and gen-l10n DateFormat english only — the custom pattern bypasses the active locale.
First gotcha:
isCustomDateFormatmust be the string"true", not a JSON booleantrue(see #153420). A boolean is treated as false and the tool tries to parse your pattern as a skeleton, throwing a build error.
Fix 1 — Use a standard skeleton instead of a custom pattern
If your format maps to a built-in skeleton, drop isCustomDateFormat entirely. Skeletons are inherently locale-aware and reorder fields per locale automatically.
// app_en.arb — no isCustomDateFormat, just a skeleton
{
"today": "{date}",
"@today": {
"placeholders": {
"date": {
"type": "DateTime",
"format": "yMMMMEEEEd"
}
}
}
}
Usage is unchanged:
Text(AppLocalizations.of(context)!.today(DateTime.now()));
// en: Friday, July 3, 2026
// de: Freitag, 3. Juli 2026
// ar: الجمعة، ٣ يوليو ٢٠٢٦
Common skeletons: yMd (7/3/2026 → 03.07.2026), yMMMd (Jul 3, 2026), yMMMMd, yMMMMEEEEd, jm (time), Hm. A skeleton adapts field order and separators per locale — a custom pattern never will. Prefer skeletons whenever one fits.
Fix 2 — A locale-aware DateFormat wrapper in Dart
When you genuinely need a pattern no skeleton covers (say EEEE 'week' w), stop expressing it in ARB and format it in Dart, passing the active locale explicitly. intl's DateFormat fully localizes textual parts when you give it the locale string as the second argument.
import 'package:flutter/widgets.dart';
import 'package:intl/intl.dart';
extension LocalizedDates on BuildContext {
/// Formats [date] with a custom ICU [pattern] in the ACTIVE locale.
String formatDate(DateTime date, String pattern) {
final locale = Localizations.localeOf(this).toString(); // e.g. 'de', 'ar', 'fr_CA'
return DateFormat(pattern, locale).format(date);
}
}
// In a widget:
Text(context.formatDate(DateTime.now(), "EEEE, d MMMM"));
// de -> Freitag, 3 Juli
// ar -> الجمعة، ٣ يوليو
// fr -> vendredi, 3 juillet
The whole fix is the second argument to DateFormat. DateFormat('EEEE, d MMMM') defaults to the ambient locale (often en_US); DateFormat('EEEE, d MMMM', 'de') reads German symbols. Because flutter_localizations already initializes date symbol data for every locale in supportedLocales, no manual initializeDateFormatting() call is needed for those.
This wrapper is the most reliable answer to flutter arb DateTime placeholder localize and flutter intl date format not translating: it is version-independent and sidesteps the gen-l10n code path entirely.
Fix 3 — Per-locale patterns without touching gen-l10n
If different languages need genuinely different patterns (not just translated names), keep the pattern out of the ARB placeholder and put a plain string key per locale instead:
// app_en.arb
{ "datePattern": "EEEE, MMMM d" }
// app_de.arb
{ "datePattern": "EEEE, d. MMMM" }
Then combine it with the Fix 2 wrapper:
final l10n = AppLocalizations.of(context)!;
final text = context.formatDate(DateTime.now(), l10n.datePattern);
// Each locale supplies its own pattern AND its own localized names.
This gives you true per-locale control that the buggy isCustomDateFormat override was supposed to provide — but reliably, today, on any Flutter version.
Should you just upgrade Flutter?
These issues have seen fixes land on newer channels, so run flutter --version and test after upgrading — a recent stable may already forward the locale into custom DateFormat constructors. But even on the latest SDK, skeletons remain the cleanest option, and the Dart wrapper is the only approach guaranteed to behave identically across your whole team's toolchains and CI. Treat the upgrade as a bonus, not the strategy.
Quick checklist
- Prefer a skeleton (
yMMMMEEEEd) over a custom pattern whenever possible. - Need a custom ICU pattern? Format in Dart with
DateFormat(pattern, localeName)— never omit the locale. - Vary patterns per language with a plain ARB string key, not a placeholder
formatoverride. - If you keep
isCustomDateFormat, write it as the string"true". - Confirm every locale you use is listed in
supportedLocalesso symbol data loads.
Managing these ARB placeholders and per-locale overrides by hand is where subtle bugs like this hide. FlutterLocalisation's visual ARB editor surfaces placeholder types, formats, and locale gaps so a stray isCustomDateFormat never ships silently — and if you want the full picture on locale-aware dates, see our guide to Flutter DateTime localization.
Try FlutterLocalisation free and keep your dates speaking every language your users do.