Flutter多語言 2022-11-15 周二

方案選擇

  • Flutter對多語言是支持的,不夠功能有限,也不是很好用。

  • GetX中對多語言的支持做得很好,果斷選擇采用。

GetX中關(guān)于多語言介紹

  • 繼承類Translations,以key,value的形式實現(xiàn)多語言。第一級的key為語言選擇,第二級的key為字段標簽。最后的value就是最終顯示的文本。
import 'package:get/get.dart';

class Messages extends Translations {
  @override
  Map<String, Map<String, String>> get keys => {
        'en_US': {
          'hello': 'Hello World',
        },
        'de_DE': {
          'hello': 'Hallo Welt',
        }
      };
}
  • 使用的話,只要加.tr后綴就可以,非常方便
Text('hello'.tr);
  • 通過@標記,還可以帶參數(shù),這個和OC的習慣一致。
import 'package:get/get.dart';


Map<String, Map<String, String>> get keys => {
    'en_US': {
        'logged_in': 'logged in as @name with email @email',
    },
    'es_ES': {
       'logged_in': 'iniciado sesión como @name con e-mail @email',
    }
};

Text('logged_in'.trParams({
  'name': 'Jhon',
  'email': 'jhon@example.com'
  }));
  • 初始化,使用Translations的地方在程序初始化的地方。這也導致Flutter的熱加載不適用于多語言,每次改動都要重新加載,不是很方便。
return GetMaterialApp(
    translations: Messages(), // your translations
    locale: Locale('en', 'US'), // translations will be displayed in that locale
    fallbackLocale: Locale('en', 'UK'), // specify the fallback locale in case an invalid locale is selected.
);
  • 切換語言,其實就是更新Locale,更換Translations的第一級key
var locale = Locale('en', 'US');
Get.updateLocale(locale);
  • 獲取系統(tǒng)語言設(shè)置。也就是獲取系統(tǒng)的語言key,自動設(shè)置。說實話,這作用不大,還不如固定一個默認語言(用在初始化中),然后提供入口進行切換(比如app的設(shè)置頁面)。
    如果非要根據(jù)手機的語言設(shè)置,自動設(shè)置程序的默認語言,那么就可以按照下面的方式來寫
return GetMaterialApp(
    locale: Get.deviceLocale,
);

修改方案

所有的語言定義都在Translations一個文件中,很容易出現(xiàn)文件過長的問題。所以會把兩級的Map進行拆分,把第二級的Map獨立為各個語言文件。

  • 將Translations的第二級Map獨立成文件
class TranslationService extends Translations {
  @override
  Map<String, Map<String, String>> get keys => {
        'en': en_language,
        'es': es_language,
        'zh': zh_language,
      };
}
  • 第二級的語言文件就是一個Map
const Map<String, String> en_language = {
  /// 公共部分-基礎(chǔ)
  WidgetIds.commonBaseTitle: 'Title',
  WidgetIds.commonBaseContent: 'Content',
  WidgetIds.commonBaseDescription: 'Description',
  WidgetIds.commonBaseOk: 'OK',
  WidgetIds.commonBaseCancel: 'Cancel',
  WidgetIds.commonBaseSubmit: 'Submit',
}
  • 各個語言文件的key是公用的,所以集中在一個地方進行定義。
class WidgetIds {
  /// 簡要說明:
  /// 這里定義組件的id
  /// 變量定義采用小駝峰的命名習慣
  /// 變量的值采用小寫加點隔離的方式,類似包名的習慣
  /// 可以用來區(qū)分組件: dart中組件統(tǒng)一用Widget表示,所以這里命名為WidgetIds
  /// 使用場景有多語言,統(tǒng)計等等需要組件定位的場景。
  /// 多語言:使用的時候,需要加上.tr后綴,比如:  WidgetIds.commonBaseTitle.tr
  /// 多語言:定義文件中,作為字典的key來用,比如: WidgetIds.commonBaseTitle: 'Title',

