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

@jagu.cz/typopo-layer

v1.0.0

Published

Nuxt layer that fixes Czech/Slovak/English/German microtypography in i18n locale files at build time.

Readme

Nuxt Typopo Layer

A Nuxt layer that runs typopo over your @nuxtjs/i18n locale files at build time — non-breaking spaces after single-letter prepositions, „Czech quotes“, real ellipses, en dashes, × for dimensions — without ever touching the JSON in your repository.

pnpm add @jagu.cz/typopo-layer
// nuxt.config.ts
export default defineNuxtConfig({
  extends: ['@jagu.cz/typopo-layer'],
})

That's the whole setup. Locales are picked up from your existing i18n.locales: cs, sk and rue map to themselves, en to typopo's en-us, de to de-de, and a regional code (en-GB) falls back to its base language. Anything typopo has no rules for is reported on startup and left alone.

Why build time

The fixes belong in the rendered output, not in the repository:

  • Running typopo in CI would mean committing its output back to the branch.
  • Running it in the browser would leave the SSR and prerendered HTML — everything crawlers, og: scrapers and no-JS clients see — carrying the plain text, and the inserted non-breaking spaces re-wrap paragraphs, so lines would visibly jump after hydration.

So the layer rewrites the JSON as the bundlers load it. Your source files keep the plain text; the client and server bundles get the same fixed strings.

Looking at what it does

typopo-locales            # report every string the build will change
typopo-locales --check    # exit 1 if anything would change (for CI)
typopo-locales --write    # bake the fixes into the source files (opt-in, never automatic)

Add it to your package.json for convenience:

{
  "scripts": {
    "typopo": "typopo-locales"
  }
}

The report names the invisible characters ([NBSP], [NNBSP], , ) — without that a before/after pair looks identical in a terminal.

Which files and locales it reads comes from .nuxt/typopo.json, written by the module on every dev/build/prepare, so the report matches the build exactly. On a fresh checkout with no .nuxt yet it falls back to i18n/locales and maps locales by filename; --dir <path> and --map cs=cs,en=en-us override both.

Configuration

Everything is optional.

export default defineNuxtConfig({
  extends: ['@jagu.cz/typopo-layer'],

  typopo: {
    // Override the locale mapping. Values are typopo locales: cs | sk | en-us | de-de | rue.
    locales: { en: 'en-us' },
    // Where the locale JSON lives, relative to the project root.
    // Defaults to @nuxtjs/i18n's own directory (i18n/locales).
    langDir: 'i18n/locales',
    // Leave strings containing `<` alone. On by default: typopo is not markup-aware and
    // rewrites the quotes inside a tag, so `<a href="/x">` comes back as `<a href=„/x“>`.
    skipMarkup: true,
  },
})

What it protects

Three things bite when you run typopo over vue-i18n messages, and the layer handles all three:

| Case | What would happen | What the layer does | | ----------------------------- | -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | ------------------------------------------------------------- | | {'@'} literal interpolation | typopo curls the quotes to {’@’}, the vue-i18n compiler rejects the file, and every message in it renders as its bare key path | placeholders are masked while typopo runs | | Strings containing markup | quotes inside a tag get curled and the link dies, silently | skipped, and listed in the CLI report | | Multi-line messages | typopo treats a newline as a hard boundary: a capitalised preposition at the start of a line keeps its breakable space, and an en dash at a line end gets pulled onto the previous word (changes— including) | each line is fixed separately, then rejoined byte-identically |

Plural pipes (no items \| one item) need no special handling — typopo leaves them alone.

@nuxtjs/i18n 10.6 and optimizeMessageBundling

10.6 stopped putting messages through either bundler: it copies the locale files to .nuxt/i18n-assets/, reads them off disk and serves them from an endpoint. Faster and smaller (the release claims 38% off build time and 75% off build output) — but no plugin ever sees the JSON, so typography fixes reach nothing.

The layer opts out for you, by setting i18n.experimental.optimizeMessageBundling: false unless your project has chosen a value. Nothing to add to your nuxt.config, and on versions without the flag it is an unknown key that is ignored. Verified on 10.6.0: with the opt-out the served HTML carries the fixes, without it nothing is transformed and the layer says so.

The cost is that you don't get 10.6's message-bundling win while you use this layer. If you'd rather have the optimisation than the fixes in server-rendered HTML, take it back explicitly:

i18n: {
  experimental: { optimizeMessageBundling: true },
}

The layer then warns on every build that the fixes are client-only.

Known limitation: dev's first paint

The locale files are read by two bundlers — Vite for the client and the Vue SSR build, Nitro for @nuxtjs/i18n's server-side messages — and the layer transforms both, so a production build is consistent everywhere.

nuxt dev renders server-side through neither of them: the JSON reaches Node without passing a plugin, so the first paint shows the plain text and the fixes appear as the client takes over. Only dev is affected. When you are eyeballing where a line wraps in dev, judge it after hydration.

If nothing happens

The module warns loudly rather than shipping unfixed copy in silence:

  • no locale files were transformed — the path is wrong, or your i18n version has stopped handing the files to the bundlers. Check typopo.langDir, and see the 10.6 section above.
  • no typopo locale for xx — that locale ships unfixed; map it via typopo.locales.
  • optimizeMessageBundling is on — your project asked for the optimisation, so the fixes are in the client bundle only, not in the server-rendered HTML.
  • … was already transformed by another plugin — a plugin-ordering regression; the layer skips rather than corrupting the messages.

Development

.playground is a regular Nuxt app with @nuxtjs/i18n and a locale file full of the awkward cases above. It prints every message twice: as the browser lays it out, and with the invisible characters escaped.

pnpm install
pnpm dev          # boot .playground on http://localhost:3000
pnpm build        # then: node .playground/.output/server/index.mjs
pnpm typopo       # the CLI against the playground locales
pnpm lint
pnpm typecheck

Verifying a change end to end means looking at the served bytes, not the source:

curl -s http://localhost:3000/ | grep -c $'\xc2\xa0'

Distributing

Bump the version in package.json, confirm the files list, commit, then push a tag — CI publishes to npm.