Files
xianyan/lib/l10n/app_locale.dart
Developer 9ea8d3d606 chore: 汇总批量提交的功能优化与bug修复
本次提交包含多项迭代优化和问题修复:
1. 新增缩略图图片组件、数字格式化工具类,补充多语言翻译类型与本地化支持
2. 优化底部导航栏主题色统一使用动态accent色值
3. 修复多处图表动画、路由跳转、API请求相关问题
4. 简化服务器公告文案,调整默认分屏状态为关闭
5. 新增安卓/iOS桌面快捷方式配置
6. 重构多处状态管理类使用SafeNotifierInit统一异常保护
7. 替换硬编码蓝色为主题色,更新版本号获取方式为动态读取
8. 优化缓存预加载逻辑,移除无用代码
9. 调整默认设置项,优化用户体验细节
2026-05-31 12:24:05 +08:00

213 lines
6.4 KiB
Dart
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
/// ============================================================
/// 闲言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());
});