  /// 公共部分-基礎(chǔ)
  static const String commonBaseTitle = 'common.base.title';
  static const String commonBaseContent = 'common.base.content';
  static const String commonBaseDescription = 'common.base.description';
  static const String commonBaseOk = 'common.base.ok';
}
  • 相關(guān)的文件可以放在一個文件夾中
企業(yè)微信截圖_8eeea650-0d45-4533-8170-207242935534.png

簡略方案

  • 將語言定義獨立成單獨的key ,value文件,這個保持不變。

  • 組件id定義,也就是語言文件的key定義,與實際的字符顯示相差較遠。另外定義想名字也是鍵頭疼的事。

  • 使用起來也比較麻煩,像WidgetIds.commonBaseTitle.tr之類的,看上去也不直觀。在這種時候,更容易忘記.tr后綴,導致顯示'common.base.title'之類的內(nèi)容。

  • 如果key對應(yīng)的value沒有定義,那么會直接用key的內(nèi)容來代替,比如Text('hello'.tr); 如果定義了語言文件,會顯示定義過的 'Hello World'或者'Hallo Welt'。但是如果沒有定義對應(yīng)的key,value,那么就會直接顯示'hello'

  • 我們的應(yīng)用,默認語言是英語,所以為圖方便,我們就直接拿英語做key了,這樣還可以少兩個文件。widget_ids.dart這key的定義文件就不需要了。

  • 當然,這樣做對于那種給@參數(shù)的用法就不適用了。凡事有利有弊。當然,要用也是可以的,把兩種思路結(jié)合起來:大部分用英文作為key;帶參數(shù)的,就自定義key。

  • 其他的語言文件,直接以英語當做key

const Map<String, String> zh_language = {
  /// 公共部分-基礎(chǔ)
  'Title': '標題',
  'Content': '內(nèi)容',
  'Description': '描述',
  'OK': '確定',
  'Cancel': '取消',
}
  • 適用的地方直接在英語后面加個.tr后綴就可以了。
    Text(
      'Cancel'.tr,
      style: TextStyle(
        color: const Color(0xFF333333),
        fontSize: 12.sp,
        fontWeight: FontWeight.w400,
      ),
    );

Locale簡介

  • 構(gòu)造函數(shù)需要兩個字符串,語言代碼和地區(qū)代碼。其中語言代碼是必選的,地區(qū)代碼是可選的
const Locale(
    this._languageCode, [
    this._countryCode,
  ])
  • iOS的手機的菜單路徑: 設(shè)置 =》通用 =》語言與地區(qū)
企業(yè)微信截圖_b4c1ae98-aab3-41bc-92c5-d4f6be5b9384.png
  • 可以理解為語言代碼是主鍵,地區(qū)代碼是副鍵。比如同樣是中文,也區(qū)分中文大陸,中文香港,中文新加坡,中文臺灣等。
    語言地區(qū)代碼
企業(yè)微信截圖_19fd2a9c-c866-414e-9a99-e2f245f2df72.png
  • Translation。s中的key,也是以語言代碼為主,地區(qū)代碼為輔。中劃線用下劃線代替。
    另外,也可以簡單處理,只用語言代碼,不要地區(qū)代碼。就算地區(qū)代碼對不上,只要語言代碼對了,也能選中。
class MultiLanguage extends Translations {
  @override
  Map<String, Map<String, String>> get keys => {
        /// 英文
        'en_US': en_US,

        /// 中文
        'zh_CN': zh_CN,
      };
}

更新的話,給Get.updateLocale(const Locale('en', 'TT'));仍然能夠選出英文。
既然如此,干脆key值只給語言代碼,不管區(qū)域代碼。

簡單實踐

  • 只管語言代碼,不管區(qū)域代碼,簡單化處理。當前的需求,只要求有中文版和英文版,還沒有那么細。

  • 初始化取手機的系統(tǒng)設(shè)置;

  • 默認語言設(shè)定為中文

  • main.dart中的設(shè)置

