Conventions: language, commits, comments
This page is the contract for who reads what, in which language, and for the shape of a
commit. It is written in English on purpose: from generation 6 on, everything addressed to
a developer is English, and everything addressed to a user is Persian (decision 3.3 in
plan-v6/README.md). The rest of docs/reference is migrated page by page (phase 9); until
then a Persian page is not “wrong”, it is “not yet migrated”.
Language by audience
Section titled “Language by audience”| Text | Reader | Language | Kept by |
|---|---|---|---|
Code comments, JSDoc, eslint-disable reasons |
developer | English | darzsaz/no-persian-comment (ESLint), comment-pattern (Stylelint) |
| Commit subject and body | developer | English, Conventional Commits | commitlint on commit-msg and in CI |
docs/reference/*, CLAUDE.md |
developer | Persian (see below) | scripts/doc-claims.mjs keeps its claims true |
CONTRIBUTING.md, this page |
newcomer | English | review |
docs/journal/* — new entries |
developer | English; old entries stay as written | — |
Developer tooling output (scripts/*, guards, CI logs) |
developer | English for new and touched scripts | review |
| Identifiers, file names, CSS class names | developer | English (as today) | — |
UI strings, print sheets, CLI --help, error messages |
user | Persian source, English via the i18n catalog | lingui/no-unlocalized-strings, i18n:check |
User guide (apps/site/.../guide), landing page |
user | Persian, with an English site | — |
Plan folders (plan-v*/) |
repository owner | Persian | — |
Why English for developer text, with numbers: on 2026-09-15 the repository had 5,938 Persian comment lines out of 8,636, and 292 of the last 324 commit subjects were Persian. The code itself — identifiers, types, library APIs, the technical vocabulary of every dependency — is English, so every Persian comment switches script mid-line, is unreadable to any tool or contributor without Persian, and cannot be searched together with the code it explains. The user never sees a comment or a commit; the user does see the UI, which stays Persian.
Why the reference went the other way in phase 9.1: it had been drifting into two half-written
copies, one Persian and one English covering eleven of twenty pages. Nearly half of it
(darz-format.md) is generated from the schema’s Persian field descriptions, so translating it
means editing the data model’s user-facing strings; the rest is read by the one person who
maintains this repository, who reads Persian. One source beats two, and the audience-facing
documents — the user guide and the landing page — stay in both languages. rules.en.md is the
exception that proves it: it is generated from the en-US catalog and cannot drift.
Persian inside an English comment
Section titled “Persian inside an English comment”A comment may quote Persian as a value: a UI string, a catalog name, a test fixture. The
quote is the signal — «…», "…", '…' or `…`:
// The label is «کابینت ۲» in fa-IR and "Cabinet 2" in en-US; the number is user data.Persian prose outside quotes is a Persian comment and is reported.
Commits
Section titled “Commits”Format (Conventional Commits 1.0):
<type>(<scope>): <subject>
<body>
<footer>- type:
feat,fix,refactor,perf,test,docs,build,ci,chore,style,revert— the set of@commitlint/config-conventional. - scope: one of
core,geometry,i18n,report,ui,web,cli,site,docs,plan,tooling,deps,deps-dev,release. Several scopes are separated by a comma (fix(core,report): …); a change that touches everything has no scope. - subject: imperative, lower case, no trailing period, Latin script, header ≤ 100 characters. It says what changed, not which files.
- body: what and why. If a road was tried and abandoned, it is written here — for the person who walks the same road three months later it is worth more than the fix. Lines ≤ 100 characters. Persian UI text may be quoted.
- footer:
BREAKING CHANGE: …when a file format, CLI flag or public API changes;Co-Authored-By: …; issue references.
Examples:
fix(geometry): open hinged fronts outward, not into the carcass
The swing sign was inverted in front-instances.ts since 52470cf: the axis waswritten by hand as +1/-1 with no convention. The axis is now the cross productof the up vector and the hinge edge; the angle comes from the hinge catalogentry instead of a fixed 100°.
Tried first: flipping the sign only. That fixed base units and broke lift-updoors, which rotate around a horizontal axis — hence the cross product.chore(deps-dev): bump vitest from 4.1.11 to 4.1.12Merge commits (Merge branch …) and reverts written by git are accepted as they are.
Enforcement
Section titled “Enforcement”commit-msghook:pnpm exec commitlint --edit(lefthook). Bypass for one commit only withLEFTHOOK=0, and only for a fixup that is squashed before push.- CI: every commit of the push or pull request that was made after the contract commit
(
d4ae1c4) is linted (commitsjob inci.yml; the list comes fromscripts/commit-range.mjs, by committer date, merges skipped). The pre-contract history — generation 5 and the wave-6 worktree commits merged in phase 0 — stays as written. scripts/test/commitlint.test.mjsruns the configuration against sample messages so a config change that silently accepts Persian subjects turns the guard tests red.
Comments
Section titled “Comments”- Explain why, and what was tried and did not work — not what the code does (rule 4 of
CLAUDE.md). Most comments in this repository are the minutes of a mistake. - English, see above. Existing Persian comments are counted in a shrink-only baseline
(
scripts/lint-baseline.jsonfor ESLint,stylelint-suppressions.jsonfor CSS): a new Persian comment in a file that has none fails lint; fixing one and not updating the baseline also fails (pnpm lint:baseline --update,node scripts/css-lint.mjs --update). The baseline only goes down; phase 8.8 of plan-v6 takes it to zero. eslint-disableneeds a reason after--(require-description); the reason is English too.
Related
Section titled “Related”CONTRIBUTING.md— the rules of the repository and who keeps them.- i18n.md — messages, catalogs, and why the core returns messages, not strings.
- testing.md — the guard-of-guards tests under
scripts/test/. - traps.md — every known trap and the guard that catches it.