Skip to main content

ADR 008: Bilingual Structural Invariant — The Symmetry Guardrail

Status: Active Decider: Architecture Lead Date: 2026-04-20 (v0.7.0 sprint, D045 — Diátaxis Migration)


Context

Zenzic.dev is a bilingual documentation site. English (docs/) is the authoritative source; Italian (i18n/it/docusaurus-plugin-content-docs/current/) is the translation mirror. Docusaurus's language switcher resolves Italian pages by mirroring the English filesystem path: a user on /docs/reference/finding-codes switches to /it/docs/reference/finding-codes — and Docusaurus serves the file at the corresponding path in the i18n/it/ tree.

During the v0.7.0 Diátaxis migration (D045), 29 English files were renamed and moved to align with the four-quadrant structure. Several Italian files were not moved atomically in the same commit. The result: the language switcher produced 404 errors on pages where the English file had been moved but the Italian mirror had not.

This class of bug is particularly insidious because:

  1. No build-time error is produced. onBrokenLinks: 'throw' only detects

    internal [text](link) references — it does not validate language switcher paths.

  2. The bug is invisible in development mode. npm run start serves a single

    locale. The switcher is inactive. The 404 only appears in just build output when both locales are built simultaneously.

  3. The time-to-detection window is long. A missing IT file discovered three

    commits after the EN rename requires a forensic git blame to trace — the coupling between the two moves is no longer visible in the history.


Decision

Every structural change to docs/ must be applied atomically to i18n/it/docusaurus-plugin-content-docs/current/ in the same commit.

This is not a recommendation — it is a hard invariant. Three specific rules follow from it:

Rule 1 — Atomic Moves

Any git mv applied to a file in docs/ must be accompanied by a corresponding git mv in the Italian mirror in the same commit. A rename in English is a rename in Italian.

# Correct — both moves in one commit
git mv docs/guides/intro.mdx docs/tutorials/intro.mdx
git mv i18n/it/docusaurus-plugin-content-docs/current/guides/intro.mdx \
i18n/it/docusaurus-plugin-content-docs/current/tutorials/intro.mdx
git commit -m "refactor(docs): move intro to tutorials quadrant (EN + IT)"

Rule 2 — Slug Parity

If a slug: value is changed in an English file, it must be changed identically in the corresponding Italian file. A diverged slug: causes the language switcher to produce a 404, with no build-time warning.

Rule 3 — Symmetry Validation Before Every Commit

Before committing any change that touches the filesystem structure (renames, additions, deletions), the following command must exit 0:

diff \
<(find docs -name "*.mdx" | sed 's|^docs/||' | sort) \
<(find i18n/it/docusaurus-plugin-content-docs/current \
-name "*.mdx" | \
sed 's|^i18n/it/docusaurus-plugin-content-docs/current/||' | sort)

Any output from this command represents a structural asymmetry that will produce a 404 on the Italian language switcher.


Rationale

1. Italian is a First-Class Citizen

The Italian documentation is not a secondary asset or a "nice to have". It is part of the Safe Harbor contract. A link that works in English but 404s in Italian is a structural failure of the documentation system — equivalent to a broken internal link in the English tree.

2. The Language Switcher Has No Safety Net

Docusaurus's onBrokenLinks: 'throw' does not cover language switcher paths. This means the only safeguard is the contributor discipline enforced by this ADR. There is no build-time backstop.

3. Git History Coherence

An atomic commit that moves both EN and IT files creates a coherent history unit: the rename is a single, reversible operation. Split commits create history noise and make bisect unreliable when investigating regressions.


Invariants (Non-Negotiable)

  • The symmetry diff command must exit 0 before any commit that modifies the

    filesystem structure of docs/ or i18n/it/.

  • New files added to docs/ must have a corresponding stub added to i18n/it/

    in the same commit — even if the Italian content is a copy of the English until a translation is provided.

  • The pre-commit hook (pre-commit-config.yaml) enforces symmetry at the gate.

    Bypassing it with --no-verify on a structural commit is a Class 1 violation (Technical Debt).


Consequences

  • Every contributor who renames or moves a documentation file must be aware of

    the Italian mirror — this is a non-optional part of the contribution workflow documented in CONTRIBUTING.md.

  • The just preflight recipe (uvx pre-commit run --all-files) enforces this

    check in CI. A PR that breaks structural symmetry will fail at the gate.

  • The symmetry invariant applies to directory structure only. Italian

    content may lag behind English during active sprints, as long as the file is present (even as a stub). A 404 is worse than a stale translation.