PW Localization Flutter
A powerful and flexible localization package for Flutter applications, designed as a replacement for EasyLocalization with enhanced features including runtime ICU formatting support and dynamic language file downloading.
Features
- 🚀 Easy Integration: Drop-in replacement for EasyLocalization with familiar API patterns
- 🌍 ICU Message Formatting: Full support for ICU message format including plurals and gender rules
- 📱 Dynamic Downloads: Download and update language files at runtime from remote servers
- 💾 Asset Fallback: Automatic fallback to asset-based translations when downloads are unavailable
- 🔄 Hot Reload: Support for runtime locale switching and translation updates
- 🏗️ Code Generation: Generate type-safe translation keys from JSON files
- 🎯 Flutter Integration: Seamless integration with Flutter's localization system
- 🗣️ 17 Built-in Delegates: Material, Cupertino, and Widget localization for Guarani, Hausa, Hawaiian, Igbo, Javanese, Latin, Maori, Nahuatl, Quechua, Shona, Somali, Swahili, Tajik, Turkmen, Xhosa, Yoruba, and Cantonese
Installation
Add this to your package's pubspec.yaml file:
dependencies:
flutter_pipwave_localization: ^0.3.0+1
Example
A complete working example is available in the example/ directory. The example demonstrates:
- Custom setup with
PWLocalizationServiceandPWLocalizationDelegate - Using
context.tr()for simple translations,context.plural()andcontext.gender()for plurals and gender - Runtime language switching with
AnimatedBuilder - Dynamic download of language files from a remote URL
- BCP-47 locale support (e.g.,
en,id,zh-CN)
To run the example:
cd example
flutter pub get
flutter run
Quick Start
1. Basic Setup
Wrap your app with PWLocalizationProvider:
import 'package:flutter/material.dart';
import 'package:flutter_pipwave_localization/flutter_pipwave_localization.dart';
void main() {
runApp(
PWLocalizationProvider(
startLocale: const Locale('en'),
assetPath: 'assets/translations',
downloadedPath: 'translations',
supportedLocales: const [
Locale('en'),
Locale('es'),
Locale('fr'),
],
child: const MyApp(),
),
);
}
2. Using Translations
Access translations through the context extension:
class MyHomePage extends StatelessWidget {
const MyHomePage({super.key});
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: Text(context.tr('welcome.title')),
),
body: Center(
child: Column(
mainAxisAlignment: MainAxisAlignment.center,
children: [
Text(context.tr('welcome.message', args: {'name': 'John'})),
Text(context.plural('items.countInObject', 5)),
],
),
),
);
}
}
3. Using Translation Keys (Optional)
After running the code generator, use type-safe constants:
// Generated keys (names depend on your JSON structure)
Text(LocaleKeys.welcomeTitle.tr());
Text(LocaleKeys.itemsCount.trPlural(5));
API Reference
PWLocalizationProvider
The main widget that provides localization to your app. Wraps your app and initializes PWLocalizationService.
PWLocalizationProvider({
required Widget child,
required Locale startLocale,
required String assetPath,
required String downloadedPath,
List<Locale> supportedLocales = const [Locale('en')],
Widget? loadingWidget,
bool isNewInstance = false,
Key? key,
});
Parameters:
child: The widget tree to provide localization forstartLocale: The initial locale to useassetPath: Path to asset-based translation files (e.g., 'assets/translations')downloadedPath: Directory name for downloaded translation filessupportedLocales: List of supported localesloadingWidget: Optional widget to show while the initial locale is loadingisNewInstance: If true, creates a new service instance instead of using the singleton
PWLocalizedMaterialApp
A convenience widget combining MaterialApp with localization. Use when you need both Material Design and localization in a single widget.
PWLocalizedMaterialApp(
home: const MyHomePage(),
startLocale: const Locale('en'),
assetPath: 'assets/translations',
downloadedPath: 'translations',
supportedLocales: const [Locale('en'), Locale('es')],
theme: ThemeData.light(),
);
PWLocalizationService
The core service that handles translations and ICU formatting. Accessed via PWLocalizationService.instance.
Key Methods
// Initialize the service
await PWLocalizationService.instance.initialize(
assetPath: 'assets/translations',
downloadedPath: 'translations',
defaultLanguage: 'en',
locale: const Locale('en'),
);
// Translate a key (supports map, list, or named args)
String translated = PWLocalizationService.instance.tr(
'welcome.message',
args: {'name': 'John'},
);
// For plurals, use plural()
String plural = PWLocalizationService.instance.plural(
'newMessages',
5,
);
// Check if locale is supported
bool isSupported = PWLocalizationService.instance.isLocaleSupported('en');
// Download language files (headers and query params optional)
await PWLocalizationService.instance.downloadLanguageFiles(
'https://api.example.com/translations',
['en', 'es', 'fr'],
headers: {'Authorization': 'Bearer token'},
queryParameters: {'version': '1.0'},
);
// Alternative: download by locale tags or Locale objects
await PWLocalizationService.instance.downloadLanguageFilesForTags(
baseUrl, ['en', 'zh-CN'],
);
await PWLocalizationService.instance.downloadLanguageFilesForLocales(
baseUrl, [const Locale('en'), const Locale('zh', 'CN')],
);
// Clear downloaded translations
await PWLocalizationService.instance.clearDownloadedTranslations();
Locale Helpers
// Convert between Locale and BCP-47 tags
String tag = PWLocalizationService.localeToTag(const Locale('zh', 'CN')); // 'zh-CN'
Locale locale = PWLocalizationService.localeFromTag('zh-CN');
PWLocalizationDelegate
Integrates with Flutter's localizationsDelegates. Requires either an already-initialized service or configuration for initialization.
PWLocalizationDelegate(
instance: PWLocalizationService.instance,
locale: locale,
supportedLocales: supportedLocales,
assetPath: 'assets/translations',
downloadedPath: 'translations',
defaultLocaleTag: 'en',
);
Helper to get configured delegates:
PWLocalizationDelegate.getConfiguredDelegates(
instance: PWLocalizationService.instance,
locale: locale,
supportedLocales: supportedLocales,
assetPath: 'assets/translations',
downloadedPath: 'translations',
defaultLocaleTag: 'en',
);
Context Extensions
Convenient extensions for accessing localization in widgets:
// Simple translation with arguments
String text = context.tr('welcome.message', args: {'name': 'John'});
// Plural forms (object-based: zero, one, many, other)
String plural = context.plural('items.countInObject', 5);
// Gender forms (object-based: male, female, other)
String welcome = context.gender('profileWelcome', 'male', namedArgs: {'name': 'John'});
// Get current locale
Locale? locale = context.currentLocale;
String Extensions
Extensions for direct string translation:
// Simple translation
String text = 'welcome.message'.tr();
// Translation with arguments
String text = 'welcome.message'.tr(args: {'name': 'John'});
String text = 'welcome.message'.trArgs({'name': 'John'});
// Plural and gender (for object-based translation keys)
String text = 'items.countInObject'.trPlural(5);
String text = 'profileWelcome'.trGender('male', namedArgs: {'name': 'John'});
ICU Message Format Support
This package fully supports ICU message formatting for advanced pluralization and gender rules. Use dedicated methods:
- Object-based plurals (zero, one, many, other):
context.plural(key, count)orservice.plural(key, count) - Object-based gender (male, female, other):
context.gender(key, gender)orservice.gender(key, gender) - ICU inline format (e.g.
{count, plural, ...}in string):context.tr(key, args: {...})
Plural Rules
Object-Based Plural Format
Object-based plural definitions use plural():
{
"items_count": {
"zero": "No items",
"one": "One item",
"many": "{count} items",
"other": "{count} items"
}
}
Usage:
Text(context.plural('items_count', 5));
ICU Message Format Plural
For ICU plural syntax embedded directly in string values:
{
"items_count": "{count, plural, =0{No items} =1{One item} =2{Two items} few{Few items} many{Many items} other{{count} items}}"
}
Usage:
Text(context.tr('items_count', args: {'count': 5}));
The ICU format supports:
- Exact matches:
=0,=1,=2, etc. - Plural categories:
zero,one,two,few,many,other - Variable interpolation:
{count}within messages
Gender Rules
{
"welcome": {
"male": "Welcome Mr. {name}",
"female": "Welcome Ms. {name}",
"other": "Welcome {name}"
}
}
Usage:
Text(context.gender('welcome', 'male', namedArgs: {'name': 'John'}));
Complex ICU Formatting
{
"message": "You have {count, plural, zero{no messages} one{one message} many{# messages}} from {sender}."
}
Usage:
Text(context.tr('message', args: {
'count': 5,
'sender': 'Alice'
}));
Code Generation
Generate type-safe translation keys from your JSON files:
dart run flutter_pipwave_localization:generate assets/translations/en.json lib/src/generated/locale_keys.g.dart
The arguments are optional and default to:
- Translation file:
assets/translations/en.json - Output path:
lib/src/generated/locale_keys.g.dart
You can also run without arguments to use the defaults:
dart run flutter_pipwave_localization:generate
This generates a LocaleKeys class with static constants for all translation keys.
Built-in Localization Delegates
The package includes Material, Cupertino, and Widget localization delegates for 17 languages. These provide localized labels for Flutter's built-in widgets (date pickers, time pickers, buttons, dialogs, reorder semantics, etc.).
Supported Languages
| Code | Language | Code | Language |
|---|---|---|---|
| gn | Guarani | sn | Shona |
| ha | Hausa | so | Somali |
| haw | Hawaiian | sw | Swahili |
| ig | Igbo | tg | Tajik |
| jv | Javanese | tk | Turkmen |
| la | Latin | xh | Xhosa |
| mi | Maori | yo | Yoruba |
| nah | Nahuatl | yue | Cantonese |
| qu | Quechua |
Plus Flutter's built-in locales (en, es, fr, zh, etc.) for Material, Cupertino, and Widgets.
Implementation
These delegates are automatically included when you use PWLocalizationProvider or PWLocalizationDelegate.getConfiguredDelegates(). You only need to add your desired locales to supportedLocales:
PWLocalizationProvider(
startLocale: const Locale('ha'),
supportedLocales: const [
Locale('en'),
Locale('ha'), // Hausa - Material/Cupertino/Widget labels
Locale('yo'), // Yoruba
Locale('sw'), // Swahili
],
assetPath: 'assets/translations',
downloadedPath: 'translations',
child: const MyApp(),
);
When the app locale matches one of the built-in delegates (e.g. ha for Hausa), date pickers, buttons, and other Flutter widgets will display labels in that language.
Adding New Delegates
To add a new language delegate, use the delegate generator tool:
dart tool/create_delegate.dart tool/sample_delegate.json
Provide a JSON file following the structure in tool/sample_delegate.json. The script generates all delegate files and automatically updates lib/src/delegates/delegates.dart. See tool/README.md for the JSON format and available keys.
Advanced Usage
Custom Localization Setup
For more control (e.g., manual locale switching without PWLocalizationProvider), use the service and delegate directly:
class MyApp extends StatelessWidget {
const MyApp({super.key});
@override
Widget build(BuildContext context) {
final localizationService = PWLocalizationService.instance;
return AnimatedBuilder(
animation: localizationService,
builder: (context, _) {
final locale = localizationService.currentLocale ?? const Locale('en');
const supported = [Locale('en'), Locale('es'), Locale('zh', 'CN')];
return MaterialApp(
home: const HomePage(),
localizationsDelegates: PWLocalizationDelegate.getConfiguredDelegates(
instance: localizationService,
locale: locale,
supportedLocales: supported,
assetPath: 'assets/translations',
downloadedPath: 'translations',
defaultLocaleTag: 'en',
),
supportedLocales: supported,
locale: locale,
);
},
);
}
}
Dynamic Locale Switching
// Access the localization service (works with both PWLocalizationProvider and custom setup)
final localization = PWLocalizationProvider.of(context);
// Optionally download new language files first
await PWLocalizationService.instance.downloadLanguageFiles(
'https://api.example.com/translations',
['es'],
);
// Switch locale (re-initializes with new locale)
await localization.initialize(
assetPath: 'assets/translations',
downloadedPath: 'translations',
defaultLanguage: 'en', // BCP-47 tag, lowercased internally
locale: const Locale('es'),
);
When using AnimatedBuilder with the service, locale changes will trigger a rebuild and the delegate will load the new locale.
BCP-47 Locale Tags (e.g. zh-CN)
This package stores and looks up translations by lowercased BCP-47 locale tags
such as en, id, zh-CN (not by languageCode only).
- Assets/downloads are resolved as
{localeTag}.json(e.g.assets/translations/zh-CN.json) - Backward compatibility: if
{localeTag}.jsonis missing, it may fall back to{languageCode}.json(e.g.zh.json) for legacy projects.
Example download:
await PWLocalizationService.instance.downloadLanguageFiles(
'https://api.example.com/translations',
['en', 'es', 'fr', 'zh-CN'],
);
Error Handling
try {
await PWLocalizationService.instance.initialize(
assetPath: 'assets/translations',
downloadedPath: 'translations',
defaultLanguage: 'en',
locale: const Locale('en'),
);
} catch (e) {
// Handle initialization errors
print('Localization initialization failed: $e');
}
Translation File Structure
Your translation files should be JSON files with the following structure:
{
"welcome": {
"title": "Welcome",
"message": "Hello {name}!",
"items": {
"count": {
"zero": "No items",
"one": "One item",
"many": "{count} items",
"other": "{count} items"
}
}
},
"items_count_icu": "{count, plural, =0{No items} =1{One item} =2{Two items} few{Few items} many{Many items} other{{count} items}}",
"common": {
"ok": "OK",
"cancel": "Cancel",
"loading": "Loading..."
}
}
You can use either:
- Object-based plurals: Nested objects with
zero,one,many,other, etc. - ICU message format: Direct string values with ICU plural syntax
Best Practices
- Key Naming: Use descriptive, hierarchical keys (e.g.,
welcome.titleinstead ofwelcomeTitle) - Fallback Strategy: Always provide fallback translations in your default language
- ICU Formatting: Use ICU message format for complex pluralization and gender rules
- Asset Organization: Keep translation files organized in
assets/translations/directory - Error Handling: Implement proper error handling for network failures when downloading translations
License
This project is licensed under the MIT License - see the LICENSE file for details.