persistence-artifacts.ts 2.8 KB

1234567891011121314151617181920212223242526272829303132333435363738394041424344454647484950515253
  1. /** Render complete persistence documentation pairs without Git or file mutation. */
  2. import { readFileSync } from 'node:fs'
  3. import { hasLanguageSwitcher } from './translation-links.ts'
  4. import { translationPairPaths } from './translation-pairing-record.ts'
  5. import {
  6. blobHash, languageSwitcherTargets, parseTranslationMarkdown, parseTranslationPairingManifest,
  7. renderPairMeta, requiresSourceLanguageSwitcher, translationPairSourcePredicate,
  8. translationStructureDiff, translationStructureSignature,
  9. } from './translation-pairing.ts'
  10. const isTranslationPairSource = translationPairSourcePredicate(parseTranslationPairingManifest(
  11. readFileSync(new URL('./translation-pairing.manifest.json', import.meta.url), 'utf8'),
  12. ))
  13. /** One repository-relative generated file and its complete UTF-8 content. */
  14. export interface PersistenceArtifact {
  15. readonly path: string
  16. readonly content: string
  17. }
  18. /**
  19. * Check a pair's code, structure and localized links, then render its three files.
  20. * Link existence remains the Markdown gate's responsibility. Pair hashes are
  21. * computed in-process; staging the documents stores the corresponding Git blobs.
  22. * @param root - checkout root used to resolve relative link identities.
  23. * @param source - repository-relative English document path.
  24. * @param en - complete authored or generated English Markdown.
  25. * @param zh - complete authored or generated Chinese Markdown.
  26. * @returns the documents and their matching consistency sidecar, without writing files.
  27. */
  28. export function renderPersistencePair(root: string, source: string, en: string, zh: string): PersistenceArtifact[] {
  29. const paths = translationPairPaths(source)
  30. const sourceTree = parseTranslationMarkdown(en)
  31. const zhTree = parseTranslationMarkdown(zh)
  32. const sourceTargets = languageSwitcherTargets(paths.source)
  33. const zhTargets = languageSwitcherTargets(paths.zh)
  34. if (!hasLanguageSwitcher(zhTree, zh, sourceTargets)
  35. || requiresSourceLanguageSwitcher(source) && !hasLanguageSwitcher(sourceTree, en, zhTargets)) {
  36. throw new Error(`${source}: both authored languages need their counterpart switcher`)
  37. }
  38. const context = { repoRoot: root, isTranslationPairSource, repositoryFileExists: () => true }
  39. const errors = translationStructureDiff(
  40. translationStructureSignature(sourceTree, zhTargets, { ...context, sourcePath: paths.source, markdown: en }),
  41. translationStructureSignature(zhTree, sourceTargets, { ...context, sourcePath: paths.zh, markdown: zh }),
  42. )
  43. if (errors.length > 0) throw new Error(`${source}: bilingual structure mismatch: ${errors.join('; ')}`)
  44. return [
  45. { path: paths.source, content: en },
  46. { path: paths.zh, content: zh },
  47. { path: paths.meta, content: renderPairMeta(paths.source, blobHash(Buffer.from(en)), paths.zh, blobHash(Buffer.from(zh))) },
  48. ]
  49. }