modular_l10n 4.0.0 copy "modular_l10n: ^4.0.0" to clipboard
modular_l10n: ^4.0.0 copied to clipboard

Lightweight locale utilities for Flutter with RTL detection, locale parsing, display names for 70+ languages. Works standalone or with Modular Flutter L10n extension.

modular_l10n #

Pub Version Pub Points Popularity License

Comprehensive locale utilities for Flutter — 177 languages, RTL detection, plural rules, calendar metadata, and more. Zero dependencies.


Use This Package Standalone or With the Extension #

This package provides valuable locale utilities for ANY Flutter app — no extension required!

Works Standalone (Without Extension) #

Feature Description
177 Languages English + native display names for all ISO 639-1 languages
RTL Detection 16 RTL languages including Arabic, Hebrew, Urdu, Persian, and more
Locale Parsing Parse en_US, zh_Hans_CN, sr_Latn_RS, and more
Best Match Algorithm Find closest supported locale from device locale, with fallback chains
Country Names ISO 3166-1 country code to display name (~150 countries)
Plural Rules CLDR plural categories for major language groups
Calendar Metadata First day of week, default calendar system, date order
Number Formats Decimal/grouping separators and numeral system per locale
Writing System Info Logographic, abugida, word spaces, uppercase support
Validation Validate language and country codes; normalize deprecated codes
Zero Dependencies No external packages — pure Dart + Flutter SDK
// Use with any localization solution
if (locale.isRtl) { /* Handle RTL layout */ }

print(locale.nativeName);             // "العربية"
print(locale.fullDisplayName);        // "Arabic (Egypt)"
print(locale.textExpansionFactor);    // 1.25

LocaleUtils.getPluralCategory(locale, 3); // PluralCategory.few  (Arabic)
LocaleUtils.getFirstDayOfWeek(locale);    // 6 (Saturday, Saudi Arabia)

Extra Power With the VS Code Extension #

Pair with the Modular Flutter Localization Extension for a complete localization solution:

Extension Feature What You Get
Code Generation Generates type-safe ML class from ARB files
Modular Organization Organize translations by feature/module
Auto-Regeneration Watch mode — changes to ARB files instantly update code
ICU Message Support Plurals, select, gender, and complex messages
Extract to ARB Right-click strings in code to extract them
Multi-locale Management Easy commands to add/remove locales
// With extension — type-safe translations
Text(ML.of(context).auth.emailLabel)

// With extension + this package — RTL + metadata
Directionality(
  textDirection: Localizations.localeOf(context).textDirection,
  child: child,
)

Installation #

dependencies:
  modular_l10n: ^4.0.0
flutter pub get

Quick Start #

import 'package:modular_l10n/modular_l10n.dart';

// RTL detection
if (LocaleUtils.isRtl(const Locale('ar'))) { /* ... */ }
if (locale.isRtl) { /* ... */ }

// Parse locale strings
final locale = LocaleUtils.parseLocale('zh_Hans_CN');
// → Locale.fromSubtags(languageCode: 'zh', scriptCode: 'Hans', countryCode: 'CN')

// Normalize deprecated codes
LocaleUtils.normalizeLocale('iw');  // → Locale('he')  (Hebrew)
LocaleUtils.normalizeLocale('in');  // → Locale('id')  (Indonesian)

// Display names
print(const Locale('ar').displayName);        // "Arabic"
print(const Locale('ar').nativeName);         // "العربية"
print(const Locale('ar', 'EG').fullDisplayName); // "Arabic (Egypt)"

// Find best match for device locale
final bestMatch = LocaleUtils.findBestMatch(deviceLocale, supportedLocales);

// With intelligent fallback chain (e.g. nb → no → da)
final bestMatch = LocaleUtils.findBestMatch(
  deviceLocale,
  supportedLocales,
  useFallbackChain: true,
);

RTL Support #

Supported RTL Languages #

Code Language Code Language
ar Arabic ug Uyghur
fa Persian/Farsi dv Divehi
he Hebrew ks Kashmiri
ur Urdu yi Yiddish
ps Pashto ckb Central Kurdish (Sorani)
sd Sindhi syr Syriac
arc Aramaic khw Khowar
bal Balochi nqo N'Ko

