Skip to content
loclizr
  • CLI commands init, build and check.
  • JSON catalogs in (ICU, or i18next converted at read time); typed ESM functions out, one module per top-level key.
  • Every rule in every build, with stable codes and per-rule severity.
  • A per-message context record, compared by check.
  • Typed lookup groups for run-time keys.
  • A locale store with cookie persistence, React bindings, AsyncLocalStorage request scoping for Web Fetch handlers, and a header primitive for Express and Fastify.
  • One example app, React on Vite: the reference implementation and the only one tested.

For content unknown at build time. It needs a runtime ICU parser, which the browser must never ship; a separate entry would keep that true for anyone not importing it. Until then, such content is not a message. Run-time keys: Dynamic keys.

A second compiler code path, with platform-dependent recursive fs.watch and its own severity semantics, that nothing in v0.1 needs.

The loop today is loclizr build --no-fail in predev, plus either nodemon -w locales -x 'loclizr build --no-fail' or the small Vite plugin in the example app’s vite.config.ts, which reruns build when a catalog changes. A 2000-key, 10-locale catalog builds in about half a second on a laptop, so each loop is a full rebuild; a watcher would be reconsidered only if large catalogs make that too slow.

The record says where a string is used, never what it is.

Field Why not yet
Glossary hits Cheap, deterministic, first in line. Its motivating number (terminology retrieval gains in machine translation) is single-vendor and unreproduced, so it does not gate a first release.
Surrounding copy The record has each usage’s file and enclosing scope. Nearby text means reading the component, and the scan is a tokenizer, not a parser.
Element role Button, heading or tooltip is a JSX tree question the scan cannot answer.

v0.1 never finds string literals in components or rewrites them into message calls: that needs a bundler AST pass, the tax this project avoids. A retrofit would be a separate opt-in command, run once, outside the build.

Middleware runs in another runtime and cannot wrap a render; the root layout entry point is open. No test, no example, no claim. The message layer runs anywhere; the Pages Router reaches runWithLocale through localeFromHeaders.

Hydration agrees because the locale comes from a cookie or Accept-Language, never the path. A /de/cart prefix is a second channel to keep in agreement: the router’s job.

Every locale is inlined into each message function, so bundle size grows with locale count. One locale per request needs the metaframework’s build, which this compiler stays out of. Sizes are on Limits.

Bug reports against the example app, and catalogs large enough to make build slow. Continuity covers how the project is run.