Nguồn : Internationalizing Phần này hướng dẫn app bạn có thể hỗ trợ nhiều ngôn ngữ. Tham khảo source code gen_l10n_example

I. Cấu Hình
1.1. Cài đặt Package
  • Mở terminal, thực hiện các lệnh sau :

    flutter pub add flutter_localizations --sdk=flutter
    flutter pub add intl:any
    
  • Sau khi cài đặt xong, mở file pubspec.yml kiểm tra, có các khai báo như bên dưới thì ok.

    dependencies:
      flutter:
        sdk: flutter
      flutter_localizations:
        sdk: flutter
      intl: any
    
1.2. Cấu hình localizationsDelegatessupportedLocales
  • Thực hiện bổ sung đoạn code dưới đây :

    import 'package:flutter_localizations/flutter_localizations.dart';
    //-----------------------------------------------------------------
    return const MaterialApp(
      title: 'Localizations Sample App',
      localizationsDelegates: [
        GlobalMaterialLocalizations.delegate,
        GlobalWidgetsLocalizations.delegate,
        GlobalCupertinoLocalizations.delegate,
      ],
      supportedLocales: [
        Locale('en'), // English
        Locale('es'), // Spanish
      ],
      home: MyHomePage(),
    );
    
  • Trong đó :

    • GlobalMaterialLocalizations : Cung cấp các chuỗi localized cho Material Components
    • GlobalCupertinoLocalizations : Cung cấp các chuỗi localized cho Cupertino Components
    • GlobalWidgetsLocalizations : Hướng text (left-to-right, right-to-left…)
1.3. Tạo các localized messages cho App của bạn
  • Bước 1 :: Thêm package intl

    flutter pub add intl:any
    
  • Bước 2 : Mở file pubspec.yaml Set flag generate = true.

    flutter:
      generate: true
    
  • Bước 3 : Tạo file l10n.yaml tại vị trí root project của bạn, mở file và thêm các đoạn cấu hình như dưới đây :

    ```dart
    arb-dir: lib/l10n  
    template-arb-file: app_en.arb
    output-localization-file: app_localizations.dart
    

    Trong đó :

    • arb-dir : là folder chứa file .arb, ${FLUTTER_PROJECT}/lib/l10n
    • template-arb-file : định dạng tên file .arb
    • output-localization-file : dựa vào file .arb, flutter sẽ thực hiện generate code dart
  • Bước 5 : Thêm các giá trị message cần thiết cho app của bạn vào các file .arb

    • Ví dụ: Trong file app_en.arb
    "helloWorld": "Hello World!",
    
    • Trong file app_vi_arb
    "helloWorld": "Xin chào các bạn!",
    
  • Bước 6 : Thực hiện tạo file bằng lệnh flutter gen-l10n. Bạn sẽ tìm thấy các file đã được generate ở ${FLUTTER_PROJECT}/.dart_tool/flutter_gen/gen_l10n

  • Bước 7: Thay đổi các giá trị localizationsDelegatessupportedLocales. Ở đầu bài viết, chúng ta chỉ định dựa vào các constants của thư viện.

    localizationsDelegates: [
        GlobalMaterialLocalizations.delegate,
        GlobalWidgetsLocalizations.delegate,
        GlobalCupertinoLocalizations.delegate,
      ],
      supportedLocales: [
        Locale('en'), // English
        Locale('es'), // Spanish
      ],
    

    Hãy thay đổi các giá trị này bằng các giá trị đã được generate.

    const MaterialApp(
      title: 'Localizations Sample App',
      localizationsDelegates: AppLocalizations.localizationsDelegates,
      supportedLocales: AppLocalizations.supportedLocales,
    );
    
  • Bước 8 : Các bước setup đã ok, giờ thì sử dụng thôi. Mỗi keywork bạn khai báo trong các file .arb khi generate ra code dart sẽ là một getter. Ví dụ ở đây, chúng ta có thể truy cập key helloWorld như là một getter.

    Text(AppLocalizations.of(context)!.helloWorld
    
II. Placeholders, plurals, and selects
2.1 Placeholders
  • Trong app bạn cần một số chuỗi cụ thể, chúng giống nhau hoàn toàn chỉ khác một giá trị. Bình thường, chúng ta sẽ tạo ra các key như thế này :

    "hellotam" : "Hello Tâm"
    "hellohuy" : "Hello Huy"
    "hellohung" : "Hello Hùng"
    
  • Chúng ta có thể thấy, chuỗi Hello là giống nhau. Có cách nào tái sử dụng nó không ?. Placeholder sẽ giúp chúng ta làm việc này.
    Khai báo placeholder trong file .arb :

    "hello": "Hello {userName}",
    "@hello": {
    "description": "A message with a single parameter",
    "placeholders": {
      "userName": {
        "type": "String",
        "example": "Bob"
      }
    }
    }
    
  • Các giá trị trên có ý nghĩa như thế nào khi generate ra code dart :

    • @hello : sẽ tạo ra method hello
    • description : Mô tả cho method hello
    • userName : Tên tham số của method hello
    • type: Kiểu dữ liệu của tham số

      Text(AppLocalizations.of(context)!.hello('Tâm')),
      // Kết quả sẽ là : "Hello Tâm";
      
2.2 Plurals
  • Trở lại với vị dụ của Placeholders. Mình sẽ thêm 2 giá trị hellohehelloshe, và đặt tinh huống là hai giá trị này dùng rất nhiều trong app.

    "hellotam" : "Hello Tâm"
    "hellohuy" : "Hello Huy"
    "hellohung" : "Hello Hùng"
    "hellohe" : "Hello Anh"
    "helloshe" : "Hello Chị"
    
  • Nếu áp dụng placeholder như 2.1 thì ở mỗi nơi gọi mình lại cần truyền Anh hoặc Chị thì không hay. Plurals giúp chúng ta cải thiện điều này

    "hello": "Hello {userName, plural, =0{Anh} =1{Chi} other}",
    "@hello": {
    "description": "A message with a single parameter",
    "placeholders": {
      "userName": {
        "type": "String",
        "example": "Bob"
      }
    }
    
  • Các giá trị trên sẽ giống với placeholder khi generate ra code dart. Nhưng chúng ta có lưu ý nơi định nghĩa chuỗi hello

    "hello": "Hello {userName, plural, =0{Anh} =1{Chi} other}",
    ```Hello Anh
    
  • Trong đó :

    • userName : chính là tham số của method hello
    • plural : chỉ định keyword này để thông báo rằng chúng ta đang dùng plural
    • =0{Anh} hoặc =1{Chi} : Lúc này method hello sẽ nhận tham số là kiểu int
    • other{userName} : Lúc này method hello sẽ nhận tham số là chuỗi
    Text(AppLocalizations.of(context)!.hello(0)),
    // Kết quả sẽ là : Hello Anh
    
