Skip to content
ktav-langPublic

About

Assemble multi-language documents from per-section triple-translated source units, with drift protection, a crash-safe atomic writer, and structural translation-parity checks.

Topics

Resources

Code of conduct

Security policy

Stars

0 stars

Watchers

0 watching

Forks

CI License: MIT OR Apache-2.0

polydoc

Languages: English · Русский · 简体中文

Assemble multi-language documents from per-section source units that carry every configured language — with drift protection, journalled crash recovery for output writes, and structural translation-parity checks.

Origin: this is a general-purpose extraction of the document build engine behind ktav-lang/spec — the same code that assembles the Ktav specification's own spec.md and its root documents (README, CHANGELOG, CONTRIBUTING, SECURITY) from per-section source units carrying every language in one file.

Why

Keeping a document's translations in lockstep by hand doesn't survive contact with a real corpus. ktav-lang/spec shipped a fix for exactly this shape of defect more than once: a section that keeps every cross-reference and every keyword while one language quietly loses most of its bullet points, or a rendered-document parity check that passes because it only ever looks at the final output, never the sources that produced it. polydoc is the tooling that grew out of fixing those — generalized so any project assembling documents from small, per-section, multi-language units can reuse it instead of reinventing it.

What it does

  • Content-unit assembly — a manifest of small unit directories (meta.js + body-N.md), each body carrying every configured language behind a >>>>> lang=<code> separator, split at the same paragraph boundary in every language so part k is always the same fragment, never three independent slices that happen to add up.
  • Root-document assembly — the same unit shape for a handful of hand-maintained-feeling documents (README, CHANGELOG, ...), without the numbered-heading and section-inventory machinery a full spec needs.
  • Shared Markdown fragments — define code, images, links or other Markdown once and include it in every translation with reference-count parity.
  • Journalled crash recovery for output writes — writeBuildOutputs journals a transaction, then backs up and installs outputs sequentially, one file at a time. After a process crash, recovery restores a consistent pre-write state or completes a durable commit. This is not an atomic snapshot for unrelated readers: during backup/install, a reader can temporarily see missing, old, or new files. If a write is interrupted, call recoverBuildOutputTransaction before the next build or validation. File and directory flushing is limited by platform support; Windows and the underlying filesystem/storage can limit durability, so this is not a promise that every acknowledged write survives sudden power loss.
  • A separate root-document write contract — writeRootDocs writes each root document directly with writeFileSync, without this journal or cross-file crash recovery. It provides no cross-file snapshot guarantee; use checkRootDocs/docs:check to validate the result afterward.
  • A docs registry — classifies every public Markdown output as generated (rebuilt and byte-checked), frozen (historical, pinned by a SHA-256 lock) or internal, so a stale frozen file and a brand-new unregistered one are both caught instead of silently shipping.
  • Structural translation-parity checks — part alignment, list-item parity, indentation shape, and unwrapped paragraphs, checked against the sources, not the rendered output, where a defect can be pointed at a file and a line.

Install

npm install @ktav-lang/polydoc

Requires Node.js 24 or later.

Usage

configure() must run once, before anything else — it fixes the language set and file-naming conventions for the rest of the process.

writeBuildOutputs provides journalled process-crash recovery, not an atomic reader snapshot. checkBuildOutputs throws on byte-level divergence.

import {
  configure,
  buildBuffers,
  writeBuildOutputs,
  recoverBuildOutputTransaction,
  checkBuildOutputs,
} from '@ktav-lang/polydoc';

configure({
  langs: ['en', 'ru', 'zh'],
  outFileNames: { en: 'spec.md', ru: 'spec.ru.md', zh: 'spec.zh.md' },
  readmeFileNames: { en: 'README.md', ru: 'README.ru.md', zh: 'README.zh.md' },
  sectionInventoryLockFormat: 'my-project-section-inventory',
  rootDocuments: ['README', 'CHANGELOG'],
});

recoverBuildOutputTransaction('versions/1.0', 'versions/1.0/content');
const build = await buildBuffers('versions/1.0/content', {
  requireSectionInventoryLock: true,
  sectionInventoryLockPath: 'scripts/locks/section-inventory.1.0.lock.json',
});
writeBuildOutputs('versions/1.0', 'versions/1.0/content', build);
checkBuildOutputs('versions/1.0', 'versions/1.0/content', build);

See src/index.mjs for the full exported surface — content assembly, root documents, the registry, structural checks, and the transaction internals are all re-exported from the top-level entry point.

Shared Markdown fragments

Define language-independent Markdown once, before the first language block:

>>>>> shared=example
```js
console.log(42);
```

>>>>> shared=logo
![Logo](https://example.com/logo.svg)

>>>>> shared=links
[docs]: https://example.com/docs
  • Inside every >>>>> lang=<code> block, put <<<<< include=example, <<<<< include=logo or <<<<< include=links on its own column-zero line. Translated link labels can use the shared definition: [Read][docs].
  • Names are case-sensitive ASCII identifiers: start with a letter, then use letters, digits, _ or -. Definitions are scoped to one body-N.md file.
  • Fragments can contain code, images, links, lists, tables or other Markdown. Separator blank lines at the end of definitions are excluded; internal whitespace is preserved. Existing language-separator restrictions still apply.
  • Every configured language must use each shared name the same number of times. Empty definitions, unknown names, duplicate names and nested includes fail.
  • Includes are not expanded inside fenced code. No files or URLs are loaded, and nothing is executed. Relative links keep their normal Markdown meaning.

Expansion happens before heading, split-size, translation-shape and output checks, and before release-token substitution. Generated Markdown is complete; readers do not need Polydoc or an include extension. Legacy inline sources remain valid.

License

Licensed under either of Apache License, Version 2.0 or MIT license at your option.

About

Assemble multi-language documents from per-section triple-translated source units, with drift protection, a crash-safe atomic writer, and structural translation-parity checks.

Topics

Resources

Code of conduct

Security policy

Stars

0 stars

Watchers

0 watching

Forks

Releases

Packages

Contributors

Languages