refactor(i18n): remove directory index inference
This commit is contained in:
parent
8a334c4d49
commit
f5eb06915a
5 changed files with 18 additions and 59 deletions
|
|
@ -2,5 +2,5 @@
|
|||
# side as of the last confirmed-consistent state. Both languages carry equal authority;
|
||||
# after editing either side, bring the other along and re-record with:
|
||||
# pnpm run verify-translation-pairing --write .agents/notes/implemented/process/2026-08-18-localized-bilingual-links.md
|
||||
2026-08-18-localized-bilingual-links.md: fab52cb70e611c25842883513bb89e1e1861a935
|
||||
2026-08-18-localized-bilingual-links.zh.md: a86be2800adaa7113b8da78ac4420ae48fde1663
|
||||
2026-08-18-localized-bilingual-links.md: d8b792d5d1e9c23a1e6389328ed02cf2fb337a0e
|
||||
2026-08-18-localized-bilingual-links.zh.md: ed49fccd975c6681831724d3577104a8ae7221cf
|
||||
|
|
|
|||
|
|
@ -22,7 +22,7 @@ Existing active bilingual sources use the locale-correct target. A Chinese targe
|
|||
|
||||
## Verification
|
||||
|
||||
Pairing tests cover English and Chinese locale selection, out-of-scope targets with siblings, in-scope targets missing a counterpart, switcher exclusion, exact query/fragment retention, directory-index resolution, definitions, rewrites, and diagnostics. Documentation-site tests also pin the audited basic, framework, and practice entry links to explicit index pages in both locales. Merge-driver, translation-brief, and Cordis generator tests cover their respective consumers. Corpus checks require zero wrong-locale links, resolvable fragments, fresh generated regions, current pair records, and a successful documentation-site build.
|
||||
Pairing tests cover English and Chinese locale selection, out-of-scope targets with siblings, in-scope targets missing a counterpart, switcher exclusion, exact query/fragment retention, non-inference of directory targets, definitions, rewrites, and diagnostics. Documentation-site tests also pin the audited basic, framework, and practice entry links to explicit index pages in both locales. Merge-driver, translation-brief, and Cordis generator tests cover their respective consumers. Corpus checks require zero wrong-locale links, resolvable fragments, fresh generated regions, current pair records, and a successful documentation-site build.
|
||||
|
||||
## Alternatives considered
|
||||
|
||||
|
|
|
|||
|
|
@ -22,7 +22,7 @@ Cordis subsystem 区块生成器先渲染同一个 catalog model,再为中文
|
|||
|
||||
## Verification
|
||||
|
||||
配对测试覆盖中英文 locale 选择、存在兄弟文件的范围外目标、范围内缺少对侧的目标、语言切换行排除、query/fragment 原样保留、目录索引解析、定义式链接、重写与诊断。文档网站测试还会固定中英文两侧已审计的 basic、framework 与 practice 入口,要求它们指向具体索引页。合并驱动、翻译简报与 Cordis 生成器测试分别覆盖各自消费路径。全语料检查要求 locale 错误链接为零、fragment 可解析、生成区块新鲜、配对记录为当前内容,并要求文档网站成功构建。
|
||||
配对测试覆盖中英文 locale 选择、存在兄弟文件的范围外目标、范围内缺少对侧的目标、语言切换行排除、query/fragment 原样保留、不从目录目标推断索引页、定义式链接、重写与诊断。文档网站测试还会固定中英文两侧已审计的 basic、framework 与 practice 入口,要求它们指向具体索引页。合并驱动、翻译简报与 Cordis 生成器测试分别覆盖各自消费路径。全语料检查要求 locale 错误链接为零、fragment 可解析、生成区块新鲜、配对记录为当前内容,并要求文档网站成功构建。
|
||||
|
||||
## Alternatives considered
|
||||
|
||||
|
|
|
|||
|
|
@ -96,12 +96,12 @@ describe('translation link locale validation', () => {
|
|||
})
|
||||
})
|
||||
|
||||
it('retains an English directory-index alias', () => {
|
||||
it('does not infer an index page from a directory target', () => {
|
||||
const root = fixture()
|
||||
expect(translationLinkLocaleViolations(
|
||||
'[Section](section/)\n',
|
||||
linkContext(root, 'docs/guide.md'),
|
||||
)).toEqual([])
|
||||
const input = '[Section](section/)\n'
|
||||
expect(translationLinkLocaleViolations(input, linkContext(root, 'docs/guide.zh.md'))).toEqual([])
|
||||
expect(rewriteTranslationLinkLocales(input, linkContext(root, 'docs/guide.zh.md')))
|
||||
.toEqual({ content: input, rewritten: 0 })
|
||||
})
|
||||
|
||||
it('exempts the language switcher target explicitly', () => {
|
||||
|
|
@ -133,21 +133,6 @@ describe('translation link locale validation', () => {
|
|||
).content).toBe('# 指南\n\n[English](guide.md) | 中文\n\n[正文](guide.zh.md)\n')
|
||||
})
|
||||
|
||||
it('resolves a directory alias to its paired index page', () => {
|
||||
const root = fixture()
|
||||
expect(translationLinkLocaleViolations(
|
||||
'[章节](section/)\n',
|
||||
linkContext(root, 'docs/guide.zh.md'),
|
||||
)[0]).toMatchObject({ expectedUrl: 'section/index.zh.md' })
|
||||
expect(normalizeTranslationMarkdownLinks(
|
||||
'[Section](section/)\n',
|
||||
linkContext(root, 'docs/guide.md'),
|
||||
)).toBe(normalizeTranslationMarkdownLinks(
|
||||
'[Section](section/index.zh.md)\n',
|
||||
linkContext(root, 'docs/guide.zh.md'),
|
||||
))
|
||||
})
|
||||
|
||||
it('uses the selected content plane for target existence without deriving scope from siblings', () => {
|
||||
const root = fixture()
|
||||
const staged = new Set(['docs/reference.md', 'docs/reference.zh.md'])
|
||||
|
|
|
|||
|
|
@ -49,7 +49,6 @@ interface ResolvedTranslationLink {
|
|||
suffix: string
|
||||
expectedPath: string
|
||||
expectedUrl: string
|
||||
kind: ResolutionKind
|
||||
locale: 'en' | 'zh'
|
||||
}
|
||||
|
||||
|
|
@ -60,7 +59,6 @@ interface Replacement {
|
|||
}
|
||||
|
||||
type LinkNode = Extract<Nodes, { type: 'link' | 'definition' }>
|
||||
type ResolutionKind = 'exact' | 'directory-index'
|
||||
|
||||
/** Offset of the one top-level switcher link immediately following the H1. */
|
||||
export function languageSwitcherLinkOffset(
|
||||
|
|
@ -129,19 +127,11 @@ function repositoryRelativePath(path: string): string | undefined {
|
|||
function resolveRepositoryTarget(
|
||||
rawPath: string,
|
||||
context: TranslationLinkContext,
|
||||
): { path: string; kind: ResolutionKind } | undefined {
|
||||
): string | undefined {
|
||||
const decoded = decodePath(rawPath)
|
||||
const exact = repositoryRelativePath(posix.join(posix.dirname(context.sourcePath), decoded))
|
||||
if (exact === undefined) return undefined
|
||||
if (repositoryFileExists(context, exact)) return { path: exact, kind: 'exact' }
|
||||
const index = repositoryRelativePath(posix.join(exact, 'index.md'))
|
||||
if (decoded.endsWith('/') && index !== undefined && repositoryFileExists(context, index)) {
|
||||
return { path: index, kind: 'directory-index' }
|
||||
}
|
||||
if (posix.extname(decoded) === '' && index !== undefined && repositoryFileExists(context, index)) {
|
||||
return { path: index, kind: 'directory-index' }
|
||||
}
|
||||
return undefined
|
||||
return repositoryFileExists(context, exact) ? exact : undefined
|
||||
}
|
||||
|
||||
function translationPairTarget(targetPath: string, context: TranslationLinkContext): TranslationPairTarget | undefined {
|
||||
|
|
@ -153,29 +143,15 @@ function translationPairTarget(targetPath: string, context: TranslationLinkConte
|
|||
return { source, zh }
|
||||
}
|
||||
|
||||
function fallbackRelativePath(context: TranslationLinkContext, targetPath: string, rawPath: string): string {
|
||||
const target = posix.relative(posix.dirname(context.sourcePath), targetPath)
|
||||
const encoded = encodeURI(target)
|
||||
return rawPath.startsWith('./') && !encoded.startsWith('.') ? `./${encoded}` : encoded
|
||||
}
|
||||
|
||||
function expectedLocalePath(
|
||||
rawPath: string,
|
||||
kind: ResolutionKind,
|
||||
locale: 'en' | 'zh',
|
||||
context: TranslationLinkContext,
|
||||
targetPath: string,
|
||||
): string {
|
||||
if (kind === 'exact') {
|
||||
if (locale === 'zh' && rawPath.endsWith('.md') && !rawPath.endsWith('.zh.md')) {
|
||||
return rawPath.replace(/\.md$/, '.zh.md')
|
||||
}
|
||||
if (locale === 'en' && rawPath.endsWith('.zh.md')) return rawPath.replace(/\.zh\.md$/, '.md')
|
||||
if (locale === 'zh' && rawPath.endsWith('.md') && !rawPath.endsWith('.zh.md')) {
|
||||
return rawPath.replace(/\.md$/, '.zh.md')
|
||||
}
|
||||
if (kind === 'directory-index' && locale === 'zh') {
|
||||
return `${rawPath}${rawPath.endsWith('/') ? '' : '/'}index.zh.md`
|
||||
}
|
||||
return fallbackRelativePath(context, targetPath, rawPath)
|
||||
if (locale === 'en' && rawPath.endsWith('.zh.md')) return rawPath.replace(/\.zh\.md$/, '.md')
|
||||
return rawPath
|
||||
}
|
||||
|
||||
function resolveTranslationLink(
|
||||
|
|
@ -187,9 +163,8 @@ function resolveTranslationLink(
|
|||
const { path } = splitMarkdownUrlTarget(url)
|
||||
const authored = splitMarkdownUrlTarget(authoredUrl)
|
||||
if (path === '') return undefined
|
||||
const resolved = resolveRepositoryTarget(path, context)
|
||||
if (resolved === undefined) return undefined
|
||||
const targetPath = resolved.path
|
||||
const targetPath = resolveRepositoryTarget(path, context)
|
||||
if (targetPath === undefined) return undefined
|
||||
const pair = translationPairTarget(targetPath, context)
|
||||
if (pair === undefined) return undefined
|
||||
const locale = context.sourcePath.endsWith('.zh.md') ? 'zh' : 'en'
|
||||
|
|
@ -199,8 +174,7 @@ function resolveTranslationLink(
|
|||
targetPath,
|
||||
suffix: authored.suffix,
|
||||
expectedPath,
|
||||
expectedUrl: `${expectedLocalePath(authored.path, resolved.kind, locale, context, expectedPath)}${authored.suffix}`,
|
||||
kind: resolved.kind,
|
||||
expectedUrl: `${expectedLocalePath(authored.path, locale)}${authored.suffix}`,
|
||||
locale,
|
||||
}
|
||||
}
|
||||
|
|
|
|||
Loading…
Add table
Reference in a new issue