i18n.dart 5.2 KB

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