ku is deliberately absent. Bare Kurdish defaults to Kurmanji, written in Latin script left-to-right; only Sorani (ckb) uses the Arabic script. Use ckb explicitly for RTL Kurdish.

The three-letter entries only match when a Locale was built with that exact code — Flutter normalizes to the shortest available subtag — so they exist for languages with no two-letter ISO 639-1 code.

// Static method
if (LocaleUtils.isRtl(locale)) { /* ... */ }

// Extension
if (locale.isRtl) { /* ... */ }

// In widgets
Directionality(
  textDirection: locale.textDirection,
  child: child,
)

Display Names #

177 languages with both English and native names:

const Locale('ar').displayName  // "Arabic"
const Locale('ar').nativeName   // "العربية"
const Locale('zh').displayName  // "Chinese"
const Locale('zh').nativeName   // "中文"
const Locale('hi').displayName  // "Hindi"
const Locale('hi').nativeName   // "हिन्दी"

Full Display Name with Region #

LocaleUtils.getFullDisplayName(const Locale('ar', 'EG'));
// → "Arabic (Egypt)"

LocaleUtils.getFullNativeDisplayName(const Locale('ar', 'EG'));
// → "العربية (مصر)"

LocaleUtils.getFullDisplayName(
  const Locale.fromSubtags(languageCode: 'zh', scriptCode: 'Hans', countryCode: 'CN'),
);
// → "Chinese (China)"

Country Names #

LocaleUtils.getCountryName('US');  // "United States"
LocaleUtils.getCountryName('EG');  // "Egypt"
LocaleUtils.getCountryName('SA');  // "Saudi Arabia"

const Locale('ar', 'EG').countryDisplayName;  // "Egypt"

Locale Parsing & Matching #

Parse Locale Strings #

LocaleUtils.parseLocale('en');          // Locale('en')
LocaleUtils.parseLocale('en_US');       // Locale('en', 'US')
LocaleUtils.parseLocale('en-US');       // Locale('en', 'US')
LocaleUtils.parseLocale('zh_Hans');     // Locale.fromSubtags(languageCode: 'zh', scriptCode: 'Hans')
LocaleUtils.parseLocale('zh_Hans_CN');  // Locale.fromSubtags(languageCode: 'zh', scriptCode: 'Hans', countryCode: 'CN')
LocaleUtils.parseLocale('sr_Latn_RS');  // Locale.fromSubtags(languageCode: 'sr', scriptCode: 'Latn', countryCode: 'RS')
LocaleUtils.parseLocale('es_419');      // Locale('es', '419')  — UN M.49 region

Parsing never throws. A subtag is only read as a script when it matches a known script code, and anything unparseable degrades rather than producing a Locale with an empty subtag (which asserts in debug builds):

LocaleUtils.parseLocale('en_US_POSIX'); // Locale('en', 'US')  — variant dropped
LocaleUtils.parseLocale('en__US');      // Locale('en', 'US')  — empty segment ignored
LocaleUtils.parseLocale('en_Zzzz');     // Locale('en')        — unknown script ignored
LocaleUtils.parseLocale('');            // Locale('und')       — undetermined

Find Best Matching Locale #

Priority: exact match → language + country → language only.

final supported = [Locale('en'), Locale('en', 'GB'), Locale('ar'), Locale('de')];

LocaleUtils.findBestMatch(Locale('en', 'GB'), supported);  // Locale('en', 'GB')
LocaleUtils.findBestMatch(Locale('ar', 'SA'), supported);  // Locale('ar')
LocaleUtils.findBestMatch(Locale('fr'), supported);         // null

Fallback Chains #

When useFallbackChain: true, falls back through related/intelligible languages:

LocaleUtils.findBestMatch(
  const Locale('nb'),  // Norwegian Bokmål
  [Locale('no'), Locale('da'), Locale('en')],
  useFallbackChain: true,
);
// → Locale('no')  — Norwegian is a valid fallback for Bokmål

// Built-in chains:
// nb → no → nn → da → sv
// hr → sr → bs → sl
// ms → id
LocaleUtils.getRelatedLocales(const Locale('hr'));
// → [Locale('sr'), Locale('bs'), Locale('sl')]

Validation & Normalization #

