2026-08-04 17:36:14 +08:00
/** Unit tests for the prompt-v7 content and unchanged three-section protocol. */
2026-07-14 23:09:01 +08:00
2026-07-16 00:19:25 -07:00
import { readFileSync } from 'node:fs'
import { join , resolve } from 'node:path'
2026-07-14 23:09:01 +08:00
import { describe , expect , it } from 'vitest'
import {
2026-07-23 22:41:53 +08:00
consumeTranslationResponse ,
2026-07-14 23:09:01 +08:00
parseTranslationResponse ,
renderTranslationPrompt ,
2026-07-23 22:41:53 +08:00
renderTranslationRequest ,
2026-07-14 23:09:01 +08:00
renderTranslationResponse ,
} from './translation-prompt.ts'
2026-07-16 00:19:25 -07:00
const root = resolve ( import . meta . dirname , '..' )
const document = readFileSync ( join ( root , 'docs/i18n/translation-prompt.md' ) , 'utf8' )
const terminology = '| English | 中文 |\n|---|---|\n| agent | agent |'
2026-07-14 23:09:01 +08:00
2026-08-04 17:36:14 +08:00
const retainedExamples = [
[ '### Colloquial verb → Professional verb' , 'The repo pins pnpm@11.7.0 in package.json' , '该仓库在 package.json 中固定使用 pnpm@11.7.0' ] ,
[ '### Run-on sentence → Natural phrasing with pause' , 'Read docs/architecture.md before changing anything under packages/.' , '在修改 packages/ 目录下的任何内容之前,请先阅读 docs/architecture.md。' ] ,
[ '### Stiff passive voice → Active and natural' , 'a green gate means the pair was confirmed consistent at these exact contents, not that the confirmation was sound.' , '门禁通过意味着这组文档在当前内容上的一致性得到了确认,不代表确认本身正确可靠。' ] ,
[ '### Invented word → Natural expression' , 'A sidecar record of both blob hashes makes consistency checkable' , '伴随记录保存两侧 blob hash,使一致性可检查' ] ,
[ '### Em-dash → Colon/period' , 'FIXME — an issue that should block a new release.' , 'FIXME:应当阻塞新版本发布的问题。' ] ,
2026-08-09 15:27:21 +08:00
[ '### Overly literal → Meaningful rendering' , 'awkward phrasing is easier to notice when you read the translation without comparing it with the source' , '不对照原文阅读译文时,更容易察觉别扭的表达' ] ,
2026-08-04 17:36:14 +08:00
[ '### Terminology — do not translate what should be kept in English' , 'typed service seams, and explicit extension points' , '类型化的服务 seam 与显式扩展点' ] ,
[ '### Slang/jargon → Professional phrasing' , 'The committed agent workflow lives in .agents/skills/dsh-translate-docs' , '仓库内置的 agent 工作流见 .agents/skills/dsh-translate-docs' ] ,
[ '### "For humans" — translate the intent, not the word' , 'For humans, start with the development guide' , '面向开发者:请先阅读开发指南' ] ,
[ '### Code block comments — NEVER translate' , '# full-screen TUI coding agent (needs DEEPSEEK_API_KEY)' , 'keep exactly as-is, byte-for-byte' ] ,
[ '### Language switcher — flip direction' , 'English | [中文](README.zh.md)' , '[English](README.md) | 中文' ] ,
]
2026-07-14 23:09:01 +08:00
describe ( 'translation prompt rendering' , ( ) = > {
2026-07-16 00:19:25 -07:00
it ( 'renders both directions with every placeholder resolved' , ( ) = > {
2026-07-23 22:41:53 +08:00
const en = renderTranslationPrompt ( document , { sourceLanguage : 'English' , sourceFilename : 'guide.md' , terminology } )
2026-07-16 00:19:25 -07:00
expect ( en ) . toContain ( 'from English to Chinese' )
expect ( en ) . toContain ( terminology )
expect ( en ) . not . toContain ( '{{' )
2026-07-23 21:44:41 +08:00
expect ( en ) . toContain ( 'plain source stays plain (必须)' )
2026-08-04 17:36:14 +08:00
expect ( en ) . toContain ( 'For an English target, use the established English technical term' )
expect ( en ) . toContain ( 'does a Chinese target use an established Chinese rendering' )
expect ( en ) . toContain ( 'does an English target use the established English technical term' )
2026-07-23 21:44:41 +08:00
expect ( en ) . toContain ( 'The parser removes exactly one framing escape' )
2026-07-23 22:41:53 +08:00
const zh = renderTranslationPrompt ( document , { sourceLanguage : 'Chinese' , sourceFilename : 'guide.zh.md' , terminology } )
2026-07-16 00:19:25 -07:00
expect ( zh ) . toContain ( 'from Chinese to English' )
2026-07-14 23:09:01 +08:00
} )
2026-07-15 20:39:24 +08:00
2026-08-09 15:27:21 +08:00
it ( 'contains every embedded example' , ( ) = > {
2026-08-04 17:36:14 +08:00
for ( const example of retainedExamples ) {
for ( const fragment of example ) expect ( document ) . toContain ( fragment )
}
} )
it ( 'states the selected v7 safeguards' , ( ) = > {
const rendered = renderTranslationPrompt ( document , { sourceLanguage : 'English' , sourceFilename : 'guide.md' , terminology } )
expect ( rendered ) . toContain ( '## Priority' )
expect ( rendered ) . toContain ( '### Faithfulness' )
expect ( rendered ) . toContain ( 'do not invent a filename or switcher' )
expect ( rendered ) . toContain ( 'Markdown emphasis markers do not create a word boundary' )
expect ( rendered ) . toContain ( 'Never invent responsibility merely to avoid a passive construction' )
expect ( rendered ) . toContain ( 'Never vary a terminology-table form, defined concept, or contract verb merely for stylistic variety' )
expect ( rendered ) . toContain ( 'Return exactly three raw XML sections' )
} )
2026-07-16 00:19:25 -07:00
it ( 'rejects a template with unknown or missing placeholders' , ( ) = > {
const alien = document . replaceAll ( '{{terminology}}' , '{{terms_prompt}}' )
2026-07-23 22:41:53 +08:00
expect ( ( ) = > renderTranslationPrompt ( alien , { sourceLanguage : 'English' , sourceFilename : 'guide.md' , terminology } ) ) . toThrow ( /unsupported placeholder/ )
2026-07-16 00:19:25 -07:00
const missing = document . replaceAll ( '{{terminology}}' , '' )
2026-07-23 22:41:53 +08:00
expect ( ( ) = > renderTranslationPrompt ( missing , { sourceLanguage : 'English' , sourceFilename : 'guide.md' , terminology } ) ) . toThrow ( /required placeholder/ )
} )
2026-07-23 23:12:56 +08:00
it ( 'rejects unmatched placeholder delimiters' , ( ) = > {
for ( const delimiter of [ '{{' , '}}' ] ) {
const malformed = document . replace ( 'Your task is to translate' , ` Your task ${ delimiter } is to translate ` )
expect ( ( ) = > renderTranslationPrompt ( malformed , {
sourceLanguage : 'English' ,
sourceFilename : 'guide.md' ,
terminology ,
} ) ) . toThrow ( /malformed placeholder syntax/ )
}
} )
2026-07-23 22:41:53 +08:00
it ( 'assembles bare few-shot turns before the real source document' , ( ) = > {
const request = renderTranslationRequest ( document , {
sourceLanguage : 'English' ,
sourceFilename : 'guide.md' ,
sourceDocument : '# Guide\n\nNew source.' ,
terminology ,
examples : [ { english : '# Example\n\nEnglish.' , chinese : '# 示例\n\n中文。' } ] ,
} )
expect ( request . targetFilename ) . toBe ( 'guide.zh.md' )
expect ( request . messages . map ( message = > message . role ) ) . toEqual ( [ 'system' , 'user' , 'assistant' , 'user' ] )
expect ( request . messages . slice ( 1 ) . map ( message = > message . content ) ) . toEqual ( [
'# Example\n\nEnglish.' ,
'# 示例\n\n中文。' ,
'# Guide\n\nNew source.' ,
] )
const reverse = renderTranslationRequest ( document , {
sourceLanguage : 'Chinese' ,
sourceFilename : 'guide.zh.md' ,
sourceDocument : '# 指南\n\n新源文。' ,
terminology ,
examples : [ { english : '# Example\n\nEnglish.' , chinese : '# 示例\n\n中文。' } ] ,
} )
expect ( reverse . targetFilename ) . toBe ( 'guide.md' )
expect ( reverse . messages . slice ( 1 ) . map ( message = > message . content ) ) . toEqual ( [
'# 示例\n\n中文。' ,
'# Example\n\nEnglish.' ,
'# 指南\n\n新源文。' ,
] )
2026-07-15 20:39:24 +08:00
} )
2026-07-14 23:09:01 +08:00
} )
2026-07-16 00:19:25 -07:00
describe ( 'translation response sections' , ( ) = > {
it ( 'round-trips Markdown bodies' , ( ) = > {
const response = { translation : '# 标题\n\n正文 **加粗**。' , review : '- [Tone] 修正一处。\n- 无修正' , final : '# 标题\n\n定稿。' }
2026-07-14 23:09:01 +08:00
expect ( parseTranslationResponse ( renderTranslationResponse ( response ) ) ) . toEqual ( response )
} )
2026-07-16 00:19:25 -07:00
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' )
} )
2026-07-20 19:46:46 -07:00
it ( 'keeps an inline close tag inside prose from terminating the section' , ( ) = > {
const doc = { translation : 'the wire format uses </translation> as its close tag' , review : '- 无修正' , final : 'F' }
expect ( parseTranslationResponse ( renderTranslationResponse ( doc ) ) ) . toEqual ( doc )
} )
2026-07-23 21:44:41 +08:00
it ( 'round-trips wrapper-tag lines inside Markdown bodies' , ( ) = > {
const doc = {
translation : '```xml\n</translation>\n```' ,
review : '- [Structure] Preserved `<final>` on its own line.' ,
final : 'literal delimiters\n</final>\n\\</final>' ,
}
const rendered = renderTranslationResponse ( doc )
expect ( parseTranslationResponse ( rendered ) ) . toEqual ( doc )
expect ( ( ) = > parseTranslationResponse ( rendered . replace ( '\\</translation>' , '</translation>' ) ) ) . toThrow ( /duplicate <translation>/ )
} )
2026-07-20 19:46:46 -07:00
it ( 'rejects a duplicate section appearing before final' , ( ) = > {
const early = '<translation>\nA\n</translation>\n<translation>\nB\n</translation>\n<review>\nR\n</review>\n<final>\nF\n</final>'
expect ( ( ) = > parseTranslationResponse ( early ) ) . toThrow ( /duplicate <translation>/ )
} )
2026-07-16 00:19:25 -07:00
it ( 'rejects missing, unterminated, or duplicated sections' , ( ) = > {
2026-07-20 19:46:46 -07:00
expect ( ( ) = > parseTranslationResponse ( '<translation>\nA\n</translation>' ) ) . toThrow ( /missing or unterminated <review>/ )
expect ( ( ) = > parseTranslationResponse ( '<translation>\nA' ) ) . toThrow ( /missing or unterminated <translation>/ )
2026-07-16 00:19:25 -07:00
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>/ )
2026-07-23 21:44:41 +08:00
expect ( ( ) = > parseTranslationResponse ( ` ${ renderTranslationResponse ( { translation : 'A' , review : 'R' , final : 'F' } )} \ nstray ` ) )
. toThrow ( /content is not allowed outside/ )
2026-07-14 23:09:01 +08:00
} )
2026-07-23 22:41:53 +08:00
it ( 'inserts or corrects the target switcher after parsing a new-pair response' , ( ) = > {
const response = renderTranslationResponse ( {
translation : '# 指南\n\n初稿。' ,
review : '- 无修正' ,
final : '# 指南\n\nEnglish | [中文](guide.zh.md)\n\n定稿。' ,
} )
expect ( consumeTranslationResponse ( response , { sourceLanguage : 'English' , sourceFilename : 'guide.md' } ) . final ) . toBe ( [
'# 指南' ,
'' ,
'[English](guide.md) | 中文' ,
'' ,
'定稿。' ,
'' ,
] . join ( '\n' ) )
} )
2026-07-23 23:12:56 +08:00
it ( 'preserves YAML frontmatter before inserting the target switcher' , ( ) = > {
const response = renderTranslationResponse ( {
translation : '# 指南\n\n初稿。' ,
review : '- 无修正' ,
final : [
'---' ,
'layout: home' ,
'---' ,
'' ,
'# 指南' ,
'' ,
'定稿。' ,
] . join ( '\n' ) ,
} )
expect ( consumeTranslationResponse ( response , { sourceLanguage : 'English' , sourceFilename : 'guide.md' } ) . final ) . toBe ( [
'---' ,
'layout: home' ,
'---' ,
'' ,
'# 指南' ,
'' ,
'[English](guide.md) | 中文' ,
'' ,
'定稿。' ,
'' ,
] . join ( '\n' ) )
} )
it ( 'rejects unterminated YAML frontmatter before the target H1' , ( ) = > {
const response = renderTranslationResponse ( {
translation : '# 指南\n\n初稿。' ,
review : '- 无修正' ,
final : '---\nlayout: home\n\n# 指南\n\n定稿。' ,
} )
expect ( ( ) = > consumeTranslationResponse ( response , {
sourceLanguage : 'English' ,
sourceFilename : 'guide.md' ,
} ) ) . toThrow ( /unterminated YAML frontmatter/ )
} )
2026-07-23 22:41:53 +08:00
it ( 'rejects a source filename that contradicts the translation direction' , ( ) = > {
expect ( ( ) = > renderTranslationPrompt ( document , {
sourceLanguage : 'Chinese' ,
sourceFilename : 'guide.md' ,
terminology ,
} ) ) . toThrow ( /does not match source language Chinese/ )
} )
it ( 'inserts the English target switcher for a Chinese source' , ( ) = > {
const response = renderTranslationResponse ( {
translation : '# Guide\n\nDraft.' ,
review : '- [None] No corrections.' ,
final : '# Guide\n\nFinal.' ,
} )
expect ( consumeTranslationResponse ( response , {
sourceLanguage : 'Chinese' ,
sourceFilename : 'guide.zh.md' ,
} ) . final ) . toContain ( '\n\nEnglish | [中文](guide.zh.md)\n\n' )
} )
2026-07-14 23:09:01 +08:00
} )