Jaspr Localizations

Internationalization and localization support for Jaspr applications with ARB file generation and Flutter-like APIs.
Note
Jaspr Localizations brings Flutter's proven localization patterns to Jaspr web applications, making it easy to build multilingual web experiences with automatic platform-aware language detection.
Overview
Jaspr Localizations provides a comprehensive solution for internationalizing Jaspr applications. It combines the power of ARB (Application Resource Bundle) files with automatic code generation to deliver type-safe, efficient localization that follows Flutter's established patterns.
The package leverages Jaspr's InheritedComponent pattern for optimal performance and provides a familiar API for developers coming from Flutter while being perfectly optimized for web applications.
β¨ Features
- π Type-Safe Localization: Compile-time safety with generated Dart classes
- π¦ ARB File Support: Industry-standard format compatible with Flutter tooling
- β‘ Automatic Code Generation: Build-time generation of localization classes
- π Platform-Aware Detection: Automatic language detection from browser (client) or system (server)
- π Dynamic Locale Switching: Runtime locale changes with automatic UI rebuilding
- π§© InheritedComponent Pattern: Efficient state management using Jaspr's architecture
- π ICU MessageFormat: Support for plurals, selects, and complex formatting
- π― Locale Management: Comprehensive locale switching and validation
- β‘ Performance Optimized: Minimal runtime overhead and efficient updates
- π Flutter-Compatible: Easy migration path for Flutter developers
π Getting Started
Prerequisites
- Dart SDK 3.9.0 or later
- Jaspr 0.21.6 or later
Installation
Add jaspr_localizations to your pubspec.yaml:
dependencies:
jaspr_localizations: ^1.0.0
dev_dependencies:
build_runner: ^2.4.0
Then run:
dart pub get
Quick Setup
- Configure localization in your
l10n.yaml:
arb-dir: lib/l10n
template-arb-file: app_en.arb
output-localization-file: generated/l10n.dart
output-class: AppLocalizations
- Create ARB files in
lib/l10n/:
lib/l10n/
βββ app_en.arb # Template file (required)
βββ app_es.arb # Spanish translations
βββ app_fr.arb # French translations
βββ app_de.arb # German translations
- Run code generation:
dart run build_runner build
π Basic Usage
1. Create ARB Files
Create your localization files in lib/l10n/:
lib/l10n/app_en.arb (Template):
{
"@@locale": "en",
"appTitle": "My Application",
"welcomeMessage": "Welcome to {appName}!",
"@welcomeMessage": {
"description": "Welcome message with app name",
"placeholders": {
"appName": {
"type": "String"
}
}
},
"itemCount": "{count, plural, =0{No items} =1{One item} other{{count} items}}",
"@itemCount": {
"description": "Number of items with pluralization",
"placeholders": {
"count": {
"type": "num"
}
}
}
}
lib/l10n/app_es.arb (Spanish):
{
"@@locale": "es",
"appTitle": "Mi AplicaciΓ³n",
"welcomeMessage": "Β‘Bienvenido a {appName}!",
"itemCount": "{count, plural, =0{Sin elementos} =1{Un elemento} other{{count} elementos}}"
}
2. Generate Localization Code
Run the build system to generate type-safe localization classes:
dart run build_runner build
# Or for continuous generation during development
dart run build_runner watch
3. Set Up Your App
Wrap your application with JasprLocalizations:
import 'package:jaspr/jaspr.dart';
import 'package:jaspr_localizations/jaspr_localizations.dart';
import 'generated/l10n.dart';
class App extends StatelessComponent {
@override
Iterable<Component> build(BuildContext context) sync* {
yield JasprLocalizations(
supportedLocales: [
Locale('en', 'US'),
Locale('es', 'ES'),
Locale('fr', 'FR'),
],
initialLocale: Locale('en', 'US'), // Or use getCurrentLocale() for auto-detection
delegates: [AppLocalizations.delegate],
builder: (context, locale) {
return HomePage();
},
);
}
}
4. Use Localizations in Components
Access localized strings in your components:
class HomePage extends StatelessComponent {
@override
Iterable<Component> build(BuildContext context) sync* {
final l10n = AppLocalizations.of(context);
yield div([
h1([text(l10n.appTitle)]),
p([text(l10n.welcomeMessage('Jaspr'))]),
p([text(l10n.itemCount(5))]), // "5 items" or "5 elementos"
]);
}
}
5. Change Locale at Runtime
Create a language switcher:
class LanguageSwitcher extends StatelessComponent {
@override
Iterable<Component> build(BuildContext context) sync* {
final provider = JasprLocalizationProvider.of(context);
final currentLocale = provider.currentLocale;
for (final locale in provider.supportedLocales) {
yield button(
classes: currentLocale == locale ? 'active' : '',
onClick: () => JasprLocalizationProvider.setLocale(context, locale),
[text('${locale.languageCode.toUpperCase()}')],
);
}
}
}
π Platform-Aware Language Detection
Jaspr Localizations automatically detects the user's preferred language from the browser (client-side) or system (server-side):
import 'package:jaspr_localizations/jaspr_localizations.dart';
void main() {
// Automatically detect user's language
final userLocale = getCurrentLocale();
runApp(
JasprLocalizations(
supportedLocales: [
Locale('en', 'US'),
Locale('es', 'ES'),
Locale('fr', 'FR'),
],
initialLocale: userLocale, // Use detected locale
delegates: [AppLocalizations.delegate],
builder: (context, locale) => MyApp(),
),
);
}
Detection Utilities
// Get language code (e.g., 'en-US', 'es', 'fr-FR')
final languageCode = getCurrentLanguageCode();
// Get Locale object
final locale = getCurrentLocale();
// Convert language code to Locale
final locale = languageCodeToLocale('en-US'); // Locale('en', 'US')
final locale2 = languageCodeToLocale('es'); // Locale('es')
How It Works
-
Client-side (Web): Reads
navigator.languageornavigator.languages[0] -
Server-side (SSR): Reads
Intl.getCurrentLocale()from system environment -
Fallback: Defaults to
'en'if detection fails -
Fallback: Defaults to
'en'if detection fails
π― Advanced Features
ICU MessageFormat Support
Handle complex localization scenarios with plurals and selects:
Pluralization
Different languages have different plural rules. ICU MessageFormat handles them automatically:
{
"itemCount": "{count, plural, =0{No items} =1{One item} other{{count} items}}",
"@itemCount": {
"placeholders": {
"count": {"type": "num"}
}
}
}
Supported plural forms:
=0,=1,=2- Exact valueszero,one,two,few,many,other- Named formsotheris required as fallback
Usage:
final l10n = AppLocalizations.of(context);
print(l10n.itemCount(0)); // "No items"
print(l10n.itemCount(1)); // "One item"
print(l10n.itemCount(5)); // "5 items"
Select Messages
Create conditional messages based on categories:
{
"genderMessage": "{gender, select, male{He likes Jaspr} female{She likes Jaspr} other{They like Jaspr}}",
"@genderMessage": {
"placeholders": {
"gender": {"type": "String"}
}
}
}
Usage:
final l10n = AppLocalizations.of(context);
print(l10n.genderMessage('male')); // "He likes Jaspr"
print(l10n.genderMessage('female')); // "She likes Jaspr"
print(l10n.genderMessage('other')); // "They like Jaspr"
Custom Locale Management
For advanced use cases, manually control the locale:
class CustomApp extends StatefulComponent {
@override
State<CustomApp> createState() => _CustomAppState();
}
class _CustomAppState extends State<CustomApp> {
late LocaleChangeNotifier _controller;
@override
void initState() {
super.initState();
_controller = LocaleChangeNotifier(
initialLocale: Locale('en'),
supportedLocales: [
Locale('en'),
Locale('es'),
Locale('fr'),
],
);
}
@override
void dispose() {
_controller.dispose();
super.dispose();
}
@override
Component build(BuildContext context) {
return JasprLocalizationProvider.withController(
controller: _controller,
delegates: [AppLocalizations.delegate],
child: JasprLocaleBuilder(
controller: _controller,
builder: (context, locale) => MyAppContent(),
),
);
}
}
Scoped Locale Override
Display specific content in a different locale:
// Main app in English
JasprLocalizations(
initialLocale: Locale('en'),
supportedLocales: [Locale('en'), Locale('es'), Locale('fr')],
delegates: [AppLocalizations.delegate],
builder: (context, locale) {
return div([
h1([text('English Content')]),
// Preview in Spanish
JasprLocalizations.withLocale(
locale: Locale('es'),
child: PreviewWidget(),
),
]);
},
)
π Configuration Options
l10n.yaml Configuration
Full configuration options for ARB processing:
# Required: Directory containing ARB files
arb-dir: lib/l10n
# Required: Template ARB file (defines all message keys)
template-arb-file: app_en.arb
# Required: Output file path for generated code
output-localization-file: generated/l10n.dart
# Required: Generated class name
output-class: AppLocalizations
# Optional: Use synthetic package for imports (default: true)
synthetic-package: true
# Optional: Nullable getter (default: true)
nullable-getter: true
# Optional: Generate toString method (default: true)
gen-l10n-dart-doc: true
Directory Structure
Recommended project structure:
my_jaspr_app/
βββ lib/
β βββ l10n/
β β βββ app_en.arb # English (template)
β β βββ app_es.arb # Spanish
β β βββ app_fr.arb # French
β β βββ app_de.arb # German
β β βββ app_zh.arb # Chinese
β βββ generated/
β β βββ l10n.dart # Auto-generated (don't edit)
β βββ components/
β β βββ language_switcher.dart
β βββ main.dart
βββ l10n.yaml # Localization config
βββ pubspec.yaml
βββ README.md
π API Reference
Core Classes
Locale
Represents a locale with language and optional country code.
// Constructor
const Locale(String languageCode, [String? countryCode, String? scriptCode]);
// Factory
Locale.fromLanguageTag(String tag);
Locale.fromSubtags({
required String languageCode,
String? scriptCode,
String? countryCode,
});
// Properties
String languageCode; // e.g., 'en', 'es', 'zh'
String? countryCode; // e.g., 'US', 'GB', 'CN'
String? scriptCode; // e.g., 'Hans', 'Latn'
// Methods
String toLanguageTag(); // Returns 'en_US' or 'en'
Examples:
const enUS = Locale('en', 'US');
const es = Locale('es');
const zhHans = Locale('zh', 'CN', 'Hans');
final locale = Locale.fromLanguageTag('en_US');
print(locale.toLanguageTag()); // 'en_US'
JasprLocalizations
High-level component with automatic rebuilding on locale changes.
JasprLocalizations({
required List<Locale> supportedLocales,
Locale? initialLocale,
List<LocalizationsDelegate> delegates = const [],
required Component Function(BuildContext, Locale) builder,
Key? key,
})
// Factory for scoped locale override
JasprLocalizations.withLocale({
required Locale locale,
required Component child,
Key? key,
})
Properties:
supportedLocales- List of all supported localesinitialLocale- Starting locale (defaults to first supported)delegates- Localization delegates for loading resourcesbuilder- Function to build UI with current locale
JasprLocalizationProvider
Low-level InheritedComponent for locale state management.
// Static methods
static JasprLocalizationProvider of(BuildContext context);
static JasprLocalizationProvider? maybeOf(BuildContext context);
static LocaleChangeNotifier? controllerOf(BuildContext context);
static void setLocale(BuildContext context, Locale newLocale);
static void setLanguage(BuildContext context, String languageCode, [String? countryCode]);
// Properties
Locale currentLocale;
Iterable<Locale> supportedLocales;
List<LocalizationsDelegate> delegates;
LocaleChangeNotifier? controller;
Examples:
// Get provider
final provider = JasprLocalizationProvider.of(context);
final locale = provider.currentLocale;
// Change locale
JasprLocalizationProvider.setLocale(context, Locale('es'));
JasprLocalizationProvider.setLanguage(context, 'fr', 'FR');
LocaleChangeNotifier
Controller for managing locale state with change notifications.
LocaleChangeNotifier({
required Locale initialLocale,
required List<Locale> supportedLocales,
})
// Methods
void setLocale(Locale newLocale);
void setLanguage(String languageCode, [String? countryCode]);
void nextLocale(); // Cycle to next supported locale
// Properties
Locale currentLocale;
List<Locale> supportedLocales;
Utility Functions
Language Detection
// Get current language code from browser/platform
String getCurrentLanguageCode();
// Returns: 'en-US', 'es', 'fr-FR', etc.
// Get current locale object
Locale getCurrentLocale();
// Returns: Locale('en', 'US'), etc.
// Convert language code to Locale
Locale languageCodeToLocale(String languageCode);
// Examples:
// 'en' β Locale('en')
// 'en-US' β Locale('en', 'US')
// 'zh-Hans-CN' β Locale('zh', 'CN')
Generated Classes
The build system generates these classes from your ARB files:
AppLocalizations (your generated class)
abstract class AppLocalizations {
// Access localizations
static AppLocalizations of(BuildContext context);
// All your message getters
String get appTitle;
String welcomeMessage(String name);
String itemCount(num count);
// ... etc.
}
AppLocalizationsDelegate
class AppLocalizationsDelegate extends LocalizationsDelegate<AppLocalizations> {
const AppLocalizationsDelegate();
static const List<Locale> supportedLocales = [...];
bool isSupported(Locale locale);
Future<AppLocalizations> load(Locale locale);
bool shouldReload(covariant AppLocalizationsDelegate old);
}
π¨ Complete Example
Here's a complete working example:
import 'package:jaspr/jaspr.dart';
import 'package:jaspr_localizations/jaspr_localizations.dart';
import 'generated/l10n.dart';
void main() {
runApp(App());
}
class App extends StatelessComponent {
@override
Iterable<Component> build(BuildContext context) sync* {
yield JasprLocalizations(
supportedLocales: [
Locale('en', 'US'),
Locale('es', 'ES'),
Locale('fr', 'FR'),
],
initialLocale: getCurrentLocale(), // Auto-detect user's language
delegates: [AppLocalizations.delegate],
builder: (context, locale) => HomePage(),
);
}
}
class HomePage extends StatelessComponent {
@override
Iterable<Component> build(BuildContext context) sync* {
final l10n = AppLocalizations.of(context);
yield div([
LanguageSwitcher(),
h1([text(l10n.appTitle)]),
p([text(l10n.welcomeMessage('Jaspr'))]),
p([text(l10n.itemCount(5))]),
]);
}
}
class LanguageSwitcher extends StatelessComponent {
@override
Iterable<Component> build(BuildContext context) sync* {
final provider = JasprLocalizationProvider.of(context);
final currentLocale = provider.currentLocale;
yield div(
classes: 'language-switcher',
[
for (final locale in provider.supportedLocales)
button(
classes: currentLocale == locale ? 'active' : '',
onClick: () => JasprLocalizationProvider.setLocale(context, locale),
[text(locale.languageCode.toUpperCase())],
),
],
);
}
}
π§ Troubleshooting
Common Issues
π§ Troubleshooting
Common Issues
Build generation fails:
# Clean build cache and regenerate
dart run build_runner clean
dart run build_runner build --delete-conflicting-outputs
Locale not found errors:
- β Verify ARB files exist for all supported locales
- β Check locale codes match between ARB files and code
- β
Ensure template ARB file (
app_en.arb) exists - β
Confirm
l10n.yamlconfiguration is correct
Missing translations:
- β All ARB files must contain the same message keys
- β Template ARB file defines the complete message set
- β
Run
build_runnerafter adding new messages - β Check for typos in message keys
dart:isolate errors in web builds:
This occurs when generated code conditionally imports dart:isolate. To fix:
// Remove @client annotation from components using l10n
// OR restructure to avoid client-specific code
Locale doesn't change:
- β
Ensure you're using
JasprLocalizations(not justJasprLocalizationProvider) - β
Verify the new locale is in
supportedLocales - β
Check that components depend on the provider (using
.of(context))
Performance Issues
If you experience slow locale switching:
- Use
JasprLocaleBuilderfor targeted rebuilds - Avoid frequent locale changes (debounce user input)
- Cache localization instances in component builds
- Profile with Dart DevTools to identify bottlenecks
π¦ Example Projects
Check out the /example directory for complete working examples:
- β¨ Multi-language support - 8+ languages (English, Spanish, French, German, Chinese, Czech, Polish, Slovak)
- π Dynamic locale switching - Interactive language selector
- π Platform-aware detection - Auto-detects user's preferred language
- π ICU MessageFormat - Plurals, selects, and complex formatting
- β‘ Performance optimizations - Best practices demonstrated
Run the example:
cd example
dart run build_runner build
jaspr serve
π€ Contributing
We welcome contributions! Here's how you can help:
Reporting Issues
- π Bug reports: Include minimal reproduction steps
- π‘ Feature requests: Describe the use case and expected behavior
- π Documentation: Suggest improvements or fix typos
Development Setup
-
Fork and clone the repository:
git clone https://github.com/YOUR_USERNAME/jaspr_localizations.git cd jaspr_localizations -
Install dependencies:
dart pub get -
Run tests:
dart test -
Make changes and submit a pull request
Guidelines
- β
Follow Dart style guide and use
dart format - β Add tests for new features
- β Update documentation for API changes
- β Keep commits atomic and well-described
- β Ensure all tests pass before submitting PR
π Changelog
See CHANGELOG.md for detailed release history.
π License
This project is licensed under the MIT License - see the LICENSE file for details.
π Resources
- π Jaspr Documentation
- π Flutter Internationalization Guide
- π ICU MessageFormat Guide
- π ARB File Specification
- π¬ GitHub Discussions
- π Issue Tracker
π Acknowledgments
- Built on top of the excellent Jaspr framework
- Inspired by Flutter's internationalization system
- Uses the industry-standard intl package
Made with β€οΈ for the Jaspr community
If you find this package helpful, please give it a β on GitHub!
Libraries
- builder
- jaspr_localizations
- Jaspr Localizations - Internationalization and localization for Jaspr applications.