本次提交包含多项迭代优化和问题修复: 1. 新增缩略图图片组件、数字格式化工具类,补充多语言翻译类型与本地化支持 2. 优化底部导航栏主题色统一使用动态accent色值 3. 修复多处图表动画、路由跳转、API请求相关问题 4. 简化服务器公告文案,调整默认分屏状态为关闭 5. 新增安卓/iOS桌面快捷方式配置 6. 重构多处状态管理类使用SafeNotifierInit统一异常保护 7. 替换硬编码蓝色为主题色,更新版本号获取方式为动态读取 8. 优化缓存预加载逻辑,移除无用代码 9. 调整默认设置项,优化用户体验细节
228 lines
6.2 KiB
Dart
228 lines
6.2 KiB
Dart
/// ============================================================
|
||
/// 闲言APP — 翻译覆盖率检测
|
||
/// 创建时间: 2026-05-29
|
||
/// 更新时间: 2026-05-31
|
||
/// 作用: 各语言翻译完成度检测与覆盖率报告
|
||
/// 上次更新: 新增ko/de/it语言覆盖率检测,基于toMap自动检测所有子模块
|
||
/// ============================================================
|
||
|
||
import 'types/t.dart';
|
||
import 'languages/zh_cn.dart';
|
||
import 'languages/en.dart';
|
||
import 'languages/ja.dart';
|
||
import 'languages/zh_tw.dart';
|
||
import 'languages/ko.dart';
|
||
import 'languages/de.dart';
|
||
import 'languages/it.dart';
|
||
import 'languages/es.dart';
|
||
import 'languages/ar.dart';
|
||
import 'languages/bn.dart';
|
||
import 'languages/hi.dart';
|
||
import 'languages/pt.dart';
|
||
import 'languages/ru.dart';
|
||
import 'languages/fr.dart';
|
||
|
||
/// 翻译覆盖率检测工具
|
||
class TranslationCoverage {
|
||
/// 总翻译字段数(自动计算,基于zhCN的toMap)
|
||
static int get totalFieldCount {
|
||
final map = zhCN.toMap();
|
||
var count = 0;
|
||
for (final section in map.values) {
|
||
count += section.length;
|
||
}
|
||
return count;
|
||
}
|
||
|
||
/// 各语言实际翻译完成度(0-100),覆盖自动检测的空字段计数
|
||
/// 自动检测仅判断字段是否为空,无法区分"机器翻译占位"与"人工精校"
|
||
/// 因此由维护者手动标注真实进度
|
||
static const Map<String, int> _progressOverride = {
|
||
'zh_CN': 100,
|
||
'en': 95,
|
||
'ja': 75,
|
||
'zh_TW': 90,
|
||
'ko': 70,
|
||
'de': 60,
|
||
'it': 55,
|
||
'es': 60,
|
||
'ar': 55,
|
||
'bn': 50,
|
||
'hi': 45,
|
||
'pt': 55,
|
||
'ru': 60,
|
||
'fr': 65,
|
||
};
|
||
|
||
/// 检测所有语言的翻译完成数量
|
||
static Map<String, int> checkAll() {
|
||
final result = <String, int>{};
|
||
result['zh_CN'] = totalFieldCount;
|
||
final languages = <String, T>{
|
||
'en': en,
|
||
'ja': ja,
|
||
'zh_TW': zhTW,
|
||
'ko': ko,
|
||
'de': de,
|
||
'it': it,
|
||
'es': es,
|
||
'ar': ar,
|
||
'bn': bn,
|
||
'hi': hi,
|
||
'pt': pt,
|
||
'ru': ru,
|
||
'fr': fr,
|
||
};
|
||
for (final entry in languages.entries) {
|
||
final overridePercent = _progressOverride[entry.key];
|
||
if (overridePercent != null) {
|
||
result[entry.key] = (totalFieldCount * overridePercent / 100).round();
|
||
} else {
|
||
final missing = checkCoverage(entry.value, zhCN);
|
||
result[entry.key] = totalFieldCount - missing.length;
|
||
}
|
||
}
|
||
return result;
|
||
}
|
||
|
||
/// 获取某语言的实际翻译完成百分比(0-100)
|
||
static int getProgressPercent(String langId) {
|
||
final percent = _progressOverride[langId];
|
||
if (percent != null) return percent;
|
||
return 100;
|
||
}
|
||
|
||
/// 检测目标语言相对于参考语言的缺失翻译字段
|
||
/// 基于 toMap() 自动遍历,新增字段无需手动添加检测代码
|
||
static List<String> checkCoverage(T target, T reference) {
|
||
final targetMap = target.toMap();
|
||
final missing = <String>[];
|
||
for (final sectionEntry in targetMap.entries) {
|
||
final sectionName = sectionEntry.key;
|
||
final sectionMap = sectionEntry.value;
|
||
for (final fieldEntry in sectionMap.entries) {
|
||
if (fieldEntry.value.isEmpty) {
|
||
missing.add('$sectionName.${fieldEntry.key}');
|
||
}
|
||
}
|
||
}
|
||
return missing;
|
||
}
|
||
|
||
/// 获取某语言的缺失翻译字段详情
|
||
static Map<String, List<String>> checkCoverageDetail(T target) {
|
||
final targetMap = target.toMap();
|
||
final result = <String, List<String>>{};
|
||
for (final sectionEntry in targetMap.entries) {
|
||
final sectionName = sectionEntry.key;
|
||
final sectionMap = sectionEntry.value;
|
||
final missingFields = <String>[];
|
||
for (final fieldEntry in sectionMap.entries) {
|
||
if (fieldEntry.value.isEmpty) {
|
||
missingFields.add(fieldEntry.key);
|
||
}
|
||
}
|
||
if (missingFields.isNotEmpty) {
|
||
result[sectionName] = missingFields;
|
||
}
|
||
}
|
||
return result;
|
||
}
|
||
|
||
/// 获取所有语言的覆盖率报告
|
||
static Map<String, Map<String, dynamic>> fullReport() {
|
||
final languages = <String, T>{
|
||
'zh_CN': zhCN,
|
||
'en': en,
|
||
'ja': ja,
|
||
'zh_TW': zhTW,
|
||
'ko': ko,
|
||
'de': de,
|
||
'it': it,
|
||
'es': es,
|
||
'ar': ar,
|
||
'bn': bn,
|
||
'hi': hi,
|
||
'pt': pt,
|
||
'ru': ru,
|
||
'fr': fr,
|
||
};
|
||
final total = totalFieldCount;
|
||
final report = <String, Map<String, dynamic>>{};
|
||
for (final entry in languages.entries) {
|
||
final missing = checkCoverage(entry.value, zhCN);
|
||
final diff = diffFields(entry.value, zhCN);
|
||
final overridePercent = _progressOverride[entry.key];
|
||
final effectivePercent =
|
||
overridePercent ?? ((total - missing.length) * 100 / total).round();
|
||
report[entry.key] = {
|
||
'total': total,
|
||
'translated': total - missing.length,
|
||
'missing': missing,
|
||
'percent': effectivePercent,
|
||
'missingFields': diff,
|
||
};
|
||
}
|
||
return report;
|
||
}
|
||
|
||
/// 检测目标语言与参考语言的字段差异
|
||
/// 返回参考语言中存在但目标语言中缺失或为空的字段
|
||
static Map<String, List<String>> diffFields(T target, T reference) {
|
||
final targetMap = target.toMap();
|
||
final referenceMap = reference.toMap();
|
||
final result = <String, List<String>>{};
|
||
for (final sectionEntry in referenceMap.entries) {
|
||
final sectionName = sectionEntry.key;
|
||
final refSection = sectionEntry.value;
|
||
final targetSection = targetMap[sectionName] ?? {};
|
||
final missing = <String>[];
|
||
for (final fieldEntry in refSection.entries) {
|
||
final targetValue = targetSection[fieldEntry.key];
|
||
if (targetValue == null || targetValue.isEmpty) {
|
||
missing.add(fieldEntry.key);
|
||
}
|
||
}
|
||
if (missing.isNotEmpty) {
|
||
result[sectionName] = missing;
|
||
}
|
||
}
|
||
return result;
|
||
}
|
||
|
||
/// 获取所有语言ID列表(不含zh_CN)
|
||
static List<String> get allLanguageIds => [
|
||
'en',
|
||
'ja',
|
||
'zh_TW',
|
||
'ko',
|
||
'de',
|
||
'it',
|
||
'es',
|
||
'ar',
|
||
'bn',
|
||
'hi',
|
||
'pt',
|
||
'ru',
|
||
'fr',
|
||
];
|
||
|
||
/// 获取所有语言的翻译实例映射
|
||
static Map<String, T> get allLanguages => {
|
||
'zh_CN': zhCN,
|
||
'en': en,
|
||
'ja': ja,
|
||
'zh_TW': zhTW,
|
||
'ko': ko,
|
||
'de': de,
|
||
'it': it,
|
||
'es': es,
|
||
'ar': ar,
|
||
'bn': bn,
|
||
'hi': hi,
|
||
'pt': pt,
|
||
'ru': ru,
|
||
'fr': fr,
|
||
};
|
||
}
|