k8ordo
  • UI
  • Form
  • State
  • Router
  • Static
  • Server
  • i18n
Switch to dark mode
k8ordo

React libraries that use Baseline features without holding back.

Packages
  • @k8ordo/ui
  • @k8ordo/form
  • @k8ordo/state
  • @k8ordo/router
  • @k8ordo/static
  • @k8ordo/server
  • @k8ordo/i18n
UI
  • Get Started
  • Theming
  • i18n
  • Components
  • Hooks
  • Helpers
  • AI Chat
  • Generative UI
  • AI Agents
  • Storybook
Resources
  • GitHub
  • npm

© 2026 k8o — MIT License

Typeset in Noto Sans JP & M PLUS 2

@k8ordo/i18n

Owns the locale axis of an application. One locale set derives the URL segment, negotiation, the params schema, and the static paths; each message is a function that reads the locale where it is called, so the same line renders on the server and in the browser, and only the messages a page names reach its bundle.

npmGitHub

This site is the demo

Every word from the header to the footer comes from @k8ordo/i18n's message. The greeting below is a message that takes an argument.

Hello.

Locale negotiated from your browser languages
detecting

The argument of greeting(name) is typed by the function written for ja. An en written with different parameters does not compile.

Usage

Define the locale set, register it, write each message with message, and call it. That is all.

Features

  • Locales in one placedefineLocales(['ja', 'en']) is written once. The default, membership, negotiation, the URL segment, and the [locale] schema all come from it, so there is nowhere to copy the list to.
  • Owns the first URL segmentThe locale lives in the URL. localize / delocalize put the segment on and take it off, and paramsSchema makes /fr/… a real 404. The rest of the pathname belongs to @k8ordo/router.
  • Negotiates over a listnegotiate(navigator.languages) and negotiate(parseAcceptLanguage(header)) are the same function: each requested tag in order, exact match, then the same language, then the default.
  • A message is a functionmessage({ ja, en }) is one message. It does not type-check until every locale is there, and its arguments are typed by the function you wrote. No key list, no dictionary object.
  • Only what is called shipsMessages are ordinary exports, so a bundler keeps only the ones a module names. Text a Server Component rendered never reaches the client.
  • The same line on either sideNo provider, no hook. On the server the accepted [locale] is the locale of that render; in the browser the URL is. nav.home() renders the same on both.

Design guide

The guide ships inside the npm package. An AI coding assistant reads the exact installed version out of node_modules/@k8ordo/i18n/docs/.

Home
// i18n.ts — 一覧はここにしか書かない
export const locales = defineLocales(['ja', 'en']);
declare module '@k8ordo/i18n' {
  interface Register { locale: LocaleOf<typeof locales> }
}

// messages/nav.ts — 文言は 1 つずつ関数。全ロケールが揃わないと通らない
export const home = message({ ja: 'ホーム', en: 'Home' });
export const greeting = message({
  ja: (name: string) => `こんにちは、${name}さん`,
  en: (name) => `Hello, ${name}`, // 引数の型は ja から流れる
});

// routes/[locale]/layout.tsx — 受理したロケールがこの描画のロケールになる
export const paramsSchema = locales.paramsSchema; // /fr/… は 404

// Server Component でも Client Component でも、同じ 1 行
<h1>{nav.home()}</h1>
<p>{nav.greeting(name)}</p>