diff --git a/.github/copilot-instructions.md b/.github/copilot-instructions.md index 30fa8bb..1c44cd7 100644 --- a/.github/copilot-instructions.md +++ b/.github/copilot-instructions.md @@ -8,7 +8,7 @@ - **Sync Mode**: Runs in SOURCE repo, creates translation PRs in target repo - **Review Mode**: Runs in TARGET repo, posts quality review comments on translation PRs -**Current Version**: v0.12.3 | **Tests**: 934 (39 suites) | **Glossary**: 357 terms (zh-cn, fa) +**Current Version**: v0.12.3 | **Tests**: 935 (39 suites) | **Glossary**: 357 terms (zh-cn, fa) --- diff --git a/CHANGELOG.md b/CHANGELOG.md index a608d30..194f327 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -7,6 +7,11 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 ## [Unreleased] +### Added +- **CJK–MyST spacing rule for zh-cn**: New language-config rule instructs Claude to insert a space between Chinese characters and inline MyST directives (`{doc}`, `{ref}`, etc.) or Markdown links, preventing rendering failures (e.g. `请参阅 {doc}` not `请参阅{doc}`) +- **MyST target-label blank-line cleanup**: `reconstructFromComponents` now strips blank lines between MyST target labels (`(label)=`) and headings in post-processing, so targets always attach to their heading correctly +- **1 test** for target-label blank-line removal (934 → 935 total) + ## [0.12.3] - 2026-03-24 ### Fixed diff --git a/src/__tests__/component-reconstruction.test.ts b/src/__tests__/component-reconstruction.test.ts index 79673b7..7a8b1c9 100644 --- a/src/__tests__/component-reconstruction.test.ts +++ b/src/__tests__/component-reconstruction.test.ts @@ -1615,4 +1615,93 @@ heading-map: expect(result).toContain('Section B: B部分'); expect(result).not.toContain('Section A:'); }); + + it('should remove blank lines between MyST target labels and headings', async () => { + const oldContent = `--- +config: test +--- + +# Advanced Features + +Overview paragraph. + +## Iterables + +Content about iterables. + +(iterators)= +### Iterators + +Iterator details. + +(descriptors)= +## Decorators + +Decorator content.`; + + const newContent = `--- +config: test +--- + +# Advanced Features + +Overview paragraph updated. + +## Iterables + +Content about iterables. + +(iterators)= +### Iterators + +Iterator details. + +(descriptors)= +## Decorators + +Decorator content.`; + + const targetContent = `--- +config: test +--- + +# 高级特性 + +概述段落。 + +## 可迭代对象 + +关于可迭代对象的内容。 + +(iterators)= +### 迭代器 + +迭代器详情。 + +(descriptors)= +## 装饰器 + +装饰器内容。`; + + // Mock translation for intro change + mockTranslator.translateSection.mockResolvedValue({ + success: true, + translatedSection: '概述段落已更新。', + }); + + const result = await processor.processSectionBased( + oldContent, + newContent, + targetContent, + 'test.md', + 'en', + 'zh-cn' + ); + + // Target labels should be directly above headings with no blank line + expect(result).toContain('(iterators)=\n### 迭代器'); + expect(result).not.toContain('(iterators)=\n\n### 迭代器'); + expect(result).toContain('(descriptors)=\n## 装饰器'); + expect(result).not.toContain('(descriptors)=\n\n## 装饰器'); + }); }); diff --git a/src/__tests__/language-config.test.ts b/src/__tests__/language-config.test.ts index ac1f926..a7436c7 100644 --- a/src/__tests__/language-config.test.ts +++ b/src/__tests__/language-config.test.ts @@ -17,8 +17,9 @@ describe('Language Configuration', () => { const config = getLanguageConfig('zh-cn'); expect(config.code).toBe('zh-cn'); expect(config.name).toBe('Chinese (Simplified)'); - expect(config.additionalRules).toHaveLength(1); + expect(config.additionalRules).toHaveLength(2); expect(config.additionalRules[0]).toContain('full-width Chinese punctuation'); + expect(config.additionalRules[1]).toContain('space between Chinese characters and inline MyST directives'); }); it('should handle case insensitive language codes', () => { diff --git a/src/file-processor.ts b/src/file-processor.ts index dd5b20c..2ece55e 100644 --- a/src/file-processor.ts +++ b/src/file-processor.ts @@ -489,7 +489,12 @@ export class FileProcessor { parts.push(''); // Empty line between sections } - return parts.join('\n').trim() + '\n'; + const joined = parts.join('\n').trim() + '\n'; + + // Post-process: remove blank lines between MyST target labels and headings. + // Target labels like (some_id)= must be directly above their heading with no + // intervening blank line, otherwise MyST won't attach the target to the heading. + return joined.replace(/(\([a-zA-Z0-9_.:-]+\)=)\n\n(#+\s)/g, '$1\n$2'); } /** diff --git a/src/language-config.ts b/src/language-config.ts index ba97e50..921000a 100644 --- a/src/language-config.ts +++ b/src/language-config.ts @@ -33,6 +33,7 @@ export const LANGUAGE_CONFIGS: Record = { name: 'Chinese (Simplified)', additionalRules: [ 'Use proper full-width Chinese punctuation marks (,:。!?) not ASCII punctuation (,.!?) in prose text', + 'Always insert a space between Chinese characters and inline MyST directives ({doc}, {ref}, {any}, {term}, etc.) or Markdown links ([text](url)), e.g., "请参阅 {doc}\`介绍 \`" not "请参阅{doc}\`介绍 \`"', ], }, 'fa': {