LocaleUtils.isValidLanguageCode('en');   // true
LocaleUtils.isValidLanguageCode('xyz');  // false
LocaleUtils.isValidCountryCode('US');    // true
LocaleUtils.isValidCountryCode('XX');    // false
LocaleUtils.isValidLocale(const Locale('en', 'US'));  // true

// Deprecated code mapping
LocaleUtils.normalizeLocale('iw');  // Locale('he')  — Hebrew
LocaleUtils.normalizeLocale('in');  // Locale('id')  — Indonesian
LocaleUtils.normalizeLocale('jw');  // Locale('jv')  — Javanese
LocaleUtils.normalizeLocale('ji');  // Locale('yi')  — Yiddish
LocaleUtils.normalizeLocale('mo');  // Locale('ro')  — Romanian
LocaleUtils.normalizeLocale('sh');  // Locale('sr')  — Serbian
LocaleUtils.normalizeLocale('tl');  // Locale('fil') — Filipino

// Case normalization
LocaleUtils.normalizeLocale('EN_us');       // Locale('en', 'US')
LocaleUtils.normalizeLocale('zh_hans_cn');  // zh_Hans_CN — script codes are title-cased

Plural Rules #

Different languages have different plural forms. This is critical for correct localization.

// Arabic has 6 plural forms: zero, one, two, few, many, other
LocaleUtils.getPluralCategory(const Locale('ar'), 0);   // PluralCategory.zero
LocaleUtils.getPluralCategory(const Locale('ar'), 1);   // PluralCategory.one
LocaleUtils.getPluralCategory(const Locale('ar'), 2);   // PluralCategory.two
LocaleUtils.getPluralCategory(const Locale('ar'), 5);   // PluralCategory.few
LocaleUtils.getPluralCategory(const Locale('ar'), 15);  // PluralCategory.many

// English: one, other
LocaleUtils.getPluralCategory(const Locale('en'), 1);   // PluralCategory.one
LocaleUtils.getPluralCategory(const Locale('en'), 5);   // PluralCategory.other

// Russian: one, few, many
LocaleUtils.getPluralCategory(const Locale('ru'), 1);   // PluralCategory.one
LocaleUtils.getPluralCategory(const Locale('ru'), 3);   // PluralCategory.few
LocaleUtils.getPluralCategory(const Locale('ru'), 11);  // PluralCategory.many

// Chinese, Japanese, Korean: no plural (always other)
LocaleUtils.getPluralCategory(const Locale('zh'), 5);   // PluralCategory.other

// Extension
locale.getPluralCategory(3);  // PluralCategory
Group Languages Forms
noPlural Chinese, Japanese, Korean, Vietnamese, Thai, Indonesian, Malay, … other
germanic English, German, Dutch, Swedish, Turkish, Georgian, Kazakh, Uzbek, … one, other
frenchLike French, Portuguese, Hindi, Bengali, … one, other (0 is singular)
slavicRussian Russian, Ukrainian, Belarusian one, few, many, other
bosnianCroatianSerbian Bosnian, Croatian, Serbian one, few, other
slavicPolish Polish, Czech, Slovak one, few, other
arabic Arabic zero, one, two, few, many, other
irish Irish one, two, few (3–6), many (7–10), other
welsh Welsh zero, one, two, few (3), many (6), other
hebrew Hebrew one, two, other

Categories follow the CLDR plural rules, including the v operand — a value with visible decimal digits takes other in the languages whose rules require it:

LocaleUtils.getPluralCategory(const Locale('ru'), 1);    // one
LocaleUtils.getPluralCategory(const Locale('ru'), 1.5);  // other

Number Formats #

final fmt = LocaleUtils.getNumberFormat(const Locale('ar'));
fmt.decimalSeparator;   // '٫'
fmt.groupingSeparator;  // '٬'
fmt.percentSign;        // '٪'
fmt.numeralSystem;      // NumeralSystem.arabicIndic

final fmt2 = LocaleUtils.getNumberFormat(const Locale('de'));
fmt2.decimalSeparator;  // ','
fmt2.groupingSeparator; // '.'

// Get just the numeral system
LocaleUtils.getNumeralSystem(const Locale('ar'));  // NumeralSystem.arabicIndic
LocaleUtils.getNumeralSystem(const Locale('fa'));  // NumeralSystem.persianUrdu
LocaleUtils.getNumeralSystem(const Locale('th'));  // NumeralSystem.thai

