Skip to content

Internationalization (i18n)

FLEX provides an internationalization (i18n) system that handles multiple locales, currency formatting, and date formatting for global commerce applications. The i18n feature automatically adapts to user preferences and device settings.

The FLEX i18n system consists of three main components:

  • LocaleManager: Handles locale selection, persistence, and change notifications
  • CurrencyFormatManager: Formats monetary values according to locale conventions
  • DateFormatManager: Formats dates and times with locale-specific patterns

All components work together seamlessly and automatically update when the user changes their locale preference.

Configure internationalization in your main.dart:

await FlexCore.initialize([
I18nFeature(
supportedLocales: [
const Locale('en', 'CA'), // English (Canada)
const Locale('en', 'US'), // English (United States)
const Locale('fr', 'CA'), // French (Canada)
const Locale('es', 'MX'), // Spanish (Mexico)
],
defaultLocale: const Locale('en', 'CA'),
),
// Other features...
]);

Define which locales your app supports:

I18nFeature(
supportedLocales: [
const Locale('en', 'US'), // English (US)
const Locale('en', 'GB'), // English (UK)
const Locale('en', 'CA'), // English (Canada)
const Locale('fr', 'FR'), // French (France)
const Locale('fr', 'CA'), // French (Canada)
const Locale('es', 'ES'), // Spanish (Spain)
const Locale('es', 'MX'), // Spanish (Mexico)
const Locale('de', 'DE'), // German (Germany)
const Locale('zh', 'CN'), // Chinese (Simplified)
const Locale('ja', 'JP'), // Japanese
],
defaultLocale: const Locale('en', 'US'),
)

The LocaleManager handles all locale-related operations:

final localeManager = FlexCore.container.get<LocaleManager>();
// Get current locale
final currentLocale = localeManager.currentLocale;
// Change locale
await localeManager.changeLocale(const Locale('fr', 'CA'));
// Listen to locale changes
localeManager.onLocaleChanged.listen((newLocale) {
print('Locale changed to: ${newLocale.toString()}');
});
class LocaleService {
final LocaleManager _localeManager = FlexCore.container.get<LocaleManager>();
Future<void> changeToFrench() async {
await _localeManager.changeLocale(const Locale('fr', 'CA'));
}
Future<void> changeToSpanish() async {
await _localeManager.changeLocale(const Locale('es', 'MX'));
}
}
// Check if a locale is supported
bool isSupported = localeManager.isLocaleSupported(const Locale('fr', 'CA'));
// Find best matching locale
Locale bestMatch = localeManager.findBestLocaleMatch(const Locale('fr'));
// Get locale by language code
Locale? frenchLocale = localeManager.getLocaleByCode('fr', 'CA');
// Reset to device locale if supported
await localeManager.resetToDeviceLocale();
// Reset to default locale
await localeManager.resetToDefault();

Format monetary values according to locale conventions:

final currencyFormatter = FlexCore.container.get<CurrencyFormatManager>();
// Format currency amounts
String formatted = currencyFormatter.format(1234.56); // "$1,234.56" (en_US)
String formatted = currencyFormatter.format(1234.56); // "1 234,56 €" (fr_FR)
// Format with specific currency
String usd = currencyFormatter.format(1234.56, currencyCode: 'USD');
String eur = currencyFormatter.format(1234.56, currencyCode: 'EUR');
String gbp = currencyFormatter.format(1234.56, currencyCode: 'GBP');
class ProductPriceWidget extends StatelessWidget {
final double price;
const ProductPriceWidget({required this.price});
@override
Widget build(BuildContext context) {
final currencyFormatter = FlexCore.container.get<CurrencyFormatManager>();
return Text(
currencyFormatter.format(price),
style: Theme.of(context).textTheme.headlineMedium,
);
}
}
// For large numbers, use compact formatting
String compact = currencyFormatter.formatCompact(1234567.89);
// Results:
// en_US: "$1.2M"
// fr_FR: "1,2 M €"
// ja_JP: "¥123万"
// Parse currency strings back to numbers
double? amount = currencyFormatter.parse("\$1,234.56"); // 1234.56
double? amount = currencyFormatter.parse("€1.234,56"); // 1234.56
class CurrencyDisplay extends StatelessWidget {
final double amount;
final String? currencyCode;
final bool showCompact;
const CurrencyDisplay({
required this.amount,
this.currencyCode,
this.showCompact = false,
});
@override
Widget build(BuildContext context) {
final formatter = FlexCore.container.get<CurrencyFormatManager>();
final formattedAmount = showCompact
? formatter.formatCompact(amount, currencyCode: currencyCode)
: formatter.format(amount, currencyCode: currencyCode);
return Text(formattedAmount);
}
}

Format dates and times according to locale conventions:

