本次提交包含多项迭代优化和问题修复: 1. 新增缩略图图片组件、数字格式化工具类,补充多语言翻译类型与本地化支持 2. 优化底部导航栏主题色统一使用动态accent色值 3. 修复多处图表动画、路由跳转、API请求相关问题 4. 简化服务器公告文案,调整默认分屏状态为关闭 5. 新增安卓/iOS桌面快捷方式配置 6. 重构多处状态管理类使用SafeNotifierInit统一异常保护 7. 替换硬编码蓝色为主题色,更新版本号获取方式为动态读取 8. 优化缓存预加载逻辑,移除无用代码 9. 调整默认设置项,优化用户体验细节
213 lines
6.4 KiB
Dart
213 lines
6.4 KiB
Dart
/// ============================================================
|
||
/// 闲言APP — 应用语言枚举与Locale解析
|
||
/// 创建时间: 2026-05-19
|
||
/// 更新时间: 2026-05-31
|
||
/// 作用: 定义所有支持的语言,提供Locale解析/RTL检测/机器翻译标注/系统语言监听
|
||
/// 部分英文字段 可以硬编码 不需要翻译
|
||
/// 上次更新: 新增isMachineTranslated属性,覆盖率低于80%的语言标注为机器翻译
|
||
/// ============================================================
|
||
|
||
import 'package:flutter/widgets.dart';
|
||
import 'package:flutter_riverpod/flutter_riverpod.dart';
|
||
import 'package:intl/intl.dart' hide TextDirection;
|
||
|
||
import '../../features/mine/settings/providers/general_settings_provider.dart';
|
||
|
||
const _rtlLanguageCodes = {'ar', 'he', 'fa', 'ur'};
|
||
|
||
enum AppLocale {
|
||
system('system', '跟随系统', 'Follow System', null, null, '🌐'),
|
||
zhCN('zh_CN', '简体中文', 'Simplified Chinese', 'zh', 'CN', '🇨🇳'),
|
||
en('en', 'English', 'English', 'en', null, '🇺🇸'),
|
||
ja('ja', '日本語', 'Japanese', 'ja', null, '🇯🇵'),
|
||
zhTW('zh_TW', '繁體中文', 'Traditional Chinese', 'zh', 'TW', '🇨🇳'),
|
||
ko('ko', '한국어', 'Korean', 'ko', null, '🇰🇷'),
|
||
de('de', 'Deutsch', 'German', 'de', null, '🇩🇪'),
|
||
it('it', 'Italiano', 'Italian', 'it', null, '🇮🇹'),
|
||
es('es', 'Español', 'Spanish', 'es', null, '🇪🇸'),
|
||
ar('ar', 'العربية', 'Arabic', 'ar', null, '🇸🇦'),
|
||
bn('bn', 'বাংলা', 'Bengali', 'bn', null, '🇧🇩'),
|
||
hi('hi', 'हिन्दी', 'Hindi', 'hi', null, '🇮🇳'),
|
||
pt('pt', 'Português', 'Portuguese', 'pt', null, '🇧🇷'),
|
||
ru('ru', 'Русский', 'Russian', 'ru', null, '🇷🇺'),
|
||
fr('fr', 'Français', 'French', 'fr', null, '🇫🇷');
|
||
|
||
const AppLocale(
|
||
this.id,
|
||
this.nativeName,
|
||
this.englishName,
|
||
this.languageCode,
|
||
this.countryCode,
|
||
this.flag,
|
||
);
|
||
|
||
final String id;
|
||
final String nativeName;
|
||
final String englishName;
|
||
final String? languageCode;
|
||
final String? countryCode;
|
||
final String flag;
|
||
|
||
bool get isRTL {
|
||
if (this == AppLocale.system) return false;
|
||
return _rtlLanguageCodes.contains(languageCode);
|
||
}
|
||
|
||
/// 是否为机器翻译(覆盖率低于80%的语言)
|
||
bool get isMachineTranslated {
|
||
switch (this) {
|
||
case AppLocale.system:
|
||
case AppLocale.zhCN:
|
||
case AppLocale.en:
|
||
case AppLocale.zhTW:
|
||
case AppLocale.ja:
|
||
case AppLocale.ko:
|
||
return false;
|
||
case AppLocale.de:
|
||
case AppLocale.it:
|
||
case AppLocale.es:
|
||
case AppLocale.fr:
|
||
case AppLocale.ru:
|
||
case AppLocale.pt:
|
||
return false;
|
||
case AppLocale.ar:
|
||
case AppLocale.bn:
|
||
case AppLocale.hi:
|
||
return true;
|
||
}
|
||
}
|
||
|
||
TextDirection get textDirection =>
|
||
isRTL ? TextDirection.rtl : TextDirection.ltr;
|
||
|
||
Locale get locale {
|
||
if (this == AppLocale.system) {
|
||
return WidgetsBinding.instance.platformDispatcher.locale;
|
||
}
|
||
if (countryCode != null) return Locale(languageCode!, countryCode);
|
||
return Locale(languageCode!);
|
||
}
|
||
|
||
static AppLocale fromId(String id) {
|
||
return AppLocale.values.firstWhere(
|
||
(e) => e.id == id,
|
||
orElse: () => AppLocale.system,
|
||
);
|
||
}
|
||
|
||
static List<AppLocale> get selectable =>
|
||
AppLocale.values.where((e) => e != AppLocale.system).toList();
|
||
|
||
static List<AppLocale> smartSorted() {
|
||
final locales = selectable.toList();
|
||
final systemLocale = WidgetsBinding.instance.platformDispatcher.locale;
|
||
final systemLang = systemLocale.languageCode;
|
||
final systemCountry = systemLocale.countryCode;
|
||
|
||
locales.sort((a, b) {
|
||
final aScore = _relevanceScore(a, systemLang, systemCountry);
|
||
final bScore = _relevanceScore(b, systemLang, systemCountry);
|
||
return bScore.compareTo(aScore);
|
||
});
|
||
|
||
return locales;
|
||
}
|
||
|
||
static int _relevanceScore(
|
||
AppLocale locale,
|
||
String systemLang,
|
||
String? systemCountry,
|
||
) {
|
||
if (locale.languageCode == systemLang) {
|
||
if (locale.countryCode != null && locale.countryCode == systemCountry) {
|
||
return 100;
|
||
}
|
||
return 80;
|
||
}
|
||
switch (locale.id) {
|
||
case 'zh_CN':
|
||
return 50;
|
||
case 'en':
|
||
return 40;
|
||
default:
|
||
return 20;
|
||
}
|
||
}
|
||
}
|
||
|
||
Locale _resolveSystemLocale(Locale systemLocale) {
|
||
final supported = AppLocale.selectable;
|
||
for (final appLocale in supported) {
|
||
if (appLocale.countryCode != null) {
|
||
if (systemLocale.languageCode == appLocale.languageCode &&
|
||
systemLocale.countryCode == appLocale.countryCode) {
|
||
return appLocale.locale;
|
||
}
|
||
}
|
||
if (systemLocale.languageCode == appLocale.languageCode) {
|
||
return appLocale.locale;
|
||
}
|
||
}
|
||
if (systemLocale.languageCode == 'zh') {
|
||
return AppLocale.zhCN.locale;
|
||
}
|
||
return AppLocale.zhCN.locale;
|
||
}
|
||
|
||
bool _isSystemLocaleRTL() {
|
||
final systemLocale = WidgetsBinding.instance.platformDispatcher.locale;
|
||
return _rtlLanguageCodes.contains(systemLocale.languageCode);
|
||
}
|
||
|
||
final systemLocaleVersionProvider =
|
||
NotifierProvider<SystemLocaleVersionNotifier, int>(
|
||
SystemLocaleVersionNotifier.new,
|
||
);
|
||
|
||
class SystemLocaleVersionNotifier extends Notifier<int> {
|
||
@override
|
||
int build() => 0;
|
||
|
||
void increment() => state = state + 1;
|
||
}
|
||
|
||
final appLocaleProvider = Provider<Locale>((ref) {
|
||
ref.watch(systemLocaleVersionProvider);
|
||
final settings = ref.watch(generalSettingsProvider);
|
||
final languageId = settings.languageId;
|
||
|
||
if (languageId == 'system') {
|
||
final systemLocale = WidgetsBinding.instance.platformDispatcher.locale;
|
||
return _resolveSystemLocale(systemLocale);
|
||
}
|
||
|
||
final appLocale = AppLocale.fromId(languageId);
|
||
return appLocale.locale;
|
||
});
|
||
|
||
final appTextDirectionProvider = Provider<TextDirection>((ref) {
|
||
ref.watch(systemLocaleVersionProvider);
|
||
final settings = ref.watch(generalSettingsProvider);
|
||
final languageId = settings.languageId;
|
||
|
||
if (languageId == 'system') {
|
||
return _isSystemLocaleRTL() ? TextDirection.rtl : TextDirection.ltr;
|
||
}
|
||
|
||
return AppLocale.fromId(languageId).textDirection;
|
||
});
|
||
|
||
final supportedLocalesProvider = Provider<List<Locale>>((ref) {
|
||
return AppLocale.selectable.map((e) => e.locale).toList();
|
||
});
|
||
|
||
final localeDateFormatProvider = Provider<DateFormat>((ref) {
|
||
final locale = ref.watch(appLocaleProvider);
|
||
return DateFormat.yMMMd(locale.toLanguageTag());
|
||
});
|
||
|
||
final localeNumberFormatProvider = Provider<NumberFormat>((ref) {
|
||
final locale = ref.watch(appLocaleProvider);
|
||
return NumberFormat.decimalPattern(locale.toLanguageTag());
|
||
});
|