Claude-skill-registry Internationalization Patterns
Internationalization (i18n) and localization (l10n) patterns using GetX Translations for multi-language Flutter applications
install
source · Clone the upstream repo
git clone https://github.com/majiayu000/claude-skill-registry
Claude Code · Install into ~/.claude/skills/
T=$(mktemp -d) && git clone --depth=1 https://github.com/majiayu000/claude-skill-registry "$T" && mkdir -p ~/.claude/skills && cp -r "$T/skills/data/internationalization-patterns" ~/.claude/skills/majiayu000-claude-skill-registry-internationalization-patterns && rm -rf "$T"
manifest:
skills/data/internationalization-patterns/SKILL.mdsource content
Internationalization Patterns with GetX
Complete guide to implementing multi-language support in Flutter applications using GetX's powerful internationalization system.
Setup
Define Translations
Create a translations class extending
Translations:
// lib/core/i18n/app_translations.dart import 'package:get/get.dart'; class AppTranslations extends Translations { @override Map<String, Map<String, String>> get keys => { 'en_US': EnUS.keys, 'ar_SA': ArSA.keys, 'fr_FR': FrFR.keys, 'es_ES': EsES.keys, }; } // English translations class EnUS { static const keys = { // Common 'app_name': 'My App', 'common_save': 'Save', 'common_cancel': 'Cancel', 'common_delete': 'Delete', 'common_edit': 'Edit', 'common_loading': 'Loading...', 'common_error': 'An error occurred', // Authentication 'auth_login_title': 'Login', 'auth_login_email': 'Email', 'auth_login_password': 'Password', 'auth_login_submit': 'Sign In', 'auth_login_forgot_password': 'Forgot Password?', 'auth_logout': 'Logout', // Validation 'validation_email_required': 'Email is required', 'validation_email_invalid': 'Please enter a valid email', 'validation_password_required': 'Password is required', 'validation_password_min_length': 'Password must be at least @length characters', // Errors 'error_network': 'Network error. Please check your connection.', 'error_server': 'Server error. Please try again later.', 'error_unauthorized': 'Invalid email or password', 'error_not_found': 'Resource not found', // Profile 'profile_title': 'Profile', 'profile_edit': 'Edit Profile', 'profile_settings': 'Settings', 'profile_logout': 'Logout', // Settings 'settings_title': 'Settings', 'settings_language': 'Language', 'settings_theme': 'Theme', 'settings_notifications': 'Notifications', // Pluralization 'items_count_zero': 'No items', 'items_count_one': 'One item', 'items_count_other': '@count items', }; } // Arabic translations class ArSA { static const keys = { // Common 'app_name': 'تطبيقي', 'common_save': 'حفظ', 'common_cancel': 'إلغاء', 'common_delete': 'حذف', 'common_edit': 'تعديل', 'common_loading': 'جار التحميل...', 'common_error': 'حدث خطأ', // Authentication 'auth_login_title': 'تسجيل الدخول', 'auth_login_email': 'البريد الإلكتروني', 'auth_login_password': 'كلمة المرور', 'auth_login_submit': 'تسجيل الدخول', 'auth_login_forgot_password': 'نسيت كلمة المرور؟', 'auth_logout': 'تسجيل الخروج', // Validation 'validation_email_required': 'البريد الإلكتروني مطلوب', 'validation_email_invalid': 'يرجى إدخال بريد إلكتروني صحيح', 'validation_password_required': 'كلمة المرور مطلوبة', 'validation_password_min_length': 'يجب أن تكون كلمة المرور @length أحرف على الأقل', // Errors 'error_network': 'خطأ في الشبكة. يرجى التحقق من الاتصال.', 'error_server': 'خطأ في الخادم. يرجى المحاولة لاحقاً.', 'error_unauthorized': 'البريد الإلكتروني أو كلمة المرور غير صحيحة', 'error_not_found': 'المورد غير موجود', // Profile 'profile_title': 'الملف الشخصي', 'profile_edit': 'تعديل الملف الشخصي', 'profile_settings': 'الإعدادات', 'profile_logout': 'تسجيل الخروج', // Settings 'settings_title': 'الإعدادات', 'settings_language': 'اللغة', 'settings_theme': 'المظهر', 'settings_notifications': 'الإشعارات', // Pluralization 'items_count_zero': 'لا توجد عناصر', 'items_count_one': 'عنصر واحد', 'items_count_other': '@count عنصر', }; }
Configure GetMaterialApp
// lib/main.dart import 'package:flutter/material.dart'; import 'package:get/get.dart'; import 'core/i18n/app_translations.dart'; void main() { runApp(const MyApp()); } class MyApp extends StatelessWidget { const MyApp({super.key}); @override Widget build(BuildContext context) { return GetMaterialApp( title: 'My App', translations: AppTranslations(), locale: Get.deviceLocale, // Use device locale fallbackLocale: const Locale('en', 'US'), // Fallback to English initialRoute: AppRoutes.home, getPages: AppPages.pages, ); } }
Usage in Widgets
Basic Translation
// Using .tr extension Text('auth_login_title'.tr) // Displays "Login" in English, "تسجيل الدخول" in Arabic // Alternative syntax Text(Get.find<AppLocalizations>().translate('auth_login_title'))
Translation with Parameters
// Translation key with placeholder 'validation_password_min_length': 'Password must be at least @length characters' // Usage with parameter Text('validation_password_min_length'.trParams({'length': '8'})) // Displays: "Password must be at least 8 characters"
Pluralization
// Define plural forms in translations 'items_count_zero': 'No items', 'items_count_one': 'One item', 'items_count_other': '@count items', // Usage String getItemsCountText(int count) { if (count == 0) { return 'items_count_zero'.tr; } else if (count == 1) { return 'items_count_one'.tr; } else { return 'items_count_other'.trParams({'count': count.toString()}); } } // Or use a helper extension PluralExtension on String { String trPlural(int count) { final key = this; if (count == 0) { return '${key}_zero'.tr; } else if (count == 1) { return '${key}_one'.tr; } else { return '${key}_other'.trParams({'count': count.toString()}); } } } // Usage Text('items_count'.trPlural(items.length))
Locale Management
Change Locale at Runtime
class SettingsController extends GetxController { final currentLocale = const Locale('en', 'US').obs; void changeLanguage(String languageCode, String countryCode) { final locale = Locale(languageCode, countryCode); currentLocale.value = locale; Get.updateLocale(locale); // Optionally save to local storage final storage = Get.find<GetStorage>(); storage.write('locale', '$languageCode\_$countryCode'); } } // Usage in UI DropdownButton<String>( value: controller.currentLocale.value.languageCode, items: const [ DropdownMenuItem(value: 'en', child: Text('English')), DropdownMenuItem(value: 'ar', child: Text('العربية')), DropdownMenuItem(value: 'fr', child: Text('Français')), DropdownMenuItem(value: 'es', child: Text('Español')), ], onChanged: (languageCode) { String countryCode = _getCountryCode(languageCode!); controller.changeLanguage(languageCode, countryCode); }, )
Persist Locale Preference
class LocaleService { final GetStorage _storage; LocaleService(this._storage); /// Get saved locale or device locale Locale getLocale() { final savedLocale = _storage.read<String>('locale'); if (savedLocale != null) { final parts = savedLocale.split('_'); return Locale(parts[0], parts.length > 1 ? parts[1] : ''); } return Get.deviceLocale ?? const Locale('en', 'US'); } /// Save locale preference void saveLocale(Locale locale) { _storage.write('locale', '${locale.languageCode}_${locale.countryCode}'); } } // In main.dart void main() async { WidgetsFlutterBinding.ensureInitialized(); await GetStorage.init(); final localeService = LocaleService(GetStorage()); final savedLocale = localeService.getLocale(); runApp(MyApp(initialLocale: savedLocale)); }
RTL Support
Detect RTL Languages
bool isRTL(Locale locale) { final rtlLanguages = ['ar', 'he', 'fa', 'ur']; return rtlLanguages.contains(locale.languageCode); }
Configure Text Direction
class MyApp extends StatelessWidget { @override Widget build(BuildContext context) { return GetMaterialApp( title: 'My App', translations: AppTranslations(), locale: Get.deviceLocale, fallbackLocale: const Locale('en', 'US'), // Automatically set text direction based on locale builder: (context, child) { return Directionality( textDirection: isRTL(Get.locale!) ? TextDirection.rtl : TextDirection.ltr, child: child!, ); }, initialRoute: AppRoutes.home, getPages: AppPages.pages, ); } }
RTL-Aware Widgets
// Use EdgeInsetsDirectional instead of EdgeInsets Padding( padding: const EdgeInsetsDirectional.only(start: 16.0, end: 8.0), child: Text('Hello'), ) // Use AlignmentDirectional instead of Alignment Align( alignment: AlignmentDirectional.centerStart, // Start instead of left child: Text('Aligned Text'), ) // Use leading/trailing instead of left/right in Row Row( children: [ const Icon(Icons.arrow_forward), // Will flip in RTL const SizedBox(width: 8), Text('Next'), ], )
Date and Number Formatting
Date Formatting
import 'package:intl/intl.dart'; class DateFormatter { static String formatDate(DateTime date, Locale locale) { final formatter = DateFormat.yMMMd(locale.toString()); return formatter.format(date); } static String formatTime(DateTime time, Locale locale) { final formatter = DateFormat.jm(locale.toString()); return formatter.format(time); } static String formatDateTime(DateTime dateTime, Locale locale) { final formatter = DateFormat.yMMMd(locale.toString()).add_jm(); return formatter.format(dateTime); } } // Usage final formattedDate = DateFormatter.formatDate(DateTime.now(), Get.locale!); // English: "Jan 15, 2024" // Arabic: "١٥/٠١/٢٠٢٤"
Number Formatting
import 'package:intl/intl.dart'; class NumberFormatter { static String formatNumber(num value, Locale locale) { final formatter = NumberFormat.decimalPattern(locale.toString()); return formatter.format(value); } static String formatCurrency(num value, Locale locale, String currencySymbol) { final formatter = NumberFormat.currency( locale: locale.toString(), symbol: currencySymbol, ); return formatter.format(value); } static String formatPercent(num value, Locale locale) { final formatter = NumberFormat.percentPattern(locale.toString()); return formatter.format(value); } } // Usage final price = NumberFormatter.formatCurrency(99.99, Get.locale!, '\$'); // English: "$99.99" // French: "99,99 \$"
Translation Key Organization
Naming Convention
[feature].[screen].[widget].[state] Examples: - auth.login.email.label - auth.login.email.hint - auth.login.email.error.required - auth.login.email.error.invalid - profile.settings.language.title - profile.settings.language.description - common.button.save - common.button.cancel - common.error.network - common.error.server
Hierarchical Structure
class TranslationKeys { // Common static const commonSave = 'common.button.save'; static const commonCancel = 'common.button.cancel'; static const commonLoading = 'common.loading'; // Authentication static const authLoginTitle = 'auth.login.title'; static const authLoginEmail = 'auth.login.email.label'; static const authLoginEmailHint = 'auth.login.email.hint'; static const authLoginEmailRequired = 'auth.login.email.error.required'; // Profile static const profileTitle = 'profile.title'; static const profileEdit = 'profile.edit'; } // Usage Text(TranslationKeys.authLoginTitle.tr)
Testing Translations
Test Translation Keys
void main() { test('all translation keys exist in all languages', () { final translations = AppTranslations(); final languages = translations.keys.keys.toList(); // Get keys from first language final referenceKeys = translations.keys[languages.first]!.keys.toSet(); // Check all other languages have same keys for (final lang in languages.skip(1)) { final langKeys = translations.keys[lang]!.keys.toSet(); // Missing keys final missingKeys = referenceKeys.difference(langKeys); expect(missingKeys, isEmpty, reason: 'Language $lang is missing keys: $missingKeys'); // Extra keys final extraKeys = langKeys.difference(referenceKeys); expect(extraKeys, isEmpty, reason: 'Language $lang has extra keys: $extraKeys'); } }); }
Best Practices
-
Translation Keys:
- Use hierarchical dot notation
- Keep keys descriptive and consistent
- Avoid hardcoded strings in UI code
- Define constants for frequently used keys
-
Locale Management:
- Persist user's locale preference
- Fallback to device locale when appropriate
- Provide locale switching in settings
- Test with different locales
-
RTL Support:
- Use directional classes (EdgeInsetsDirectional, AlignmentDirectional)
- Test UI with RTL languages (Arabic, Hebrew)
- Flip icons and layouts appropriately
- Consider RTL implications in custom widgets
-
Formatting:
- Use
package for dates and numbersintl - Format currency with correct symbols and positions
- Handle pluralization correctly
- Consider cultural differences (date formats, number separators)
- Use
-
Performance:
- Load only needed translations
- Cache formatted strings when appropriate
- Avoid translating in build methods unnecessarily
- Use const where possible
-
Maintenance:
- Keep translations in sync across languages
- Use translation management tools for large projects
- Involve native speakers for quality translations
- Test translations with real content (not Lorem Ipsum)