final dateFormatter = FlexCore.container.get<DateFormatManager>();
final now = DateTime.now();
// Use predefined formats
String short = dateFormatter.format(now, format: 'short'); // "12/25/23"
String medium = dateFormatter.format(now, format: 'medium'); // "Dec 25, 2023"
String long = dateFormatter.format(now, format: 'long'); // "December 25, 2023"
String time = dateFormatter.format(now, format: 'shortTime'); // "2:30 PM"
String full = dateFormatter.format(now, format: 'fullDateTime'); // "December 25, 2023 2:30:45 PM"
// Use custom patterns
String custom = dateFormatter.format(now, format: 'EEEE, MMMM d, y');
// Results:
// en_US: "Monday, December 25, 2023"
// fr_CA: "lundi 25 décembre 2023"
// es_MX: "lunes, 25 de diciembre de 2023"
// Format relative to current time
final yesterday = DateTime.now().subtract(const Duration(days: 1));
final lastWeek = DateTime.now().subtract(const Duration(days: 7));
final lastMonth = DateTime.now().subtract(const Duration(days: 35));
String relative1 = dateFormatter.formatRelative(yesterday); // "1 day ago"
String relative7 = dateFormatter.formatRelative(lastWeek); // "7 days ago"
String relative35 = dateFormatter.formatRelative(lastMonth); // "1 month ago"
// Parse date strings
DateTime? parsed = dateFormatter.parse("Dec 25, 2023", format: 'medium');
DateTime? customParsed = dateFormatter.parse("2023-12-25", format: 'yyyy-MM-dd');
class OrderHistoryItem extends StatelessWidget {
final Order order;
const OrderHistoryItem({required this.order});
@override
Widget build(BuildContext context) {
final dateFormatter = FlexCore.container.get<DateFormatManager>();
final currencyFormatter = FlexCore.container.get<CurrencyFormatManager>();
return Card(
child: ListTile(
title: Text('Order #${order.id}'),
subtitle: Text(
dateFormatter.formatRelative(order.createdAt),
),
trailing: Text(
currencyFormatter.format(order.total),
style: const TextStyle(fontWeight: FontWeight.bold),
),
),
);
}
}

All formatters automatically update when the locale changes:

class ProductCard extends StatelessWidget {
final Product product;
@override
Widget build(BuildContext context) {
final localeManager = FlexCore.container.get<LocaleManager>();
final currencyFormatter = FlexCore.container.get<CurrencyFormatManager>();
final dateFormatter = FlexCore.container.get<DateFormatManager>();
return StreamBuilder<Locale>(
stream: localeManager.onLocaleChanged,
builder: (context, snapshot) {
// This widget automatically rebuilds when locale changes
return Card(
child: Column(
children: [
Text(product.name),
Text(currencyFormatter.format(product.price)),
Text(dateFormatter.formatRelative(product.createdAt)),
],
),
);
},
);
}
}
class CustomDateFormats {
static const Map<String, String> orderFormats = {
'en_US': 'MMM d, yyyy',
'en_GB': 'd MMM yyyy',
'fr_FR': 'd MMM yyyy',
'de_DE': 'd. MMM yyyy',
};
static const Map<String, String> shipmentFormats = {
'en_US': 'EEE, MMM d',
'en_GB': 'EEE d MMM',
'fr_FR': 'EEE d MMM',
'de_DE': 'EEE, d. MMM',
};
static String formatOrderDate(DateTime date) {
final localeManager = FlexCore.container.get<LocaleManager>();
final dateFormatter = FlexCore.container.get<DateFormatManager>();
final locale = localeManager.currentLocale.toString();
final format = orderFormats[locale] ?? orderFormats['en_US']!;
return dateFormatter.format(date, format: format);
}
}
class MockLocaleManager extends Mock implements LocaleManager {
final StreamController<Locale> _controller = StreamController<Locale>.broadcast();
@override
Stream<Locale> get onLocaleChanged => _controller.stream;
@override
Locale currentLocale = const Locale('en', 'US');
@override
Future<void> changeLocale(Locale locale) async {
currentLocale = locale;
_controller.add(locale);
}
void dispose() {
_controller.close();
}
}
group('Currency Formatting Tests', () {
late CurrencyFormatManager formatter;
late MockLocaleManager mockLocaleManager;
setUp(() {
mockLocaleManager = MockLocaleManager();
// Set up test container with mock
FlexCore.container.registerSingleton<LocaleManager>(mockLocaleManager);
formatter = CurrencyFormatManager();
});
test('formats USD correctly for US locale', () {
mockLocaleManager.currentLocale = const Locale('en', 'US');
expect(formatter.format(1234.56), equals('\$1,234.56'));
});
test('formats EUR correctly for French locale', () {
mockLocaleManager.currentLocale = const Locale('fr', 'FR');
expect(formatter.format(1234.56, currencyCode: 'EUR'), contains('€'));
});
});
// ✅ Good - Show native language names
class LocaleSelector extends StatelessWidget {
@override
Widget build(BuildContext context) {
final localeManager = FlexCore.container.get<LocaleManager>();
return ListView.builder(
itemCount: localeManager.supportedLocales.length,
itemBuilder: (context, index) {
final locale = localeManager.supportedLocales[index];
return ListTile(
title: Text(localeManager.getLocaleName(locale)),
subtitle: Text('${locale.languageCode}_${locale.countryCode}'),
trailing: localeManager.currentLocale == locale
? const Icon(Icons.check)
: null,
onTap: () => localeManager.changeLocale(locale),
);
},
);
}
}
// ✅ Good - Use centralized formatting
class PriceUtils {
static String formatPrice(double price, {String? currency}) {
final formatter = FlexCore.container.get<CurrencyFormatManager>();
return formatter.format(price, currencyCode: currency);
}
static String formatDate(DateTime date, {String? format}) {
final formatter = FlexCore.container.get<DateFormatManager>();
return formatter.format(date, format: format);
}
}
// ❌ Avoid - Manual formatting
Text('\$${price.toStringAsFixed(2)}'); // Doesn't respect locale
// ✅ Good - Dispose resources properly
@override
void dispose() {
FlexCore.container.get<LocaleManager>().dispose();
FlexCore.container.get<DateFormatManager>().dispose();
FlexCore.container.get<CurrencyFormatManager>().dispose();
super.dispose();
}

The FLEX i18n system provides a robust foundation for creating globally-accessible commerce applications with proper locale support, currency formatting, and date handling.