Contribute

Add your language, or fix a rule.

polytypo is open source and the spec is the product: a locale is a data file, fixtures and a citation, and a rule is prose that every runtime implements. Pull requests are welcome on GitHub. Locales that exist today are listed on Locales.

Adding a locale

This is the sequence every locale is added through. xx stands for the new locale's tag.

  1. Read spec/schema/locale.schema.json

    It defines what a locale file may say: quote glyphs and inner-space rule, the parenthetical and range dash convention, whether ellipsis abbreviates after terminal punctuation, hyphen-binding word lists, and the no-break-space triggers. Literal strings, literal code points, string lists and enum values only — no regex, no priority numbers, no conditionals. If the locale needs a distinction the schema can't express, that is a spec discussion, not a workaround in the locale file.

  2. Write spec/locales/xx.json

    One field at a time, each backed by a citation in the mandatory sources array — a national style guide, a standards body (Accademia della Crusca, a major dictionary or publisher's house style), never a blog post or a contributor's own habit. A locale is accepted only as the triple this project requires: data, fixtures, citation. Where the source exists but is paywalled, the entry names the prevailing form it standardises on and is marked as a decision rather than a citation — uniform everywhere beats correct in a book nobody can open.

  3. Add cases to spec/fixtures/xx.json

    One { "in", "out", "rule" } pair per behaviour the citation asserts, plus at least one edge case per rule — the ordinary sentence is not the interesting test, the nested quote or nasty numeric range is. Every fixture is automatically also an idempotency case: transform(out) === out is checked for free.

  4. Register it in spec/locales/registry.json

    Add "xx" to the locales array. Only add an alias if the fallback is itself a cited decision, not a guess of convenience.

  5. Validate and test

    npm run validate:spec checks the file against the schema and NFC-normalizes it; npm test checks the structure and citations of the new fixtures. Each runtime's conformance suite then runs every fixture, and every fixture doubles as an idempotency case. All of it must be green — a red run blocks the locale, not just a warning.

  6. Open a pull request

    Disagreements about what a language actually does are settled by citation, not preference — if a review turns up a more normative source, that source wins, not whoever argued longer.

Changing or adding a rule

Rules are cross-locale algorithms, not per-language code. spec/rules/order.json is the single source of truth for a rule's id, pipeline order, default and which locale fields it reads; spec/rules/<id>.md is normative prose written before the implementation — extracting a spec from a finished implementation is exactly the rework this project's spec-first discipline exists to avoid. A behaviour change starts with a fixture in spec/fixtures/, not a change in a runtime. Implementation constraints that are already binding, across all five runtimes: no regex in a rule (Go's RE2 has no lookbehind — a rule is a single left-to-right scan over code points with explicit lookaround), index by Unicode code point rather than a native string, and rules emit edits for the pipeline to apply rather than mutating text directly, so applyEdits can enforce the contract instead of trusting it.

Already using polytypo? Add our badge

Two lines: a script tag and a span. No per-embed network call. Pick a language and a theme, then copy the code into your footer or credits. Embedding notes cover Content Security Policy. Sites with the badge are listed on Sites using polytypo.

<script async src="https://polytypo.dev/badge.js"></script>
<span data-polytypo-lang="en" data-polytypo-theme="light"></span>

Search runs in your browser. Nothing is sent anywhere.