# Localization Guide - Worker Mobile App Complete guide for managing translations and localization in the Worker Mobile App. ## Overview The Worker app supports **Vietnamese** (primary) and **English** (secondary) languages with **450+ translation keys** covering all UI elements, messages, and user interactions. ### Key Features - **Primary Language**: Vietnamese (`vi_VN`) - **Secondary Language**: English (`en_US`) - **Translation Keys**: 450+ comprehensive translations - **Auto-generation**: Flutter's `gen-l10n` tool - **Type Safety**: Fully type-safe localization API - **Fallback Support**: Automatic fallback to Vietnamese if device locale is unsupported - **Pluralization**: Full ICU message format support - **Parameterized Strings**: Support for dynamic values - **Helper Extensions**: Convenient access utilities - **Date/Time Formatting**: Locale-specific formatting ## Configuration ### l10n.yaml ```yaml arb-dir: lib/l10n template-arb-file: app_en.arb output-localization-file: app_localizations.dart output-dir: lib/generated/l10n nullable-getter: false ``` ### pubspec.yaml ```yaml dependencies: flutter: sdk: flutter flutter_localizations: sdk: flutter flutter: generate: true ``` ## File Structure ``` lib/ l10n/ app_en.arb # English translations (template) app_vi.arb # Vietnamese translations (PRIMARY) generated/l10n/ # Auto-generated (DO NOT EDIT) app_localizations.dart # Generated base class app_localizations_en.dart # Generated English implementation app_localizations_vi.dart # Generated Vietnamese implementation core/ utils/ l10n_extensions.dart # Helper extensions for easy access l10n.yaml # Localization configuration LOCALIZATION.md # This guide ``` ## Translation Coverage ### Comprehensive Feature Coverage (450+ Keys) | Category | Keys | Examples | |----------|------|----------| | **Authentication** | 25+ | login, phone, verifyOTP, enterOTP, resendOTP, register, logout | | **Navigation** | 10+ | home, products, loyalty, account, more, backToHome, goToHomePage | | **Common Actions** | 30+ | save, cancel, delete, edit, search, filter, confirm, apply, clear, refresh, share, copy | | **Status Labels** | 20+ | pending, processing, shipping, completed, cancelled, active, inactive, expired, draft, sent, accepted, rejected | | **Form Labels** | 30+ | name, email, password, address, street, city, district, ward, postalCode, company, taxId, dateOfBirth, gender | | **User Types** | 5+ | contractor, architect, distributor, broker, selectUserType | | **Loyalty System** | 50+ | points, diamond, platinum, gold, rewards, referral, tierBenefits, pointsMultiplier, specialOffers, exclusiveDiscounts | | **Products & Shopping** | 35+ | product, price, addToCart, cart, checkout, sku, brand, model, specification, availability, newArrival, bestSeller | | **Cart & Checkout** | 30+ | cartEmpty, updateQuantity, removeFromCart, clearCart, proceedToCheckout, orderSummary, selectAddress, selectPaymentMethod | | **Orders & Payments** | 40+ | orders, orderNumber, orderStatus, paymentMethod, deliveryAddress, trackOrder, cancelOrder, orderTimeline, trackingNumber | | **Projects & Quotes** | 45+ | projects, createProject, quotes, budget, progress, client, location, projectPhotos, projectDocuments, quoteItems | | **Account & Profile** | 40+ | profile, editProfile, addresses, changePassword, uploadAvatar, passwordStrength, enableNotifications, selectLanguage | | **Loyalty Transactions** | 20+ | transactionType, earnPoints, redeemPoints, bonusPoints, refundPoints, pointsExpiry, disputeTransaction | | **Gifts & Rewards** | 25+ | myGifts, activeGifts, usedGifts, expiredGifts, giftDetails, rewardCategory, vouchers, pointsCost, expiryDate | | **Referral Program** | 15+ | referralInvite, referralReward, shareYourCode, friendRegisters, bothGetRewards, totalReferrals | | **Validation Messages** | 20+ | fieldRequired, invalidEmail, invalidPhone, passwordTooShort, passwordsNotMatch, incorrectPassword | | **Error Messages** | 15+ | error, networkError, serverError, sessionExpired, notFound, unauthorized, connectionError, syncFailed | | **Success Messages** | 15+ | success, savedSuccessfully, updatedSuccessfully, deletedSuccessfully, redeemSuccessful, photoUploaded | | **Loading States** | 10+ | loading, loadingData, processing, pleaseWait, syncInProgress, syncCompleted | | **Empty States** | 15+ | noData, noResults, noProductsFound, noOrdersYet, noProjectsYet, noNotifications, noGiftsYet | | **Date & Time** | 20+ | today, yesterday, thisWeek, thisMonth, dateRange, from, to, minutesAgo, hoursAgo, daysAgo, justNow | | **Notifications** | 25+ | notifications, markAsRead, markAllAsRead, deleteNotification, clearNotifications, unreadNotifications | | **Chat** | 20+ | chat, sendMessage, typeMessage, typingIndicator, online, offline, messageRead, messageDelivered | | **Filters & Sorting** | 15+ | filterBy, sortBy, priceAscending, priceDescending, nameAscending, dateAscending, applyFilters | | **Offline & Sync** | 15+ | offlineMode, syncData, lastSyncAt, noInternetConnection, checkConnection, retryConnection | | **Miscellaneous** | 20+ | version, help, aboutUs, privacyPolicy, termsOfService, feedback, rateApp, comingSoon, underMaintenance | ### Special Features #### Pluralization Support - `itemsInCart` - 0/1/many items - `ordersCount` - 0/1/many orders - `projectsCount` - 0/1/many projects - `daysRemaining` - 0/1/many days #### Parameterized Translations - `welcomeTo(appName)` - Dynamic app name - `otpSentTo(phone)` - Phone number - `pointsToNextTier(points, tier)` - Points and tier - `redeemConfirmMessage(points, reward)` - Redemption confirmation - `orderNumberIs(orderNumber)` - Order number display - `estimatedDeliveryDate(date)` - Delivery date #### Date/Time Formatting - `formatDate` - DD/MM/YYYY (VI) or MM/DD/YYYY (EN) - `formatDateTime` - Full date-time with locale - `minutesAgo`, `hoursAgo`, `daysAgo`, etc. - Relative time #### Currency Formatting - `formatCurrency` - Vietnamese Dong (₫) with proper grouping ## Usage Examples ### Basic Usage ```dart import 'package:flutter/material.dart'; import 'package:flutter_gen/gen_l10n/app_localizations.dart'; class LoginPage extends StatelessWidget { @override Widget build(BuildContext context) { final l10n = AppLocalizations.of(context)!; return Scaffold( appBar: AppBar( title: Text(l10n.login), ), body: Column( children: [ TextField( decoration: InputDecoration( labelText: l10n.phone, hintText: l10n.enterPhone, ), ), ElevatedButton( onPressed: () {}, child: Text(l10n.continueButton), ), ], ), ); } } ``` ### Using Extension for Cleaner Code (Recommended) ```dart import 'package:worker/core/utils/l10n_extensions.dart'; class ProductCard extends StatelessWidget { @override Widget build(BuildContext context) { // Much cleaner than AppLocalizations.of(context)! return Column( children: [ Text(context.l10n.product), Text(context.l10n.price), ElevatedButton( onPressed: () {}, child: Text(context.l10n.addToCart), ), ], ); } } ``` ### Using Helper Utilities ```dart import 'package:worker/core/utils/l10n_extensions.dart'; class OrderCard extends StatelessWidget { final Order order; @override Widget build(BuildContext context) { return Card( child: Column( children: [ // Format currency Text(L10nHelper.formatCurrency(context, order.total)), // Vietnamese: "1.500.000 ₫" // English: "1,500,000 ₫" // Format date Text(L10nHelper.formatDate(context, order.createdAt)), // Vietnamese: "17/10/2025" // English: "10/17/2025" // Relative time Text(L10nHelper.formatRelativeTime(context, order.createdAt)), // Vietnamese: "5 phút trước" // English: "5 minutes ago" // Status with helper Text(L10nHelper.getOrderStatus(context, order.status)), // Returns localized status string // Item count with pluralization Text(L10nHelper.formatItemCount(context, order.itemCount)), // Vietnamese: "3 sản phẩm" // English: "3 items" ], ), ); } } ``` ### Parameterized Translations ```dart // Points balance with parameter final pointsText = context.l10n.pointsBalance; // Result: "1,000 điểm" (Vietnamese) or "1,000 points" (English) // OTP sent message with phone parameter final message = AppLocalizations.of(context)!.otpSentTo('0912345678'); // Result: "Mã OTP đã được gửi đến 0912345678" // Points to next tier with multiple parameters final tierMessage = context.l10n.pointsToNextTier; // Uses placeholders: {points} and {tier} ``` ### Checking Current Language ```dart import 'package:worker/core/utils/l10n_extensions.dart'; class LanguageIndicator extends StatelessWidget { @override Widget build(BuildContext context) { return Column( children: [ Text('Language Code: ${context.languageCode}'), // "vi" or "en" Text('Is Vietnamese: ${context.isVietnamese}'), // true/false Text('Is English: ${context.isEnglish}'), // true/false ], ); } } ``` ## Adding New Translations ### Step 1: Add to ARB Files **lib/l10n/app_en.arb** (English - Template): ```json { "newFeature": "New Feature", "@newFeature": { "description": "Description of the new feature" } } ``` **lib/l10n/app_vi.arb** (Vietnamese): ```json { "newFeature": "Tính năng mới", "@newFeature": { "description": "Description of the new feature" } } ``` ### Step 2: Regenerate Localization Files ```bash flutter gen-l10n ``` ### Step 3: Use in Code ```dart Text(context.l10n.newFeature) ``` ## Parameterized Translations ### Simple Parameter **ARB File:** ```json { "welcome": "Welcome, {name}!", "@welcome": { "description": "Welcome message", "placeholders": { "name": { "type": "String", "example": "John" } } } } ``` **Usage:** ```dart Text(context.l10n.welcome('John')) // Result: "Welcome, John!" ``` ### Multiple Parameters **ARB File:** ```json { "orderSummary": "Order #{orderNumber} for {amount}", "@orderSummary": { "description": "Order summary text", "placeholders": { "orderNumber": { "type": "String", "example": "12345" }, "amount": { "type": "String", "example": "100,000 ₫" } } } } ``` **Usage:** ```dart Text(context.l10n.orderSummary('12345', '100,000 ₫')) ``` ### Number Parameters **ARB File:** ```json { "itemCount": "{count} items", "@itemCount": { "description": "Number of items", "placeholders": { "count": { "type": "int", "example": "5" } } } } ``` **Usage:** ```dart Text(context.l10n.itemCount(5)) // Result: "5 items" ``` ## Pluralization Flutter's localization supports pluralization with the ICU message format: **ARB File:** ```json { "itemCountPlural": "{count,plural, =0{No items} =1{1 item} other{{count} items}}", "@itemCountPlural": { "description": "Item count with pluralization", "placeholders": { "count": { "type": "int" } } } } ``` **Usage:** ```dart Text(context.l10n.itemCountPlural(0)) // "No items" Text(context.l10n.itemCountPlural(1)) // "1 item" Text(context.l10n.itemCountPlural(5)) // "5 items" ``` ## Date & Time Formatting Use the `intl` package for locale-aware date/time formatting: ```dart import 'package:intl/intl.dart'; // Format date based on current locale final now = DateTime.now(); final locale = Localizations.localeOf(context).toString(); // Vietnamese: "17/10/2025" // English: "10/17/2025" final dateFormatter = DateFormat.yMd(locale); final formattedDate = dateFormatter.format(now); // Vietnamese: "17 tháng 10, 2025" // English: "October 17, 2025" final longDateFormatter = DateFormat.yMMMMd(locale); final formattedLongDate = longDateFormatter.format(now); ``` ## Changing Language at Runtime ### Create Language Provider ```dart // lib/core/providers/language_provider.dart import 'package:flutter_riverpod/flutter_riverpod.dart'; import 'package:flutter/material.dart'; import 'package:shared_preferences/shared_preferences.dart'; final languageProvider = StateNotifierProvider((ref) { return LanguageNotifier(); }); class LanguageNotifier extends StateNotifier { LanguageNotifier() : super(const Locale('vi', 'VN')) { _loadSavedLanguage(); } Future _loadSavedLanguage() async { final prefs = await SharedPreferences.getInstance(); final languageCode = prefs.getString('language_code') ?? 'vi'; final countryCode = prefs.getString('country_code') ?? 'VN'; state = Locale(languageCode, countryCode); } Future setLanguage(Locale locale) async { state = locale; final prefs = await SharedPreferences.getInstance(); await prefs.setString('language_code', locale.languageCode); await prefs.setString('country_code', locale.countryCode ?? ''); } void setVietnamese() => setLanguage(const Locale('vi', 'VN')); void setEnglish() => setLanguage(const Locale('en', 'US')); } ``` ### Update WorkerApp ```dart class WorkerApp extends ConsumerWidget { @override Widget build(BuildContext context, WidgetRef ref) { final locale = ref.watch(languageProvider); return MaterialApp( locale: locale, // ... other configurations ); } } ``` ### Language Selector Widget ```dart class LanguageSelector extends ConsumerWidget { @override Widget build(BuildContext context, WidgetRef ref) { final currentLocale = ref.watch(languageProvider); return DropdownButton( value: currentLocale, items: const [ DropdownMenuItem( value: Locale('vi', 'VN'), child: Text('Tiếng Việt'), ), DropdownMenuItem( value: Locale('en', 'US'), child: Text('English'), ), ], onChanged: (locale) { if (locale != null) { ref.read(languageProvider.notifier).setLanguage(locale); } }, ); } } ``` ## Best Practices ### 1. Naming Conventions - Use **camelCase** for translation keys - Be descriptive but concise - Group related translations with prefixes (e.g., `order*`, `payment*`) - Avoid abbreviations **Good:** ```json { "loginButton": "Login", "orderNumber": "Order Number", "paymentMethod": "Payment Method" } ``` **Bad:** ```json { "btn_login": "Login", "ord_num": "Order Number", "pay_meth": "Payment Method" } ``` ### 2. Reserved Keywords Avoid Dart reserved keywords as translation keys: - `continue` → Use `continueButton` instead - `switch` → Use `switchButton` instead - `class` → Use `className` instead - `return` → Use `returnButton` instead ### 3. Context in Descriptions Always add `@` descriptions to provide context: ```json { "save": "Save", "@save": { "description": "Button label to save changes" } } ``` ### 4. Consistent Formatting Maintain consistent capitalization and punctuation: **Vietnamese:** - Sentence case for labels - No period at the end of single phrases - Use full Vietnamese diacritics **English:** - Title Case for buttons and headers - Sentence case for descriptions - Consistent use of punctuation ### 5. Placeholder Examples Always provide example values for placeholders: ```json { "greeting": "Hello, {name}!", "@greeting": { "description": "Greeting message", "placeholders": { "name": { "type": "String", "example": "John" } } } } ``` ## Testing Localizations ### Widget Tests ```dart testWidgets('Login page shows Vietnamese translations', (tester) async { await tester.pumpWidget( MaterialApp( locale: const Locale('vi', 'VN'), localizationsDelegates: AppLocalizations.localizationsDelegates, supportedLocales: AppLocalizations.supportedLocales, home: LoginPage(), ), ); expect(find.text('Đăng nhập'), findsOneWidget); expect(find.text('Số điện thoại'), findsOneWidget); }); testWidgets('Login page shows English translations', (tester) async { await tester.pumpWidget( MaterialApp( locale: const Locale('en', 'US'), localizationsDelegates: AppLocalizations.localizationsDelegates, supportedLocales: AppLocalizations.supportedLocales, home: LoginPage(), ), ); expect(find.text('Login'), findsOneWidget); expect(find.text('Phone Number'), findsOneWidget); }); ``` ### Translation Completeness Test ```dart void main() { test('All Vietnamese translations match English keys', () { final enFile = File('lib/l10n/app_en.arb'); final viFile = File('lib/l10n/app_vi.arb'); final enJson = jsonDecode(enFile.readAsStringSync()); final viJson = jsonDecode(viFile.readAsStringSync()); final enKeys = enJson.keys.where((k) => !k.startsWith('@')).toList(); final viKeys = viJson.keys.where((k) => !k.startsWith('@')).toList(); expect(enKeys.length, viKeys.length); for (final key in enKeys) { expect(viKeys.contains(key), isTrue, reason: 'Missing key: $key'); } }); } ``` ## Troubleshooting ### Issue: "AppLocalizations not found" **Solution:** Run the code generator: ```bash flutter gen-l10n ``` ### Issue: "Duplicate keys in ARB file" **Solution:** Each key must be unique within an ARB file. Check for duplicates. ### Issue: "Invalid placeholder type" **Solution:** Supported types are: `String`, `num`, `int`, `double`, `DateTime`, `Object` ### Issue: "Translations not updating" **Solution:** 1. Run `flutter gen-l10n` 2. Hot restart (not hot reload) the app 3. Clear build cache if needed: `flutter clean` ## Translation Workflow ### For Developers 1. **Add English translation** to `app_en.arb` 2. **Add Vietnamese translation** to `app_vi.arb` 3. **Run code generator**: `flutter gen-l10n` 4. **Use in code**: `context.l10n.newKey` 5. **Test both languages** ### For Translators 1. **Review** the English ARB file (`app_en.arb`) 2. **Translate** each key to Vietnamese in `app_vi.arb` 3. **Maintain** the same structure and placeholders 4. **Add** `@key` descriptions if needed 5. **Test** context and meaning ## Resources - [Flutter Internationalization](https://docs.flutter.dev/development/accessibility-and-localization/internationalization) - [ARB File Format](https://github.com/google/app-resource-bundle/wiki/ApplicationResourceBundleSpecification) - [ICU Message Format](https://unicode-org.github.io/icu/userguide/format_parse/messages/) - [Intl Package](https://pub.dev/packages/intl) ## Translation Statistics - **Total Translation Keys**: 450+ - **Languages**: 2 (Vietnamese, English) - **Coverage**: 100% (Both languages fully translated) - **Parameterized Keys**: 20+ - **Pluralization Keys**: 10+ - **Categories**: 26 major categories - **Helper Functions**: 15+ utility methods ## Quick Reference Table | Category | Key Count | Examples | |----------|-----------|----------| | Authentication | 25+ | login, phone, verifyOTP, register, logout | | Navigation | 10+ | home, products, loyalty, account, more | | Common Actions | 30+ | save, cancel, delete, edit, search, filter | | Status Labels | 20+ | pending, completed, active, expired | | Form Labels | 30+ | name, email, address, company, taxId | | User Types | 5+ | contractor, architect, distributor, broker | | Loyalty System | 50+ | points, rewards, referral, tierBenefits | | Products | 35+ | product, price, cart, sku, brand | | Cart & Checkout | 30+ | cartEmpty, updateQuantity, orderSummary | | Orders & Payments | 40+ | orderNumber, payment, trackOrder | | Projects & Quotes | 45+ | projectName, budget, quotes | | Account & Profile | 40+ | profile, settings, addresses | | Loyalty Transactions | 20+ | earnPoints, redeemPoints, bonusPoints | | Gifts & Rewards | 25+ | myGifts, activeGifts, rewardCategory | | Referral Program | 15+ | referralInvite, shareYourCode | | Validation Messages | 20+ | fieldRequired, invalidEmail | | Error Messages | 15+ | error, networkError, sessionExpired | | Success Messages | 15+ | success, savedSuccessfully | | Loading States | 10+ | loading, processing, syncInProgress | | Empty States | 15+ | noData, noResults, noProductsFound | | Date & Time | 20+ | today, yesterday, minutesAgo | | Notifications | 25+ | notifications, markAsRead | | Chat | 20+ | chat, sendMessage, typingIndicator | | Filters & Sorting | 15+ | filterBy, sortBy, applyFilters | | Offline & Sync | 15+ | offlineMode, syncData, lastSyncAt | | Miscellaneous | 20+ | version, help, feedback, comingSoon | --- ## Summary The Worker app localization system provides: - **Comprehensive Coverage**: 450+ translation keys across 26 categories - **Full Bilingual Support**: Vietnamese (primary) and English (secondary) - **Advanced Features**: Pluralization, parameterization, date/time formatting - **Developer-Friendly**: Helper extensions and utilities for easy integration - **Type-Safe**: Flutter's code generation ensures compile-time safety - **Maintainable**: Clear organization and documentation ### Key Files - `/Users/ssg/project/worker/lib/l10n/app_vi.arb` - Vietnamese translations - `/Users/ssg/project/worker/lib/l10n/app_en.arb` - English translations - `/Users/ssg/project/worker/lib/core/utils/l10n_extensions.dart` - Helper utilities - `/Users/ssg/project/worker/l10n.yaml` - Configuration - `/Users/ssg/project/worker/LOCALIZATION.md` - This documentation --- **Last Updated**: October 17, 2025 **Version**: 1.0.0 **Languages Supported**: Vietnamese (Primary), English (Secondary) **Total Translation Keys**: 450+ **Maintained By**: Worker App Development Team