// Extension
locale.numeralSystem;

Calendar & Date Metadata #

// First day of week (1=Monday, 6=Saturday, 7=Sunday)
LocaleUtils.getFirstDayOfWeek(const Locale('en', 'US'));  // 7 (Sunday)
LocaleUtils.getFirstDayOfWeek(const Locale('ar', 'SA'));  // 6 (Saturday)
LocaleUtils.getFirstDayOfWeek(const Locale('de'));         // 1 (Monday)

// Default calendar system
LocaleUtils.getDefaultCalendar(const Locale('ar', 'SA'));  // CalendarSystem.islamic
LocaleUtils.getDefaultCalendar(const Locale('fa'));         // CalendarSystem.persian
LocaleUtils.getDefaultCalendar(const Locale('th'));         // CalendarSystem.buddhist
LocaleUtils.getDefaultCalendar(const Locale('he'));         // CalendarSystem.hebrew

// Date order
LocaleUtils.getDateOrder(const Locale('en', 'US'));  // DateOrder.mdy
LocaleUtils.getDateOrder(const Locale('en', 'GB'));  // DateOrder.dmy
LocaleUtils.getDateOrder(const Locale('ja'));         // DateOrder.ymd

// Extensions
locale.firstDayOfWeek;
locale.calendarSystem;
locale.dateOrder;

Writing System Info #

final ws = LocaleUtils.getWritingSystem(const Locale('zh'));
ws.isLogographic;    // true  — affects word count, text wrapping, search
ws.usesWordSpaces;   // false — Chinese doesn't use spaces between words
ws.hasUpperCase;     // false — no uppercase/lowercase distinction

final wsAr = LocaleUtils.getWritingSystem(const Locale('ar'));
wsAr.isRtl;          // true
wsAr.hasUpperCase;   // false

final wsTh = LocaleUtils.getWritingSystem(const Locale('th'));
wsTh.isAbugida;      // true  — Thai is an abugida script
wsTh.usesWordSpaces; // false

// Text expansion factor (relative to English)
LocaleUtils.getTextExpansionFactor(const Locale('de'));  // ~1.3  (German is ~30% longer)
LocaleUtils.getTextExpansionFactor(const Locale('fi'));  // ~1.4  (Finnish is verbose)
LocaleUtils.getTextExpansionFactor(const Locale('ar'));  // ~1.25
LocaleUtils.getTextExpansionFactor(const Locale('zh'));  // ~0.5  (Chinese is compact)
LocaleUtils.getTextExpansionFactor(const Locale('ja'));  // ~0.6

// Extension
locale.writingSystem;
locale.textExpansionFactor;

Locale Info #

Get all metadata for a locale in one call:

final info = LocaleUtils.getLocaleInfo(const Locale('ar', 'EG'));
print(info.englishName);    // "Arabic"
print(info.nativeName);     // "العربية"
print(info.textDirection);  // TextDirection.rtl
print(info.scriptCode);     // "Arab"
print(info.countryCode);    // "EG"

API Reference #

LocaleUtils — Static Methods #

Method Returns Description
isRtl(locale) bool RTL check
getTextDirection(locale) TextDirection LTR or RTL
parseLocale(string) Locale Parse locale string
normalizeLocale(string) Locale Parse + normalize deprecated codes
findBestMatch(locale, list, {useFallbackChain}) Locale? Best matching locale
getRelatedLocales(locale) List<Locale> Intelligible language fallbacks
getDisplayName(locale) String English language name
getNativeName(locale) String Native language name
getFullDisplayName(locale) String English name with region
getFullNativeDisplayName(locale) String Native name with region
getCountryName(code) String? Country name from ISO 3166-1 code
getLocaleInfo(locale) LocaleInfo All metadata bundled
getPluralCategory(locale, n) PluralCategory CLDR plural form for number
getNumberFormat(locale) NumberFormatInfo Decimal/grouping separators, numeral system
getNumeralSystem(locale) NumeralSystem Numeral system (Latin, Arabic-Indic, etc.)
getFirstDayOfWeek(locale) int 1=Mon, 6=Sat, 7=Sun
getDefaultCalendar(locale) CalendarSystem Gregorian, Islamic, Persian, etc.
getDateOrder(locale) DateOrder dmy / mdy / ymd
getWritingSystem(locale) WritingSystemInfo Script properties
getTextExpansionFactor(locale) double UI text expansion relative to English
isValidLanguageCode(code) bool Valid ISO 639 language code
isValidCountryCode(code) bool Valid ISO 3166-1 country code
isValidLocale(locale) bool Valid locale

