npm package discovery and stats viewer.

Discover Tips

  • General search

    [free text search, go nuts!]

  • Package details

    pkg:[package-name]

  • User packages

    @[username]

Sponsor

Optimize Toolset

I’ve always been into building performant and accessible sites, but lately I’ve been taking it extremely seriously. So much so that I’ve been building a tool to help me optimize and monitor the sites that I build to make sure that I’m making an attempt to offer the best experience to those who visit them. If you’re into performant, accessible and SEO friendly sites, you might like it too! You can check it out at Optimize Toolset.

About

Hi, 👋, I’m Ryan Hefner  and I built this site for me, and you! The goal of this site was to provide an easy way for me to check the stats on my npm packages, both for prioritizing issues and updates, and to give me a little kick in the pants to keep up on stuff.

As I was building it, I realized that I was actually using the tool to build the tool, and figured I might as well put this out there and hopefully others will find it to be a fast and useful way to search and browse npm packages as I have.

If you’re interested in other things I’m working on, follow me on Twitter or check out the open source projects I’ve been publishing on GitHub.

I am also working on a Twitter bot for this site to tweet the most popular, newest, random packages from npm. Please follow that account now and it will start sending out packages soon–ish.

Open Software & Tools

This site wouldn’t be possible without the immense generosity and tireless efforts from the people who make contributions to the world and share their work via open source initiatives. Thank you 🙏

© 2026 – Pkg Stats / Ryan Hefner

@shbernal/typography

v0.3.0

Published

Orthotypography rules for English, French, Spanish, German and Dutch as data: check text against a cited convention, or fix the subset that is safe to fix unattended.

Readme

@shbernal/typography

CI npm No dependencies

Composable orthotypography rules, as data rather than as advice. Six styles ship, for English, French, Spanish, German and Dutch, and you can compose your own out of the same parts.

pnpm add @shbernal/typography
pnpm dlx @shbernal/typography check --style fr README.fr.md

Why this exists

Text that comes back from a model is typographically arbitrary. Ask for the same paragraph twice and one copy has ' where the other has ’, one puts a no-break space before a colon and the other an ordinary one, and both read as correct. Nobody sees it, because U+00A0, U+202F and a plain space render identically and ' and ’ are a font away from each other. What accumulates is a body of text set a dozen different ways, where every row is defensible and no two agree.

So the question here is not "did somebody set this correctly", which is a question about a publisher. It is "does the same content come back the same way every time", which is a question about a pipeline. Three things follow, and they are what this package is:

Rules are the primitive; a style is a bundle of them with a name. fr is a rule list, and so is the style you compose yourself out of compose, derive and the builders in @shbernal/typography/rules. There is no second mechanism for the shipped ones and no plugin API to learn: a style need not even be about a language. What you get from fr is somebody's homework on French, not a privileged position in the code.

Checking and fixing are different rule sets. A Spanish sentence ending in ? with no opening ¿ is unambiguously wrong and not safely fixable, because inserting the mark means deciding where the clause began. So check reports everything and fix applies only what is safe unattended. A finding that cannot be fixed is the interesting kind: it means somebody has to decide, not that nobody got round to it.

A style does not assert what its citation does not fix. Every rule names where it came from, which is what stops a bundle becoming a place where preferences collect: Imprimerie nationale for French, RAE for Spanish, Duden for German, the Nederlandse Taalunie for Dutch, and for English, which has no such body, the two manuals en ships the agreement of. Where a source admits two spellings, the rule covers what is wrong under both and preserves the rest. The Lexique typesets its own guillemets with U+202F and specifies U+00A0 in its own table, so fr keeps whichever width a document already uses; the first version did not, and it "corrected" 6,462 guillemets in 2.4M characters of professionally typeset French. How that was found and fixed is the most useful thing in this repo.

What it is good for

Checking documentation and content in CI. typocheck check --style fr --strict over your French or Spanish Markdown, exiting non-zero on findings. Every finding carries a line, a column, the citation and an escaped excerpt, so a report is readable and a reviewer can see the character.

Normalizing generated or translated text. style.normalize is the safe subset and nothing else, and { id, normalize } is the whole contract, so a host binds a style without either package importing the other. The id is an era stamp, fr@4ed7f1b2db8f, derived from the rules themselves: two bodies of text carrying it were checked by the same rules, and the day a rule moves the stamp moves without anybody remembering to bump it. Carry it beside anything you normalized. If you normalize field by field and the fields must agree with each other, the French width section of api.md is the part you need.