2.3. Selects
  • Chúng ta thấy Plurals hỗ trợ các giá trị cố định. Nhưng khi sử dụng ta lại truyền index 0, 1 để lấy giá trị ra, nó không tường mình cho lắm, khi đọc code chúng ta lại phải kiểm tra xem nó trả về giá trị gì . Biết vậy package cung cấp cho chúng ta khái niệm Select

  • Về phân khai báo cũng giống như placeholder nên chúng ta quan tâm đến phần đĩnh nghĩa chuỗi : Cách định nghĩa của một Plurals

    "hello": "Hello {userName, plural, =0{Anh} =1{Chi} other}"
    
  • Khi dùng Select hãy đổi nó thành như thế này :

    "hello": "Hello {userName, select, he{Anh}, she{Chị}  other}"
    
  • Và khi sử dụng

    Text(AppLocalizations.of(context)!.hello("he")),
    // Kết quả là : Hello Anh
    
III. Escaping syntax
  • Chúng ta thấy nhưng giá trị đặ trong dấu ngoặc {...} được xem như tham số. Trường hợp chúng ta vẫn muốn nó hiển thị như mổt text bình thường được không ? Câu trả lời trả được chứ =)). Mở file l10n.yaml và thêm đoạn này :

    use-escaping: true
    
  • Lúc này để trong file .arb, để đánh dấu giá trị {} là chuỗi, chúng ta chỉ cần thêm kí tự nháy đơn phía trước '

    {
      "helloWorld": "Hello! '{Isn''t}' this a wonderful day?"
    }
    
