i18n.dart 5.9 KB

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