Check your coverage

pithy doctor

There is no pithy i18n check, and that is deliberate: coverage is a health question about a project, and health questions live in one report.

What it checks

pithy doctor is what runs it, writing a locale is what you are checking, and supported locales is what the kit already covers.

For every locale you serve, every key reachable in the default locale must be reachable in that one too — through your catalog, the kit’s translation, or a capability’s own English.

What a gap looks like

i18n.supportedLocales.fr
  3 messages have no `fr` translation, so a reader in that language meets en instead.
  Add them to `i18n({ messages: { fr: … } })` in your `pithy.config.ts`: app/greeting, app/nav.settings, app/sign_out.

It names the locale and the missing keys, rather than a count you then have to go and find.

It fails the exit code

So a locale with gaps is caught in CI rather than by a reader.

That is the whole reason it lives in doctor: pithy doctor is already the command CI runs, and a check nobody runs is a check that does not exist.

It is a local finding — established from your own files, needing no account. Nothing about language is a question for the Cloudflare API, so there is no account tier here and no version of this check that can be skipped for being offline.

A gap is not an error at runtime

A reader whose key is missing meets English, because the layer walk falls through to it.

That is the right behavior — a screen with one English sentence on it is far better than a screen with a raw key, or a 500. What the check adds is that you find out rather than a reader finding out for you.

In CI

pithy doctor --json

The findings are in the payload with everything else, each carrying its own sentence — so an agent fixing one never has to reproduce the report’s wording from the parts.

What it does not check

Whether the translation is any good. A key with a value is covered. A key whose Spanish is a machine translation nobody read is also covered.

That is not something a check can answer, and the kit is honest about its own state: its Spanish declares itself an unreviewed first pass in the file’s own head.

Whether a payload-carrying email template’s payload is localized. The shell follows the job and your words are yours — the catalog cannot translate a sentence it has never seen, and it cannot tell you that you forgot to.

Zod’s per-field validation messages. Worth knowing about, because it is a real gap rather than an oversight.

The one gap the kit states rather than hides

A validation failure’s top-level message translates like any other code. The per-field issue messages inside it do not.

That is a reachability problem rather than a missing feature. The validation library has exactly the right primitive — a per-parse error map, request-scoped — but the kit’s validation surface calls it without a way to pass one, and the escape hatch it does offer never receives the request. Getting a request’s locale in there needs machinery that is out of scope today.

The global slot is not the workaround, and it is banned repository-wide by a lint rule. A Worker isolate is reused across requests, so a locale written into a module-level error map during one request is still there for the next — a silent, unreproducible failure where one reader’s validation error renders in another reader’s language.

Refusing a fix that is worse than the gap is the right call, and saying so is better than leaving it to be discovered.

Check it worked

  • Adding a supported locale with no catalog fails doctor, naming the keys
  • Filling them makes it pass
  • CI catches a key added in English and not translated
  • A missing key at runtime falls back to English rather than showing the key
ESC