IV. Một số format với số
  • Bảng dưới đây, liệt kê một số format khi dùng số :

    Message “format” value Output for 1200000
    compact “1.2M”
    compactCurrency* “$1.2M”
    compactSimpleCurrency* “$1.2M”
    compactLong “1.2 million”
    currency* “USD1,200,000.00”
    decimalPattern “1,200,000”
    decimalPercentPattern* “120,000,000%”
    percentPattern “120,000,000%”
    scientificPattern “1E6”
    simpleCurrency* “$1,200,000”
  • Ví dụ :

    "numberOfDataPoints": "Number of data points: {value}",
    "@numberOfDataPoints": {
      "description": "A message with a formatted int parameter",
      "placeholders": {
        "value": {
          "type": "int",
          "format": "compactCurrency",
          "optionalParameters": {
            "decimalDigits": 2
          }
        }
      }
    }
    
VI. Formart DateTime
  • Ví dụ sử dụng date format

    "helloWorldOn": "Hello World on {date}",
    "@helloWorldOn": {
      "description": "A message with a date parameter",
      "placeholders": {
        "date": {
          "type": "DateTime",
          "format": "yMd"
        }
      }
    }
    
  • Khi sử dụng

    AppLocalizations.of(context).helloWorldOn(DateTime.utc(1959, 7, 9))
    
VII. Localizing for iOS: Updating the iOS app bundle
  • Trong iOS app, chúng ta cần khai báo các locale mà app hỗ trợ. Tại mở file info.plist theo đường dẫn : ${FLUTTER_PROJECT}/ios/Runner/Info.plist. Thực hiện khai báo các locale mà app hỗ trợ

    <key>CFBundleLocalizations</key>
    <array>
        <string>en</string>
        <string>vi</string>
    </array>
    
VIII. Advanced topics for further customization
8.1. Advanced locale definition
  • Một số ngôn ngữ hỗ trợ nhiều language code, chúng ta cần chỉ định chính xác nó. Ví dụ, tiếng Trung Quốc rất đang dạng nên chung ta cần chỉ định cụ thể language code, script code, country code thông qua contructor fromSubtags

    supportedLocales: [
      Locale.fromSubtags(languageCode: 'zh'), // generic Chinese 'zh'
      Locale.fromSubtags(
          languageCode: 'zh',
          scriptCode: 'Hans'), // generic simplified Chinese 'zh_Hans'
      Locale.fromSubtags(
          languageCode: 'zh',
          scriptCode: 'Hant'), // generic traditional Chinese 'zh_Hant'
      Locale.fromSubtags(
          languageCode: 'zh',
          scriptCode: 'Hans',
          countryCode: 'CN'), // 'zh_Hans_CN'
      Locale.fromSubtags(
          languageCode: 'zh',
          scriptCode: 'Hant',
          countryCode: 'TW'), // 'zh_Hant_TW'
      Locale.fromSubtags(
          languageCode: 'zh',
          scriptCode: 'Hant',
          countryCode: 'HK'), // 'zh_Hant_HK'
    ],
    
8.2. Tracking the locale: The Locale class and the Localizations widget
  • Locale là class đại diện cho một ngôn ngữ. Localizations là widget dùng để chỉ định locale cho các widget con. Để bạn biết UI hiện tại đang dùng Locale gì, bạn có thể sử dụng hàm localeOf

    Locale myLocale = Localizations.localeOf(context);
    
8.3. Specifying the app’s supported­Locales parameter
  • Tham số supportedLocales của MaterialApp hạn chế thay đổi. Khi User thay đổi cài đặt trên thiết bị , Localizations widget sẽ cập nhật locale dựa trên độ ưu tiên sau :
    • LocaleNew có trong danh sách mà app đã khai báo
    • Dùng locale đầu tiền mà có languageCode match với LocaleNew
    • Dùng locale đầu tiên trong danh sách supportedLocales localeResolutionCallback : TBD
8.4. Configuring the l10n.yaml
`l10n.yaml` file cho bạn cấu hình để tool `gen-l10n` làm việc :
  • Nơi tất các file input được đặt.
  • Nơi tất các output nên được tạo
  • Tên class Dart mà thể hiện localizations delegate Tham khảo : Configuring the l10n.yaml file
IX. Cách Internationalization Làm Việc
9.1. Loading and retrieving localized values
  • Localizations widget dùng để load và tìm kiếm các giá trị localized. Nếu device của bạn thay đổi locale, widget này sẽ load tự động các giá trị từ locale mới và rebuild lại widget. Localizations làm việc giống như InheritedWidget. Nghĩa là widget con của Localizations chỉ bị rebuild khi có truy cập Text(AppLocalizations.of(context)

  • LocalizationsDelegate có hàm load dùng để load các giá trị từ file .arb vào memory mỗi khi có sự thay đổi locale.

To be Contiune…