Files
worker/LOCALIZATION.md
Phuoc Nguyen 628c81ce13 runable
2025-10-17 17:22:28 +07:00

22 KiB

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

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

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

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),
          ),
        ],
      ),
    );
  }
}
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

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

// 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

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):

{
  "newFeature": "New Feature",
  "@newFeature": {
    "description": "Description of the new feature"
  }
}

lib/l10n/app_vi.arb (Vietnamese):

{
  "newFeature": "Tính năng mới",
  "@newFeature": {
    "description": "Description of the new feature"
  }
}

Step 2: Regenerate Localization Files

flutter gen-l10n

Step 3: Use in Code

Text(context.l10n.newFeature)

Parameterized Translations

Simple Parameter

ARB File:

{
  "welcome": "Welcome, {name}!",
  "@welcome": {
    "description": "Welcome message",
    "placeholders": {
      "name": {
        "type": "String",
        "example": "John"
      }
    }
  }
}

Usage:

Text(context.l10n.welcome('John'))
// Result: "Welcome, John!"

Multiple Parameters

ARB File:

{
  "orderSummary": "Order #{orderNumber} for {amount}",
  "@orderSummary": {
    "description": "Order summary text",
    "placeholders": {
      "orderNumber": {
        "type": "String",
        "example": "12345"
      },
      "amount": {
        "type": "String",
        "example": "100,000 ₫"
      }
    }
  }
}

Usage:

Text(context.l10n.orderSummary('12345', '100,000 ₫'))

Number Parameters

ARB File:

{
  "itemCount": "{count} items",
  "@itemCount": {
    "description": "Number of items",
    "placeholders": {
      "count": {
        "type": "int",
        "example": "5"
      }
    }
  }
}

Usage:

Text(context.l10n.itemCount(5))
// Result: "5 items"

Pluralization

Flutter's localization supports pluralization with the ICU message format:

ARB File:

{
  "itemCountPlural": "{count,plural, =0{No items} =1{1 item} other{{count} items}}",
  "@itemCountPlural": {
    "description": "Item count with pluralization",
    "placeholders": {
      "count": {
        "type": "int"
      }
    }
  }
}

Usage:

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:

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

// 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<LanguageNotifier, Locale>((ref) {
  return LanguageNotifier();
});

class LanguageNotifier extends StateNotifier<Locale> {
  LanguageNotifier() : super(const Locale('vi', 'VN')) {
    _loadSavedLanguage();
  }

  Future<void> _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<void> 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

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

class LanguageSelector extends ConsumerWidget {
  @override
  Widget build(BuildContext context, WidgetRef ref) {
    final currentLocale = ref.watch(languageProvider);

    return DropdownButton<Locale>(
      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:

{
  "loginButton": "Login",
  "orderNumber": "Order Number",
  "paymentMethod": "Payment Method"
}

Bad:

{
  "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:

{
  "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:

{
  "greeting": "Hello, {name}!",
  "@greeting": {
    "description": "Greeting message",
    "placeholders": {
      "name": {
        "type": "String",
        "example": "John"
      }
    }
  }
}

Testing Localizations

Widget Tests

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

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:

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

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