企業(yè)微信截圖_7ad8d70c-d49a-4abe-9a1d-6a02ace8c5a6.png
  • 字典中只管語言代碼,不管區(qū)域代碼
import 'en.dart';
import 'zh.dart';

class MultiLanguage extends Translations {
  @override
  Map<String, Map<String, String>> get keys => {
        /// 英文
        'en': en,

        /// 中文
        'zh': zh,
      };
}
/// 中文多語言字典
const Map<String, String> zh = {
  'title': '這是標題',
  'login': '登錄用戶 @name,郵箱賬號 @email',
};
/// 英文多語言字典
const Map<String, String> en = {
  'title': 'This is Title!',
  'login': 'logged in as @name with email @email',
};
  • 切換時只給語言代碼,不管區(qū)域代碼
Get.updateLocale(const Locale('en'));
  • 簡單例子
    代碼
import 'package:flutter/material.dart';
import 'package:get/get.dart';

import 'home_controller.dart';

class HomePage extends GetView<HomeController> {
  const HomePage({Key? key}) : super(key: key);

  @override
  Widget build(BuildContext context) {
    return GetBuilder<HomeController>(
      builder: (context) {
        return Scaffold(
          appBar: AppBar(
            title: const Text('HomePage'),
            centerTitle: true,
          ),
          body: Center(
            child: GestureDetector(
              behavior: HitTestBehavior.opaque,
              onTap: () {
                Get.updateLocale(const Locale('en'));
              },
              child: Text(
                'login'.trParams(
                    {'name': 'zhang san', 'email': 'zhangsan@baidu.com'}),
                style: const TextStyle(fontSize: 20),
                textAlign: TextAlign.center,
              ),
            ),
          ),
        );
      },
    );
  }
}

初始界面:(手機設(shè)置為中文)

企業(yè)微信截圖_d81b0547-16a3-478b-9eb1-99157ec2fdfa.png

點一下文字,就切換為英文

企業(yè)微信截圖_3b40853e-876c-4b76-be43-ccbafed21f78.png
  • 關(guān)于key的定義
    (1)如果想規(guī)范一點,那么就用全局變量,按照組件id的的模式統(tǒng)一規(guī)范;
    (2)如果想方便一點,就用英語作為key。那么en.dart絕大部分可以key和value一樣;
    zh.dart就算沒定義,也會顯示有意義的英文,既方便又使用。

我們一開始是用方法(1)的;很規(guī)范,但是真的煩。 后來就改成了方法(2);工作量減少很多。

系統(tǒng)日期組件多語言

  • 系統(tǒng)的DateTime組件有一個locale參數(shù),直接加上會崩潰。

  • 如果想要這種系統(tǒng)組件支持多語言,需要額外引入一個庫flutter_localizations

  • 參考文章:

Flutter配置國際化localizations

實踐結(jié)果

以上內(nèi)容是一開始時的做法,后來做了修改。

  1. 命名采用統(tǒng)一命名的方式: “模塊.頁面.其他限定”,全部用小寫字符,單詞間用下劃線_分割,也就是Linux命名方式。例如:
  'order.cancel_order.pop.title': 'Cancel Successfully!',
  'order.cancel_order.pop.info1': 'The refund is here',
  'order.cancel_order.pop.info2': 'Pandabuy account balance',

訂單模塊,取消訂單,彈窗的標題和信息

  1. 本地只保留一份語音文件,作為默認值;

  2. 其他多語言文件放后臺,到時候通過接口下載。

  3. 做一個編輯后臺,讓其他部門操作,生成新的語言文件。

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時請結(jié)合常識與多方信息審慎甄別。
平臺聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點,簡書系信息發(fā)布平臺,僅提供信息存儲服務(wù)。

相關(guān)閱讀更多精彩內(nèi)容

友情鏈接更多精彩內(nèi)容