Making a house style, and being able to say what it is. derive(fr, { drop: [...] }) or a compose call in a typography.config.mjs, and typocheck --style acme-fr is your rules with your citations. The stamp is derived, so a house style cannot quietly claim to be the shipped one, and a config the CLI loaded is named in the report footer.

Giving a coding agent typography it cannot guess at. A Claude Code skill ships inside the package, so the skill and the binary it invokes cannot be different versions. It teaches the four things --help does not, starting with the fact that these findings are invisible and must be quoted escaped.

Reading the rules. A style is a plain array of rules with summaries and citations. import { fr } from '@shbernal/typography/fr' and print it.

Six conventions, not one with a locale flag

| | English | French | Spanish | German (DE/AT) | German (CH) | Dutch | |---|---|---|---|---|---|---| | Quotation marks | curly, no rule on which pair | « … » | «…» | »…« | «…» | no rule | | Space inside them | no rule | required, U+00A0 or U+202F | forbidden | forbidden | forbidden | n/a | | Space before ; : ! ? | forbidden | required | forbidden | forbidden | forbidden | forbidden | | Opening marks | none | none | ¿ ¡, paired | none | none | none |

French and Spanish use the identical pair of characters with opposite spacing. German points them the other way and Switzerland points them back. That is one question with four answers rather than four questions, so it is one rule builder taking what the correct spacing is, called once per style with its own citation; the styles are rule lists over shared parts and there is no engine with a locale switch in it. And there is no bare de: a style id gets stamped onto a body of text, and a stamp that cannot tell a Swiss quotation from a German mistake is worse than no stamp at all.

English is the newest column and the one with no standards body behind it. It ships the intersection of Chicago and New Hart's Rules and rules on nothing they disagree about, which is why the serial comma is absent and why the dash convention is reported rather than repaired: Chicago closes an em dash up and Oxford sets a spaced en dash, so a repair in either spelling would retype text that is correct in the other. What is left is mostly one character. it's, 'tis and '90s all want U+2019, and a smart-quote pass gives the last two U+2018 instead.

Dutch is the other interesting column, and the blank is not a gap. The Taalunie's standard is a spelling standard and rules on neither spacing nor quotation marks, and its advice service says outright that there are no fixed rules for choosing between ‘…’ and “…” - and then recommends picking one and keeping to it. So nl asserts no system and instead reports a document that uses more than one. Its centre of gravity is the apostrophe, where Dutch is unusually demanding: auto's, 's morgens, A4'tje, 's-Gravenhage.

Status

0.3.0 is the published version and pre-1.0 is the accurate thing to say. It breaks every consumer of 0.2.1: styles are composed, rule ids are global, stamps are derived, --lang is --style, and en is new. CHANGELOG.md has the migration, one line per thing that moved.

The four original languages have been run past real published text: 2.4M characters of French, 2.4M of German, 1.1M of Spanish, 880k of Dutch, 699k of Swiss German. Those corpora are gone from the repository along with the gates that ran them, because the question they answered is not the one above; what they established is in docs/provenance.md, and what replaced them is audit, which holds a style to idempotence, conformance and non-interference over text you supply.

English has had none of that, and it is the first style here to ship without it. It was written against the fixtures and the properties rather than against a corpus, which is what every style is held to now; what it has not had is a measurement of how often its rules fire on text somebody already set correctly. That is the measurement that took the French guillemet rules apart, so treat en's findings as the newest thing in the package.

Dutch is the thinnest of the measured ones: it saw one corpus, which is also one of its two citations, and two of its rules met nothing they could match. That is recorded in docs/provenance.md rather than smoothed over, along with what each number is worth and the narrowings the measurements bought. Zero findings can mean the text was set correctly or that it contained nothing the rule could match, and only the first is evidence.

Documentation

| | | |---|---| | docs/api.md | Library and CLI | | docs/design.md | Why the package is shaped this way | | docs/provenance.md | Where the defaults came from and what was measured | | docs/development.md | Changing a rule, adding a style, cutting a release |

Contributing, and the report worth most

The single most useful issue this project can receive is a false positive: text that was set correctly and that typocheck complained about anyway. Unit tests measure whether a rule fires on text written to make it fire, which is recall, which was never in doubt. Only prose somebody wrote without a thought for this checker measures the failure mode these rules have, and that is the one thing this repo cannot generate for itself.

The other report worth as much is a style that misbehaves in company: a fix that does not settle, a fix that leaves behind something check still reports, or two rules that undo each other. audit is exported so you can find those in a style you composed, and a violation it reports in a shipped one is a bug here.

MIT, in LICENSE.