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

761 lines
22 KiB
Markdown

# 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<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
```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<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:**
```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