LocaleUtils — Properties #

Property Type Description
rtlLanguages Set<String> All RTL language codes

Locale Extension #

All LocaleUtils methods are also available directly on Locale:

locale.isRtl
locale.textDirection
locale.displayName
locale.nativeName
locale.fullDisplayName
locale.fullNativeDisplayName
locale.countryDisplayName
locale.isValid
locale.pluralCategory(n)
locale.numeralSystem
locale.firstDayOfWeek
locale.calendarSystem
locale.dateOrder
locale.writingSystem
locale.textExpansionFactor

Integration Examples #

With flutter_localizations #

import 'package:modular_l10n/modular_l10n.dart';

MaterialApp(
  locale: currentLocale,
  supportedLocales: AppLocalizations.supportedLocales,
  localizationsDelegates: AppLocalizations.localizationsDelegates,
  builder: (context, child) {
    return Directionality(
      textDirection: currentLocale.textDirection,
      child: child!,
    );
  },
)

Locale Switcher Widget #

DropdownButton<Locale>(
  value: currentLocale,
  items: supportedLocales.map((locale) {
    return DropdownMenuItem(
      value: locale,
      child: Row(
        children: [
          Text(locale.nativeName),
          const SizedBox(width: 8),
          if (locale.isRtl)
            const Icon(Icons.format_textdirection_r_to_l, size: 16),
        ],
      ),
    );
  }).toList(),
  onChanged: (locale) {
    if (locale != null) onLocaleChanged(locale);
  },
)

With Provider #

class LocaleNotifier extends ChangeNotifier {
  Locale _locale = const Locale('en');
  Locale get locale => _locale;

  void setLocale(Locale locale) {
    _locale = locale;
    notifyListeners();
  }
}

With Riverpod #

final localeProvider = StateProvider<Locale>((ref) => const Locale('en'));

// Change locale
ref.read(localeProvider.notifier).state = const Locale('ar');

With Bloc/Cubit #

class LocaleCubit extends Cubit<Locale> {
  LocaleCubit() : super(const Locale('en'));
  void setLocale(Locale locale) => emit(locale);
}

Performance #

  • All data maps are const — resolved at compile time, zero runtime allocation
  • RTL check is O(1) const Set lookup
  • All methods are static — no object instantiation
  • No external dependencies

Testing #

flutter test

Plural categories are covered by a table transcribed from the CLDR plural-rule charts, with cases at every rule boundary — ru at 11 and 12–14, ar across all six forms, ga and cy where many applies, and fractional values where the v operand forces other. Locale parsing, best-match resolution, RTL detection, display names, and validation are covered too.


VS Code Extension (Optional) #

The Modular Flutter Localization VS Code Extension adds code generation on top of this package:

Feature Description
Type-Safe Code Generation Generates ML class with autocomplete for all translations
Modular Organization Organize translations by feature (auth, home, profile, etc.)
Watch Mode Auto-regenerate code when ARB files change
ICU Messages Full support for plurals, select, gender, date/number formatting
Extract to ARB Right-click strings in code to move them to ARB files

👉 Get the VS Code Extension


Philosophy #

This package does not handle locale switching or state management. It only provides information about locales. Use your preferred state management solution (Provider, Riverpod, Bloc, GetX, etc.) for switching.

Why? You know your app's architecture best. No vendor lock-in, stays lightweight, you maintain full control.


Issues & Contributions #

License #

MIT License — see the LICENSE file for details.


Made with care by Utanium

0
likes
160
points
103
downloads

Documentation

Documentation
API reference

Publisher

verified publisherutanium.org

Weekly Downloads

Lightweight locale utilities for Flutter with RTL detection, locale parsing, display names for 70+ languages. Works standalone or with Modular Flutter L10n extension.

Repository (GitHub)
View/report issues

Topics

#localization #l10n #i18n #rtl

License

MIT (license)

Dependencies

flutter

More

Packages that depend on modular_l10n