i18n.dart 6.0 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218
  1. // ignore_for_file: non_constant_identifier_names
  2. import 'package:flutter/services.dart';
  3. import 'package:flutter/widgets.dart';
  4. import 'types.dart';
  5. import 'key_book.dart';
  6. import 'dart:convert';
  7. export 'key_book.dart';
  8. export 'types.dart' hide FTrMapPickExtension, ModuleBase, IPropQueryable;
  9. const String _PACKAGE_NAME = "fis_i18n";
  10. typedef FTrKeybookBuilder = Future<FTrKeybook> Function();
  11. typedef FI18nLocaleUpdater = void Function(Locale locale);
  12. /// 中文
  13. const Locale CHINESE_LOCALE = const Locale("zh", "CN");
  14. /// 英文
  15. const Locale ENGLISH_LOCALE = const Locale("en", "US");
  16. /// 俄语
  17. const Locale RUSSIAN_LOCALE = const Locale("ru", "RU");
  18. /// 西班牙语
  19. const Locale SPANISH_LOCALE = const Locale("es", "ES");
  20. /// 罗马尼亚语
  21. const Locale ROMANIA_LOCALE = const Locale("ro", "RO");
  22. AssetBundle _assetBundle = rootBundle;
  23. /// 翻译字典选项
  24. ///
  25. /// [locale] 语言
  26. ///
  27. /// [builder] 字典建造器
  28. class FTrKeybookOption {
  29. const FTrKeybookOption(this.locale, this.builder);
  30. final Locale locale;
  31. final FTrKeybookBuilder builder;
  32. /// 中文字典建造器
  33. static final FTrKeybookOption ChineseOption = FTrKeybookOption(
  34. CHINESE_LOCALE,
  35. () => _buildInnerBoook(CHINESE_LOCALE),
  36. );
  37. /// 英文字典建造器
  38. static final FTrKeybookOption EnglishOption = FTrKeybookOption(
  39. ENGLISH_LOCALE,
  40. () => _buildInnerBoook(ENGLISH_LOCALE),
  41. );
  42. /// 俄语字典建造器
  43. static final FTrKeybookOption RussianOption = FTrKeybookOption(
  44. RUSSIAN_LOCALE,
  45. () => _buildInnerBoook(RUSSIAN_LOCALE),
  46. );
  47. /// 西班牙语字典建造器
  48. static final FTrKeybookOption SpanishOption = FTrKeybookOption(
  49. SPANISH_LOCALE,
  50. () => _buildInnerBoook(SPANISH_LOCALE),
  51. );
  52. /// 罗马尼亚字典建造器
  53. static final FTrKeybookOption RomaniaOption = FTrKeybookOption(
  54. ROMANIA_LOCALE,
  55. () => _buildInnerBoook(ROMANIA_LOCALE),
  56. );
  57. }
  58. /// 国际化翻译字典
  59. FTrKeybook get i18nBook => FI18n.ins.currentBook;
  60. /// 国际化
  61. class FI18n {
  62. FI18n._();
  63. static FI18n? _singleton;
  64. /// 单例
  65. static FI18n get ins {
  66. if (_singleton == null) {
  67. throw Exception("There aren't any instance of FI18n.");
  68. }
  69. return _singleton!;
  70. }
  71. late FTrKeybook _currentBook;
  72. late FI18nLocaleUpdater _localeUpdater;
  73. final Map<Locale, FTrKeybookBuilder> _bookMap = {};
  74. /// 当前语言字典
  75. FTrKeybook get currentBook => _currentBook;
  76. /// 当前是否中文
  77. bool get isCurrentChinese => currentBook.isCurrentChinese;
  78. /// 支持的语言
  79. List<Locale> get supportedLocales => _bookMap.keys.toList();
  80. /// 设置内置资源包
  81. static void setAssetBundle(AssetBundle bundle) {
  82. _assetBundle = bundle;
  83. }
  84. /// 初始化
  85. static Future<void> init(
  86. FI18nLocaleUpdater localeUpdater,
  87. List<FTrKeybookOption> bookOptions,
  88. ) async {
  89. if (_singleton != null) {
  90. throw Exception("FI18n has been init.");
  91. }
  92. _singleton = FI18n._();
  93. _singleton!._localeUpdater = localeUpdater;
  94. await _singleton!._buildInstance(bookOptions);
  95. }
  96. Future<void> _buildInstance(List<FTrKeybookOption> bookOptions) async {
  97. bookOptions.forEach((option) {
  98. if (_bookMap.containsKey(option.locale)) {
  99. throw Exception("FI18n init: The same locale value already exists.");
  100. }
  101. setKeyBook(option.locale, option.builder);
  102. });
  103. }
  104. /// 加载(初始语言,init之后)
  105. static Future<void> load(Locale locale) async {
  106. // 语言+国家 精准匹配
  107. var bookBuilder = _singleton!._bookMap[locale];
  108. // 语言匹配
  109. if (bookBuilder == null) {
  110. final matchKey = _singleton!._bookMap.keys.firstWhere(
  111. (x) => x.languageCode == locale.languageCode,
  112. orElse: () => ENGLISH_LOCALE,
  113. );
  114. bookBuilder = _singleton!._bookMap[matchKey];
  115. }
  116. if (bookBuilder == null)
  117. throw Exception(
  118. "FI18n load error: No book-builder match the target locale.");
  119. _singleton!._currentBook = await bookBuilder();
  120. }
  121. /// 切换语言
  122. Future<Locale> switchLanguage([Locale? locale]) async {
  123. Locale matchLocale;
  124. if (locale == null) {
  125. // 顺序找下一个字典
  126. final keys = _bookMap.keys.toList();
  127. final curIdx = keys.indexOf(currentBook.locale);
  128. int nextIdx = curIdx + 1;
  129. if (nextIdx >= keys.length) nextIdx = 0;
  130. matchLocale = keys[nextIdx];
  131. } else {
  132. // 优先语言Code和国家Code双匹配,否则选语言Code匹配
  133. final langCodeMatchList =
  134. _bookMap.keys.where((x) => x.languageCode == locale.languageCode);
  135. if (langCodeMatchList.isEmpty) {
  136. // 无匹配
  137. throw Exception(
  138. "FI18n switchLanguage: No keybook found for the target locale.");
  139. }
  140. if (langCodeMatchList.length > 1) {
  141. // 查找双匹配
  142. matchLocale = langCodeMatchList.firstWhere(
  143. (x) => x.countryCode == locale.languageCode,
  144. orElse: () => langCodeMatchList.first,
  145. );
  146. } else {
  147. // 单匹配
  148. matchLocale = langCodeMatchList.first;
  149. }
  150. }
  151. FTrKeybookBuilder builder = _bookMap[matchLocale]!;
  152. _currentBook = await builder();
  153. _localeUpdater(matchLocale);
  154. return matchLocale;
  155. }
  156. /// 设置翻译字典(相同语言会覆盖)
  157. void setKeyBook(Locale locale, FTrKeybookBuilder bookBuilder) {
  158. _bookMap[locale] = bookBuilder;
  159. }
  160. /// 根据字典树翻译
  161. ///
  162. /// [keyTree] 字典树,以.分隔,例:common.loading
  163. String translate(String keyTree) {
  164. final tiers = keyTree.split('.');
  165. dynamic curr = currentBook;
  166. for (var key in tiers) {
  167. if (curr is IPropQueryable) {
  168. curr = curr.getProp(key);
  169. } else {
  170. return keyTree;
  171. }
  172. }
  173. return curr;
  174. }
  175. }
  176. Future<FTrKeybook> _buildInnerBoook(Locale locale) async {
  177. final code = locale.toCodeString();
  178. final assetKey = "packages/$_PACKAGE_NAME/assets/$code.json";
  179. final byteData = await rootBundle.load(assetKey);
  180. final text = utf8.decode(byteData.buffer.asUint8List());
  181. final book = FTrKeybook.createFromJson(text);
  182. return book;
  183. }