优先级: P0 (Phase 0 验证确认术语表对翻译质量至关重要) 估算: 26 故事点 (Phase 1 范围) 依赖: 无
实现术语表功能,确保翻译过程中角色名和专有术语保持一致,保证翻译可用性。
Phase 0 技术验证发现:
| 场景 | 原文 | 无术语表 | 有术语表 |
|---|---|---|---|
| 角色名 | 林风 | Lin wind ❌ | Lin Feng ✅ |
| 专有名词 | BMAD | BMAd ❌ | BMAD ✅ |
| 技能名 | 火球术 | fire ball ❌ | Fireball ✅ |
结论: 没有术语表功能,翻译内容不可用。术语表是保证翻译质量的核心功能。
As a 翻译用户, I want 定义和使用术语表, So that 翻译后的内容中角色名和专有术语保持一致。
Dict[str, str] (术语 → 翻译)__en__ 前缀标记pytest==7.4.0估算: 4 SP
描述: 设计术语表数据结构,支持术语和翻译的存储。
验收标准:
from typing import Dict, List
from dataclasses import dataclass
@dataclass
class GlossaryEntry:
"""术语表条目"""
source: str # 原文术语,如 "林风"
target: str # 目标翻译,如 "Lin Feng"
category: str # 术语类型:CHARACTER, SKILL, LOCATION, ITEM, OTHER
context: str = "" # 上下文说明
class Glossary:
"""术语表"""
def __init__(self):
self._terms: Dict[str, GlossaryEntry] = {}
def add(self, entry: GlossaryEntry) -> None:
"""添加术语"""
pass
def get(self, source: str) -> Optional[GlossaryEntry]:
"""获取术语翻译"""
pass
def remove(self, source: str) -> bool:
"""删除术语"""
pass
def get_all(self) -> List[GlossaryEntry]:
"""获取所有术语"""
pass
def sort_by_length_desc(self) -> List[str]:
"""按长度降序排列术语(用于匹配)"""
pass
技术任务:
src/glossary/models.pyGlossaryEntry 数据类Glossary 类估算: 6 SP
描述: 实现最长匹配算法,确保长术语优先匹配(避免"魔法"覆盖"魔法师")。
验收标准:
class GlossaryMatcher:
"""术语匹配引擎"""
def __init__(self, glossary: Glossary):
self.glossary = glossary
# 按长度降序排列,确保长术语优先匹配
self._sorted_terms = glossary.sort_by_length_desc()
def find_matches(self, text: str) -> List[TermMatch]:
"""在文本中查找所有术语匹配"""
pass
def replace_with_placeholder(self, text: str) -> Tuple[str, Dict[str, str]]:
"""将术语替换为占位符
返回: (替换后的文本, 占位符映射)
占位符格式: __en__林风
"""
pass
def restore_from_placeholder(self, text: str, mapping: Dict[str, str]) -> str:
"""将占位符还原为术语翻译"""
pass
@dataclass
class TermMatch:
"""术语匹配结果"""
source: str # 原文术语
target: str # 目标翻译
start: int # 在文本中的起始位置
end: int # 在文本中的结束位置
placeholder: str # 占位符
匹配规则:
示例:
# 输入
text = "林风释放了火球术"
glossary = {
"林风": "Lin Feng",
"火球术": "Fireball"
}
# 输出
processed = "__en__林风释放了__en__火球术"
mapping = {
"__en__林风": "Lin Feng",
"__en__火球术": "Fireball"
}
技术任务:
src/glossary/matcher.py估算: 5 SP
描述: 在翻译前处理文本,将术语替换为占位符。
验收标准:
class GlossaryPreprocessor:
"""术语预处理管道"""
def __init__(self, glossary: Glossary):
self.glossary = glossary
self.matcher = GlossaryMatcher(glossary)
def process(self, text: str) -> PreprocessingResult:
"""处理文本,替换术语为占位符
返回包含:
- processed_text: 处理后的文本
- placeholder_map: 占位符映射
- term_stats: 术语统计
"""
pass
def process_batch(self, texts: List[str]) -> List[PreprocessingResult]:
"""批量处理文本"""
pass
def calculate_retention_rate(self, original: str, processed: str) -> float:
"""计算术语保留率"""
pass
@dataclass
class PreprocessingResult:
"""预处理结果"""
processed_text: str
placeholder_map: Dict[str, str]
terms_found: Dict[str, int] # 术语 → 出现次数
retention_rate: float # 保留率百分比
处理流程:
__en__前缀)技术任务:
src/glossary/preprocessor.py估算: 6 SP
描述: 翻译后处理,去除 __en__ 前缀并还原术语翻译。
验收标准:
class GlossaryPostprocessor:
"""术语后处理模块"""
def __init__(self):
pass
def process(self, translated_text: str, placeholder_map: Dict[str, str]) -> str:
"""处理翻译后的文本
步骤:
1. 查找所有 __en__ 前缀的占位符
2. 从映射表中获取翻译
3. 替换占位符为翻译
4. 修复可能出现的标点问题
"""
pass
def fix_punctuation(self, text: str) -> str:
"""修复标点符号
处理翻译可能产生的标点问题:
- __en__林风. → Lin Feng. (去除多余空格)
- __en__林风, → Lin Feng, (修复中文标点)
"""
pass
def validate_translation(self, original: str, translated: str,
placeholder_map: Dict[str, str]) -> ValidationResult:
"""验证翻译完整性
检查:
- 所有占位符都被替换
- 翻译包含所有术语
- 没有遗漏的术语
"""
pass
@dataclass
class ValidationResult:
"""验证结果"""
is_valid: bool
missing_terms: List[str] # 遗漏的术语
extra_placeholders: List[str] # 未替换的占位符
处理流程:
__en__ 前缀技术任务:
src/glossary/postprocessor.py估算: 5 SP
描述: 完整的测试覆盖,包括单元测试和端到端集成测试。
验收标准:
测试用例:
class TestGlossary:
def test_add_and_retrieve_term(self):
pass
def test_remove_term(self):
pass
def test_sort_by_length_desc(self):
"""测试长术语排在前面"""
pass
class TestGlossaryMatcher:
def test_find_single_term(self):
pass
def test_longest_term_priority(self):
"""测试长术语优先匹配"""
text = "魔法师使用了魔法"
glossary = {"魔法": "Magic", "魔法师": "Mage"}
# 应该匹配 "魔法师" 而不是 "魔法"
pass
def test_non_overlapping_matches(self):
pass
def test_placeholder_generation(self):
pass
class TestGlossaryPreprocessor:
def test_process_text_with_terms(self):
pass
def test_retention_rate_calculation(self):
pass
def test_batch_processing(self):
pass
class TestGlossaryPostprocessor:
def test_restore_from_placeholder(self):
pass
def test_fix_punctuation(self):
pass
def test_validate_translation_success(self):
pass
def test_validate_translation_missing_terms(self):
pass
class TestGlossaryIntegration:
"""端到端集成测试"""
def test_full_pipeline(self):
"""测试完整流程"""
# 1. 创建术语表
# 2. 预处理文本
# 3. 模拟翻译
# 4. 后处理文本
# 5. 验证结果
original = "林风释放了火球术"
glossary = Glossary()
glossary.add(GlossaryEntry("林风", "Lin Feng", "CHARACTER"))
glossary.add(GlossaryEntry("火球术", "Fireball", "SKILL"))
preprocessor = GlossaryPreprocessor(glossary)
result = preprocessor.process(original)
# 模拟翻译(保留占位符)
mock_translated = "__en__林风 released __en__火球术"
postprocessor = GlossaryPostprocessor()
final = postprocessor.process(mock_translated, result.placeholder_map)
assert final == "Lin Feng released Fireball"
pass
def test_phase_0_validation_scenario(self):
"""测试 Phase 0 验证场景"""
# 无术语表: "林风" → "Lin wind"
# 有术语表: "林风" → "Lin Feng"
pass
技术任务:
tests/test_glossary.py估算: 5 SP 状态: 推迟到 Phase 2
描述: 为术语标注上下文,帮助用户确定合适的翻译。
src/
└── glossary/
├── __init__.py
├── models.py # GlossaryEntry, Glossary 类
├── matcher.py # GlossaryMatcher 类
├── preprocessor.py # GlossaryPreprocessor 类
└── postprocessor.py # GlossaryPostprocessor 类
tests/
└── test_glossary.py # 所有术语表测试
| 测试场景 | 原文 | 无术语表结果 | 有术语表结果 |
|---|---|---|---|
| 角色名翻译 | 林风 | Lin wind ❌ | Lin Feng ✅ |
| 产品名称 | BMAD | BMAd ❌ | BMAD ✅ |
| 技能名称 | 火球术 | fire ball ❌ | Fireball ✅ |
| 保留率测试 | 14个术语 | 0% | 93.4% ✅ |
结论: 术语表功能是必须的,没有它翻译内容不可用。
完成 Epic 4 核心功能后,与 Epic 1 集成,开始端到端测试。