UNPKG

cz-typography

Version:

Universal Czech typography fixer for JavaScript, React, Next.js, and any SSR framework. Non-breaking spaces after one-letter prepositions, units, dates, ordinals and more.

236 lines (164 loc) 8.05 kB
# cz-typography Universal Czech typography fixer for JavaScript, React, Next.js and any SSR framework. Replaces regular spaces with **non-breaking spaces** (`\u00A0`) so the resulting text obeys [Czech orthography rules](https://prirucka.ujc.cas.cz/?id=880). One-letter prepositions, conjunctions, units, dates, initials and more never end up dangling at the end of a line. Maintained by [Jan Vyskočil](https://github.com/OfficialVysko) (a.k.a. Vysko). ## Features - One-letter prepositions and conjunctions: `k`, `s`, `v`, `z`, `o`, `u`, `a`, `i` – glued to the next word, **case-insensitive**. - Numbers + units: `5 km`, `30 °C`, `100 Kč`, `5 €`, `100 Ω`, `1.5 kWh`, `120 km/h`, `25 %`. - Dates: `5. 12. 2024`, `5. 12.`. - Ordinal numbers before lowercase words: `1. ledna`, `25. listopadu`. - Initials: `J. Novák`, `J. R. R. Tolkien`. - Roman numerals after names: `Karel IV.`, `Jindřich VIII.`. - Thousands separators: `10 000``10\u00A0000`, `1 000 000`. Every rule can be turned off individually. ## Installation ```bash npm install cz-typography # or yarn add cz-typography # or pnpm add cz-typography ``` Peer deps `react`, `react-dom` and `next` are optional – install only what you actually use. ## Quick start ### Plain JavaScript ```js import { fixCzech } from 'cz-typography'; fixCzech('J. Novák běžel 5 km v domě.'); // => 'J.\u00A0Novák běžel 5\u00A0km v\u00A0domě.' ``` ### Pure React (Vite, CRA, anywhere) Wrap your application in `<TypoWrapper mode="dom">` and let it walk the rendered DOM after mount. This is the only way to fix text that lives inside nested components (`<App />`, third-party components, …). ```jsx import { createRoot } from 'react-dom/client'; import { TypoWrapper } from 'cz-typography/react'; import App from './App'; createRoot(document.getElementById('root')).render( <TypoWrapper mode="dom"> <App /> </TypoWrapper>, ); ``` For small JSX fragments where you control the text directly, the default `mode="jsx"` is fast and side-effect-free: ```jsx <TypoWrapper> <p>v domě</p> <p>Karel IV. žil v 14. století.</p> </TypoWrapper> ``` ### React SSR (Express, Vite SSR, Astro, Remix, …) Run `fixCzechHtml` on the rendered HTML before sending the response. ```js import { renderToString } from 'react-dom/server'; import { fixCzechHtml } from 'cz-typography'; import App from './App.jsx'; app.get('*', (req, res) => { res.send(fixCzechHtml(renderToString(<App />))); }); ``` For streaming SSR pipe the response through `createFixCzechStream`: ```js import { renderToReadableStream } from 'react-dom/server'; import { createFixCzechStream } from 'cz-typography'; const stream = await renderToReadableStream(<App />); return new Response(stream.pipeThrough(createFixCzechStream()), { headers: { 'content-type': 'text/html' }, }); ``` ### Next.js App Router (whole-website coverage) Drop a single `middleware.ts` in your Next.js root and every HTML response will be fixed on the fly. Streaming, RSC payload and Suspense are preserved. ```ts import { withCzTypography } from 'cz-typography/next/middleware'; export const middleware = withCzTypography(); export const config = { matcher: '/((?!api|_next/static|_next/image|favicon.ico).*)', }; ``` Need fine-grained control inside server components? ```jsx import { Typo } from 'cz-typography/react'; export default async function Page() { const post = await getPost(); return ( <article> <Typo> <h1>{post.title}</h1> <div dangerouslySetInnerHTML={{ __html: post.body }} /> </Typo> </article> ); } ``` For dynamic strings inside server components prefer the runtime utility: ```jsx import { fixCzech } from 'cz-typography'; const post = await getPost(); return <h1>{fixCzech(post.title)}</h1>; ``` #### Optional build-time loader For sites with a lot of static JSX text you can opt into compile-time rewriting via a webpack loader, then runtime middleware can be skipped (or reduced). Both can be combined safely. ```js const { withCzTypography } = require('cz-typography/next'); module.exports = withCzTypography({ /* the rest of next config */ }); ``` ## API ### `fixCzech(text, options?)` Apply the rules to a string. Non-strings are returned unchanged so the function is safe to use without guarding. ### `fixCzechHtml(html, options?)` Tokenises HTML and applies the rules only to text nodes. Tags, attributes, comments, doctype and the contents of `<script>`, `<style>`, `<pre>`, `<code>` and `<textarea>` are preserved. ### `createFixCzechStream(options?)` Returns a Web-Streams `TransformStream` that processes HTML on the fly. Works on Vercel Edge, Cloudflare Workers, Node.js 18+, Deno and Bun. ### `<TypoWrapper>` (`cz-typography/react`) Client component with two modes: - `mode="jsx"` (default) – traverses children passed in JSX. - `mode="dom"` – walks the rendered DOM after mount and follows future mutations. Props (all optional): - `mode``'jsx' \| 'dom'`. - `as` – element used in DOM mode (default `'span'` with `display: contents`). - `options` – complete options object. - Shortcut props: `prepositions`, `units`, `initials`, `dates`, `ordinals`, `roman`, `thousands`. ### `<Typo>` (`cz-typography/react`) Async server component. Renders children to static markup, applies `fixCzechHtml`, re-injects via `dangerouslySetInnerHTML`. **Loses client interactivity inside the wrapped subtree** – use it only on self-contained static fragments. ### `useFixCzech(text, options?)` (`cz-typography/react`) Memoised React hook returning the fixed string. ### `fixCzechDom(node, options?)` / `observeCzechDom(node, options?)` (`cz-typography/react`) Lower-level utilities that the DOM mode of `<TypoWrapper>` is built on. Handy if you want to apply the rules to a specific subtree manually. ### `withCzTypography(config?)` (`cz-typography/next/middleware`) Build a Next.js middleware that pipes HTML responses through the stream. Accepts: - `options` – rule toggles. - `shouldProcess(request)` – custom predicate. - `contentTypes` – content-type substrings to act on (default `['text/html']`). ## Options ```ts fixCzech(text, { prepositions: true, units: true, initials: true, dates: true, ordinals: true, roman: true, thousands: true, }); ``` All defaults are `true`. Set any to `false` to skip that rule. ## When to use what - **Pure React SPA (Vite/CRA)**`<TypoWrapper mode="dom">` around `<App />`. - **Pure React SSR**`fixCzechHtml(renderToString(...))`. - **Streaming SSR**`stream.pipeThrough(createFixCzechStream())`. - **Next.js App Router – whole site**`cz-typography/next/middleware`. - **Next.js App Router – local fragment**`<Typo>`. - **Client React fragment anywhere**`<TypoWrapper>` (jsx mode). - **Dynamic server text from DB**`fixCzech(post.title)`. - **Hook usage**`useFixCzech(text)`. - **Astro/Remix/anything else**`fixCzech` for strings or `fixCzechHtml` for rendered HTML. ## Migrating from v1 `v2` is a major rewrite – breaking changes: - The package is **dual ESM/CJS** with proper `exports` map. - The named export of the React component has moved – `import TypoWrapper from 'cz-typography/react'` still works, but `import { TypoWrapper } from 'cz-typography/react'` is the new canonical form. - New rules (`dates`, `ordinals`, `roman`, `thousands`) are enabled by default. - The Czech preposition fix is now case-insensitive – `K Tobě` at the start of a sentence works. - New universal API surface: `fixCzechHtml`, `createFixCzechStream`, `<Typo>`, `useFixCzech`, `fixCzechDom`, `cz-typography/next/middleware`, `cz-typography/next/loader`. ## License [MIT](LICENSE.md)