modular_l10n 3.0.0
modular_l10n: ^3.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 #
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: ML.of(context).locale.textDirection,
child: child,
)
Installation #
dependencies:
modular_l10n: ^3.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 | ku |
Kurdish (Sorani) |
fa |
Persian/Farsi | ug |
Uyghur |
he |
Hebrew | dv |
Divehi |
ur |
Urdu | ks |
Kashmiri |
ps |
Pashto | yi |
Yiddish |
sd |
Sindhi | ckb |
Central Kurdish |
arc |
Aramaic | syr |
Syriac |
bal |
Balochi | khw |
Khowar |
// 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')
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
Related Locales #
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
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.pluralCategory(3); // PluralCategory
| Group | Languages | Forms |
|---|---|---|
| No plural | Chinese, Japanese, Korean, Thai, Indonesian, … | other |
| Germanic | English, German, Dutch, Swedish, … | one, other |
| French-like | French, Portuguese, Hindi, Bengali, … | one, other (0 is singular) |
| Slavic | Russian, Ukrainian, Croatian, Serbian, … | one, few, many |
| Slavic alt | Polish, Czech, Slovak | one, few, other |
| Arabic | Arabic | zero, one, two, few, many, other |
| Celtic | Irish, Welsh | zero, one, two, few, 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
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 |
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