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-l10ntool - 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 itemsordersCount- 0/1/many ordersprojectsCount- 0/1/many projectsdaysRemaining- 0/1/many days
Parameterized Translations
welcomeTo(appName)- Dynamic app nameotpSentTo(phone)- Phone numberpointsToNextTier(points, tier)- Points and tierredeemConfirmMessage(points, reward)- Redemption confirmationorderNumberIs(orderNumber)- Order number displayestimatedDeliveryDate(date)- Delivery date
Date/Time Formatting
formatDate- DD/MM/YYYY (VI) or MM/DD/YYYY (EN)formatDateTime- Full date-time with localeminutesAgo,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),
),
],
),
);
}
}
Using Extension for Cleaner Code (Recommended)
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→ UsecontinueButtoninsteadswitch→ UseswitchButtoninsteadclass→ UseclassNameinsteadreturn→ UsereturnButtoninstead
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:
- Run
flutter gen-l10n - Hot restart (not hot reload) the app
- Clear build cache if needed:
flutter clean
Translation Workflow
For Developers
- Add English translation to
app_en.arb - Add Vietnamese translation to
app_vi.arb - Run code generator:
flutter gen-l10n - Use in code:
context.l10n.newKey - Test both languages
For Translators
- Review the English ARB file (
app_en.arb) - Translate each key to Vietnamese in
app_vi.arb - Maintain the same structure and placeholders
- Add
@keydescriptions if needed - 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