Loading...
Loading...
Enforces the gen-l10n/ARB localization contract: every string through AppLocalizations from a template app_en.arb, key + placeholder parity, nullable-getter:false so a missing key is a compile error, ICU plural/select over concatenation, Directional-only geometry (EdgeInsetsDirectional, AlignmentDirectional, TextAlign.start, Icons.adaptive), FSI/PDI isolation for mixed-script runs, per-locale NumberFormat with a pinned numbering system, canonical UTC-epoch + ASCII storage projected only at render, normalize-to-ASCII before any parse, and three vendored LocalizationsDelegates ahead of the Global* ones for a locale flutter_localizations lacks. Use when adding or translating an ARB key, building an RTL screen, formatting or parsing dates/numbers/numerals, wiring l10n.yaml, AppLocalizations, or MaterialApp localizationsDelegates, supporting a locale Flutter lacks (ckb), fixing a locale that crashes on Tooltip or renders LTR, checking bundled-font glyph coverage, isolating technical IDs, or touching app_*.arb.
npx skill4agent add zakariaf/flutter-skills i18n-rtl-l10nl10nreferences/arb-and-icu.mdl10n.yamlreferences/rtl-and-bidi.mdreferences/numerals-and-calendars.mdreferences/unsupported-locales.mdflutter_localizationsscripts/check_arb_parity.shscripts/check_i18n_bans.shapp_en.arbAppLocalizations.of(context)nullable-getter: falsel10n.yamlTexttooltip:label:hint:semanticsLabel:AppLocalizations.ofLocalizationsmain()runApp{placeholder}check_arb_parity.sh{count, plural, ...}{x, select, ...}localizationsDelegatessupportedLocalesMaterialAppDirectionality(TextDirection.rtl)GlobalWidgetsLocalizationsflutter_localizationsDirectionality.ofEdgeInsetsDirectionalAlignmentDirectionalPositionedDirectionalTextAlign.start/endMainAxisAlignment.start/endBorderRadiusDirectionalIcons.adaptive.*EdgeInsets.only(left/right)Alignment.centerLeft/RightPositioned(left/right:)TextAlign.left/rightIcons.arrow_backdateTime.toUtc().millisecondsSinceEpochDateTimedouble.parse(normalizeToAscii(text))int.parse(text)۱۲۳١٢٣1٫5U+2066U+2069fontFamilyfontFamilyFallbackgoogle_fontscmapflutter_localizationsTextDirectionMaterialLocalizationsCupertinoLocalizationsWidgetsLocalizationsGlobal*AppLocalizations.localizationsDelegatesTooltipTextDirection.ltrreferences/unsupported-locales.md@keyplaceholdersdescriptionapp_en.arbscripts/check_arb_parity.shflutter gen-l10nflutter analyze// app_en.arb (template) — declare the key + @metadata here first.
{
"@@locale": "en",
"itemsRemaining": "{count, plural, =0{No items left} =1{1 item left} other{{count} items left}}",
"@itemsRemaining": {
"description": "Count shown on the order summary card.",
"placeholders": { "count": { "type": "int" } }
}
}// app_ar.arb — same key; Arabic's SIX CLDR categories are a translation contract.
{
"@@locale": "ar",
"itemsRemaining": "{count, plural, zero{لا عناصر متبقية} one{عنصر واحد متبقٍ} two{عنصران متبقيان} few{{count} عناصر متبقية} many{{count} عنصرًا متبقيًا} other{{count} عنصر متبقٍ}}"
}// Reads correctly under LTR and RTL with zero conditionals.
Padding(
padding: const EdgeInsetsDirectional.only(start: 16, end: 8), // never left/right
child: Row(
children: [
Expanded(child: Text(task.title, textAlign: TextAlign.start)), // never .left
IconButton(
icon: Icon(Icons.adaptive.arrow_forward), // auto-mirrors in RTL
onPressed: onNext,
),
],
),
)Icons.adaptive.*references/rtl-and-bidi.mdNumberFormatintl-u-nu--uintlckbckbfa// One formatter per locale. Persian and Arabic digits are DISTINCT Unicode blocks
// (۴۵۶ U+06Fx vs ٤٥٦ U+066x) — not interchangeable. Format with a locale intl ships
// symbols for; a `-u-nu-` extension is dropped, not honored.
NumberFormat numberFormatFor(Locale locale) => switch (locale.languageCode) {
'fa' => NumberFormat.decimalPattern('fa'), // ۰۱۲۳۴۵۶۷۸۹ (arabext)
// ckb has no intl number symbols — borrow fa (same arabext block + separators).
'ckb' => NumberFormat.decimalPattern('fa'),
'ar' => NumberFormat.decimalPattern('ar'), // ٠١٢٣٤٥٦٧٨٩ (arab)
_ => NumberFormat.decimalPattern('en'), // Western (en, de, fr)
};String normalizeToAscii(String input) {
final sb = StringBuffer();
for (final r in input.runes) {
if (r >= 0x0660 && r <= 0x0669) { // Eastern-Arabic ٠-٩
sb.writeCharCode(0x30 + (r - 0x0660));
} else if (r >= 0x06F0 && r <= 0x06F9) { // Persian ۰-۹
sb.writeCharCode(0x30 + (r - 0x06F0));
} else if (r == 0x066B) { // ٫ decimal separator -> '.'
sb.write('.');
} else if (r == 0x066C) { // ٬ grouping separator -> drop
continue;
} else {
sb.writeCharCode(r);
}
}
return sb.toString();
}
// double.parse(normalizeToAscii(text)) — never int.parse on raw input.1٫5references/numerals-and-calendars.mdconst _lri = ''; // LEFT-TO-RIGHT ISOLATE
const _rli = ''; // RIGHT-TO-LEFT ISOLATE
const _fsi = ''; // FIRST STRONG ISOLATE (unknown direction)
const _pdi = ''; // POP DIRECTIONAL ISOLATE
String isolate(String run) => '$_fsi$run$_pdi'; // unknown direction
String isolateLtr(String run) => '$_lri$run$_pdi'; // known LTR (codes, numbers)
String isolateRtl(String run) => '$_rli$run$_pdi'; // known RTL
// Prefer known-direction over FSI: first-strong mis-guesses on leading punctuation.
// The isolated value is an ARB placeholder, never a hard-spliced substring:
Text(l10n.orderRef(isolateLtr(order.reference))); // "{ref} — order" reads correctly in RTLText(code, textDirection: TextDirection.ltr, textAlign: TextAlign.end)LRE/RLE/LRO/RLOintlshamsi_datehijriexamples/numeral_normalizer.dartreferences/numerals-and-calendars.mdDirectionality(textDirection: TextDirection.ltr, …)localPositiondxnumberFormatFor(locale)custom-canvas-and-gesturesText('Order details')l10n.orderDetailscheck_i18n_bans.shennullable-getter: falsecount == 1 ? 'item' : 'items'fewmanypluralText('$count items')'Total ' + n.toString()numberFormatFor٤٥٦۴۵۶faarEdgeInsets.only(left: 16)Alignment.centerLeftPositioned(left:)Directionality(TextDirection.rtl)[...AppLocalizations.localizationsDelegates, GlobalMaterialLocalizations.delegate, …]Global*Localizations._loadAllMaterialLocalizationsWidgetsLocalizationscmapU+2066U+2069Ahemwidget-golden-and-a11y-testingint.parse(userText)app_en.arb@descriptioncheck_arb_parity.shnullable-getter: falseAppLocalizationsTexttooltiplabelhintsemanticsLabelplural$countnumberFormatFor(locale)intlDateFormatnormalizeToAsciistartendIcons.adaptiveDirectionalityLRE/RLEcmapflutter_localizationsGlobal*Directionality.ofMaterialLocalizationsCFBundleLocalizationsaccessibility-as-codeSemanticsMediaQuerywidget-golden-and-a11y-testingcustom-canvas-and-gesturesvalue-objects-money-and-unitslocal-notifications-schedulerwidget-compositionscaffold-feature-moduledesign-system-structurereferences/typography-and-fonts.mdapp-startup-and-bootstrapl10n.yamlMessageFormatintlDirectionalityEdgeInsetsDirectionalLocalizationsDelegatecmap