docs(i18n): restore prompt-v4 as the pipeline baseline
按 jingtingxiang 拍板将 prompt 回到 v4 基线:模板正文恢复内嵌的 格式/语气/句式/词汇/标点全量约束与 11 组正误例(量词规则按术语表 现行裁定 package→包 写作「由三个包构成的 seam」),不再注入 translation-rules.md——该文件约束人和 agent,不进模板;占位符收敛 为 source_lang/target_lang/terminology 三个,切换行由模型按文档 自身拼写。渲染器、解析器、conformance 门禁与单测同步回 v4 契约: 三段裸 XML(translation/review/final 顺序唯一),容忍整体 ```xml 围栏回显;saxes 依赖随 CDATA 协议一并移除。
This commit is contained in:
@@ -1,5 +1,7 @@
|
||||
/** Regression tests for the executable translation prompt contract. */
|
||||
/** Unit tests for the prompt-v4 renderer and three-section response parser. */
|
||||
|
||||
import { readFileSync } from 'node:fs'
|
||||
import { join, resolve } from 'node:path'
|
||||
import { describe, expect, it } from 'vitest'
|
||||
import {
|
||||
parseTranslationResponse,
|
||||
@@ -7,70 +9,43 @@ import {
|
||||
renderTranslationResponse,
|
||||
} from './translation-prompt.ts'
|
||||
|
||||
const document = `# Wrapper
|
||||
|
||||
## 模板正文
|
||||
|
||||
\`\`\`\`text
|
||||
{{source_lang}} to {{target_lang}}
|
||||
{{translation_rules}}
|
||||
{{terminology}}
|
||||
[English]({{source_filename}}) | [中文]({{source_filename_zh}})
|
||||
\`\`\`\`
|
||||
`
|
||||
const root = resolve(import.meta.dirname, '..')
|
||||
const document = readFileSync(join(root, 'docs/i18n/translation-prompt.md'), 'utf8')
|
||||
const terminology = '| English | 中文 |\n|---|---|\n| agent | agent |'
|
||||
|
||||
describe('translation prompt rendering', () => {
|
||||
it('renders every supported placeholder without recursively rewriting injected rules', () => {
|
||||
const rendered = renderTranslationPrompt(document, {
|
||||
sourceLanguage: 'English',
|
||||
sourceFilename: 'guide.md',
|
||||
translationRules: 'A literal {{source_lang}} in injected rules.',
|
||||
terminology: '| English | 中文 |',
|
||||
})
|
||||
expect(rendered).toContain('English to Chinese')
|
||||
expect(rendered).toContain('A literal {{source_lang}} in injected rules.')
|
||||
expect(rendered).toContain('[English](guide.md) | [中文](guide.zh.md)')
|
||||
it('renders both directions with every placeholder resolved', () => {
|
||||
const en = renderTranslationPrompt(document, { sourceLanguage: 'English', terminology })
|
||||
expect(en).toContain('from English to Chinese')
|
||||
expect(en).toContain(terminology)
|
||||
expect(en).not.toContain('{{')
|
||||
const zh = renderTranslationPrompt(document, { sourceLanguage: 'Chinese', terminology })
|
||||
expect(zh).toContain('from Chinese to English')
|
||||
})
|
||||
|
||||
it('rejects a filename whose suffix contradicts the source language', () => {
|
||||
expect(() => renderTranslationPrompt(document, {
|
||||
sourceLanguage: 'Chinese',
|
||||
sourceFilename: 'guide.md',
|
||||
translationRules: 'rules',
|
||||
terminology: 'terms',
|
||||
})).toThrow('does not match source language Chinese')
|
||||
})
|
||||
|
||||
it('rejects malformed template placeholders before injecting rule contents', () => {
|
||||
expect(() => renderTranslationPrompt(document.replace('{{source_lang}}', '{{source-lang}}'), {
|
||||
sourceLanguage: 'English',
|
||||
sourceFilename: 'guide.md',
|
||||
translationRules: 'A literal {{source_lang}} in injected rules.',
|
||||
terminology: '| English | 中文 |',
|
||||
})).toThrow('template contains malformed placeholder syntax')
|
||||
it('rejects a template with unknown or missing placeholders', () => {
|
||||
const alien = document.replaceAll('{{terminology}}', '{{terms_prompt}}')
|
||||
expect(() => renderTranslationPrompt(alien, { sourceLanguage: 'English', terminology })).toThrow(/unsupported placeholder/)
|
||||
const missing = document.replaceAll('{{terminology}}', '')
|
||||
expect(() => renderTranslationPrompt(missing, { sourceLanguage: 'English', terminology })).toThrow(/required placeholder/)
|
||||
})
|
||||
})
|
||||
|
||||
describe('translation response XML', () => {
|
||||
it('round-trips Markdown and the CDATA terminator', () => {
|
||||
const response = {
|
||||
translation: '# Draft\n\nA ]]> marker.',
|
||||
review: '- [Tone] Fixed.',
|
||||
final: '# Final\n\nA ]]> marker.',
|
||||
}
|
||||
describe('translation response sections', () => {
|
||||
it('round-trips Markdown bodies', () => {
|
||||
const response = { translation: '# 标题\n\n正文 **加粗**。', review: '- [Tone] 修正一处。\n- 无修正', final: '# 标题\n\n定稿。' }
|
||||
expect(parseTranslationResponse(renderTranslationResponse(response))).toEqual(response)
|
||||
})
|
||||
|
||||
it('rejects missing, reordered, nested, attributed, or non-CDATA children', () => {
|
||||
expect(() => parseTranslationResponse('<dsh-translation-response version="1"/>')).toThrow('translation, review, and final')
|
||||
expect(() => parseTranslationResponse('<dsh-translation-response version="1"><review><![CDATA[x]]></review></dsh-translation-response>'))
|
||||
.toThrow('expected translation, got review')
|
||||
expect(() => parseTranslationResponse(renderTranslationResponse({ translation: 'x', review: 'y', final: 'z' })
|
||||
.replace('<translation><![CDATA[x]]></translation>', '<translation><b><![CDATA[x]]></b></translation>')))
|
||||
.toThrow('nested element b is not allowed')
|
||||
expect(() => parseTranslationResponse(renderTranslationResponse({ translation: 'x', review: 'y', final: 'z' }).replace('<review>', '<review lang="en">')))
|
||||
.toThrow('review must not have attributes')
|
||||
expect(() => parseTranslationResponse(renderTranslationResponse({ translation: 'x', review: 'y', final: 'z' }).replace('<![CDATA[x]]>', 'x')))
|
||||
.toThrow('all response field content must be inside CDATA')
|
||||
it('tolerates a fenced xml wrapper around the whole response', () => {
|
||||
const fenced = '```xml\n<translation>\nA\n</translation>\n\n<review>\n- 无修正\n</review>\n\n<final>\nA\n</final>\n```'
|
||||
expect(parseTranslationResponse(fenced).final).toBe('A')
|
||||
})
|
||||
|
||||
it('rejects missing, unterminated, or duplicated sections', () => {
|
||||
expect(() => parseTranslationResponse('<translation>\nA\n</translation>')).toThrow(/missing <review>/)
|
||||
expect(() => parseTranslationResponse('<translation>\nA')).toThrow(/unterminated <translation>/)
|
||||
const dup = '<translation>\nA\n</translation>\n<review>\nR\n</review>\n<final>\nF\n</final>\n<final>\nG\n</final>'
|
||||
expect(() => parseTranslationResponse(dup)).toThrow(/duplicate <final>/)
|
||||
})
|
||||
})
|
||||
|
||||
Reference in New Issue
Block a user