my_lang is a library that provides flexible and easy-to-use localization support for Flutter applications.
Requires Dart 3.9 or newer and Flutter 3.35 or newer. The current release is verified with Flutter 3.47.1 and Dart 3.13.1.
Features
- JSON, ARB, YAML, CSV, and XML translation files.
- Nested keys, locale fallback, and compact multi-locale CSV files.
- Missing-translation callbacks and custom decoders.
- ICU placeholders, plurals, and selects for ARB-style messages.
- In-memory locale cache and protection from out-of-order async loads.
- Efficient listener-based locale switching with legacy compatibility.
- Easy to integrate and use.
- Dynamic language switching in the app.
- Generates safe, typed Dart helpers from every supported file format.
Installation
Add the following to your pubspec.yaml:
my_lang: ^1.1.0
flutter:
assets:
- assets/i18n/
Translation file formats
Files use the locale name followed by the selected extension, for example
en.json, en-US.arb, or zh-Hant.yaml.
JSON
Create JSON files inside the assets/i18n/ directory:
en.json
{
"welcomeBack": "Welcome Back",
"welcomeBackNameApp": "Welcome @nameUser Back @nameApp"
}
Nested maps are flattened automatically, so {"home":{"title":"Home"}}
can be read with translate('home.title').
ARB and ICU messages
ARB metadata is ignored while messages remain available for lookup:
{
"@@locale": "en",
"cartItems": "{count, plural, =0{No items} one{# item} other{# items}}",
"@cartItems": {
"description": "Number of products in the cart"
}
}
myLang.translateMessage('cartItems', arguments: {'count': 2});
YAML
home:
title: Home
subtitle: Welcome back
CSV
A file per locale can use key,value:
key,value
home.title,Home
welcome,"Hello, world"
One compact file can contain multiple locales:
key,en,vi
home.title,Home,Trang chu
await myLang.setUp(
listLocale: const [Locale('en'), Locale('vi')],
format: MyLangFileFormat.csv,
fileName: 'translations.csv',
);
The shared CSV source is cached once while each locale column gets its own decoded map.
XML
<resources>
<string name="home.title">Home</string>
<string name="welcome">Welcome</string>
</resources>
Custom formats
Provide an extension and decoder when a project needs another format:
await myLang.setUp(
listLocale: listLocale,
fileExtension: 'toml',
decoder: (source, locale) => decodeMyToml(source),
);
vi.json
{
"welcomeBack": "Chào mừng trở lại",
"welcomeBackNameApp": "Chào mừng @nameUser trở lại @nameApp"
}
Initializing the Library
import 'package:my_lang/my_lang.dart';
import 'package:flutter/widgets.dart';
MyLang myLang = MyLang();
const listLocale = [
Locale('en'),
Locale('vi'),
];
void main() async {
WidgetsFlutterBinding.ensureInitialized();
await myLang.setUp(
listLocale: listLocale,
format: MyLangFileFormat.json,
fallbackLocale: const Locale('en'),
onMissingTranslation: (key, locale) {
debugPrint('Missing $key for $locale');
},
);
runApp(MyApp());
}
Generating typed translation code
The generator supports JSON, ARB, YAML/YML, CSV, and XML. The input format is inferred from the file extension:
dart run my_lang:my_lang \
--input assets/i18n/en.yaml \
--output lib/languages/interpreter.dart \
--class OurLang
Use --format json|arb|yaml|csv|xml to override detection. For a compact CSV
containing multiple locale columns, select the source column with --locale:
dart run my_lang:my_lang \
--input assets/i18n/translations.csv \
--output lib/languages/interpreter.dart \
--class OurLang \
--locale en
The generated extension contains getters for ordinary messages, parameters
for @name placeholders, and typed arguments for ICU placeholders, plurals,
and selects. Run dart run my_lang:my_lang --help for all options.
Creating interpreter.dart manually
You can also create the extension without the generator:
import 'package:my_lang/my_lang.dart';
extension OurLang on MyLang {
String get welcomeBack => translate('welcomeBack');
String welcomeBackNameApp(String nameUser, String nameApp) =>
translate('welcomeBackNameApp', params: {
'nameUser': nameUser,
'nameApp': nameApp,
});
}
Usage
print(myLang.welcomeBack); // "Welcome Back" or "Chào mừng trở lại"
print(myLang.welcomeBackNameApp("John", "MyApp"));
Changing language efficiently
Wrap the part of the app that reads translations with a listener. MyLang
then rebuilds listeners instead of reassembling the full widget tree:
ListenableBuilder(
listenable: myLang,
builder: (context, child) => MaterialApp(
locale: myLang.locale,
supportedLocales: listLocale,
localizationsDelegates: GlobalMaterialLocalizations.delegates,
home: const HomePage(),
),
);
Add Flutter's flutter_localizations SDK dependency when setting
MaterialApp.locale, so Material and Cupertino widgets also use the selected
locale.
await myLang.loadFile(
locale: myLang.locale.languageCode == 'en'
? const Locale('vi')
: const Locale('en'),
);
You can also use the helper:
await myLang.loadFile(
locale: myLang.locale.isEnglish
? const Locale('vi')
: const Locale('en'),
);
Decoded locale maps are cached. Call myLang.clearCache() after replacing
translation assets at runtime. loadFileJson() remains available for existing
applications.
Locale Helper
Check the language of a Locale quickly:
final locale = myLang.locale;
if (locale.isEnglish) {
// ...
}
if (locale.isLanguage('vi')) {
// ...
}
Generating OurLang Automatically
Run the following command to automatically generate interpreter.dart:
dart pub global activate my_lang
my_lang
my_lang -i assets/i18n/en.arb -o lib/interpreter.dart -c YourLang
my_lang -i assets/i18n/translations.csv -o lib/interpreter.dart -c YourLang -l en
💡 Tip: Quickly copy a file path using these shortcuts on Android Studio:
- MacBook:
Command (⌘) + Option (⌥) + C- Windows:
Ctrl + Shift + C
Two languages at the same time
import 'package:my_lang/my_lang.dart';
import 'package:flutter/widgets.dart';
MyLang myLang = MyLang();
MyLang myLang2 = MyLang();
const listLocale = [
Locale('en'),
Locale('vi'),
];
void main() async {
WidgetsFlutterBinding.ensureInitialized();
await myLang.setUp(listLocale: listLocale);
await myLang2.setUp(
listLocale: listLocale,
keySaveLocale: "otherKeyForSharedPreference"
);
runApp(MyApp());
}
Note:
ThekeySaveLocalemust be different for each instance.
This key is used to store the selected language inSharedPreferences.
Usage example:
You can use both language instances as normal:
print(myLang.welcomeBack);
print(myLang2.welcomeBack);
Contribution
If you have any suggestions or find any issues, feel free to open an issue or submit a pull request on GitHub. If you want to know what i do in package, checking my document here https://wong-coupon.gitbook.io/flutter/ui/multi-language