Files
xianyan/lib/l10n/translation_coverage.dart
Developer f91be94e9c refactor: 完成项目架构重构,统一模块导入路径
- 清理大量废弃的 barrel 导出文件,移除冗余的中间导出层
- 修复所有相对路径导入错误,统一调整为扁平化模块引用
- 更新多平台 pubspec 版本号与依赖库版本
- 补充后端功能问题管理后台与脚本工具
- 调整部分页面的快捷方式文案适配新功能
- 更新部分翻译覆盖率与API文档
2026-06-12 08:53:57 +08:00

228 lines
6.2 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 — 翻译覆盖率检测
/// 创建时间: 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': 90,
'ja': 85,
'zh_TW': 95,
'ko': 85,
'de': 75,
'it': 70,
'es': 75,
'ar': 70,
'bn': 65,
'hi': 60,
'pt': 70,
'ru': 75,
'fr': 80,
};
/// 检测所有语言的翻译完成数量
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,
};
}