@byline/i18n

中文文档
6.13.0发布于 2026/11/04MPL-2.0TypeScriptES Module近 30 天下载 2385

Byline CMS admin interface i18n — translation registry, ICU formatter, React provider, language switcher

原始使用文档
文档目录10 节
  1. Install
  2. Three entry points
  3. Minimal wiring
  4. Adding a second locale
  5. Plugin contributions
  6. Using useTranslation in components
  7. Server-side translation
  8. Locale resolution cascade
  9. What's not here (yet)
  10. License

@byline/i18n

Admin interface i18n primitives for Byline CMS — a translation registry, an ICU MessageFormat formatter, a React provider + useTranslation() hook, a self-contained <LanguageMenu>, and a pure locale-resolution cascade.

This package is the interface axis of i18n (what language the admin UI renders in). The content axis (what languages your documents publish in) is a separate concern handled by i18n.content on the existing BaseConfig. The two are deliberately independent.

For the full design — registration model, plugin extensibility surface, per-user persistence, future phases — see docs/08-internationalization/index.md at the repo root.

Install

pnpm add @byline/i18n

The package bundles every official Byline admin translation as a JSON file under src/admin/. Today that's English (en.json) and French (fr.json); adding a new locale is a single-file PR. The adminTranslations({ locales }) factory reads only the requested codes and assembles a bundle.

Three entry points

import { mergeTranslations, createFormatter, resolveAdminLocale } from '@byline/i18n'
import { I18nProvider, useTranslation, LanguageMenu } from '@byline/i18n/react'
import { adminTranslations, en } from '@byline/i18n/admin'
  • Root — types, formatter, merge helper, locale cascade. React-free; safe on the server.
  • /react — provider, hook, switcher. Single barrel by design (sidesteps the Vite optimizeDeps Context-identity trap that has bitten this codebase before).
  • /admin — the bundled byline-admin namespace and the adminTranslations(...) factory.

Minimal wiring

// apps/your-app/byline/admin.config.ts
import { defineAdminConfig } from '@byline/core'
import { adminTranslations } from '@byline/i18n/admin'

defineAdminConfig({
  i18n: {
    admin: {
      defaultLocale: 'en',
      locales: ['en'],
    },
    // … content locales as before
    translations: adminTranslations({ locales: ['en'] }),
  },
  // … the rest of your admin config
})

Adding a second locale

import { adminTranslations } from '@byline/i18n/admin'

i18n: {
  admin: { defaultLocale: 'en', locales: ['en', 'fr'] },
  translations: adminTranslations({ locales: ['en', 'fr'] }),
}

The set of bundled locales is exported as bundledLocales for hosts that want to derive their locale list from what's available:

import { adminTranslations, bundledLocales } from '@byline/i18n/admin'

i18n: {
  admin: { defaultLocale: 'en', locales: [...bundledLocales] },
  translations: adminTranslations({ locales: bundledLocales }),
}

adminTranslations({ locales: ['xx'] }) throws at config time when a requested code is not in bundledLocales. To contribute a new locale, drop a JSON file alongside src/admin/en.json and add it to the bundle map.

Plugin contributions

A plugin (richtext extension, custom field, AI tool, …) ships its own JSON files inside its own package and exposes a factory matching adminTranslations's shape — takes { locales }, returns a TranslationBundle for the plugin's own namespace. The host merges them in defineAdminConfig:

import { mergeTranslations } from '@byline/i18n'
import { adminTranslations } from '@byline/i18n/admin'
import { aiTranslations } from '@byline/ai/i18n'

i18n: {
  admin: { defaultLocale: 'en', locales: ['en', 'fr'] },
  translations: mergeTranslations(
    adminTranslations({ locales: ['en', 'fr'] }),
    aiTranslations({ locales: ['en', 'fr'] }),
  ),
}

A plugin that ships a locale the host hasn't enabled is harmless — the plugin's factory simply returns nothing for that code, the merge produces an empty entry for it, and the boot validator gates against i18n.admin.locales anyway.

mergeTranslations is associative + deterministic; later sources override earlier ones at the (locale, namespace, key) grain, and a MergeOptions.onCollision callback is available for surfacing conflicts during development.

Conventions:

  • byline-admin — the built-in admin shell namespace.
  • byline-<package> — every Byline-shipped package (byline-richtext-lexical, byline-ai, …).
  • <org>-<plugin> — third-party plugins (the package name with @ and / flattened).

Using useTranslation in components

import { useTranslation } from '@byline/i18n/react'

export function PublishButton() {
  const { t } = useTranslation('byline-admin')
  return <button>{t('common.actions.publish')}</button>
}

t(key, values) always returns a string. ICU MessageFormat syntax works in values — plurals, dates, numbers, selects:

t('list.unread', { count: 3 })             // "3 unread"
t('doc.publishedOn', { date: new Date() }) // "Published on May 28, 2026"

Missing keys fall through active locale → default locale → raw key. A one-shot console.warn per missing (locale, namespace, key) triple fires in dev so gaps are visible.

Server-side translation

createFormatter is pure and React-free, so loaders and server functions use it directly:

import { createFormatter } from '@byline/i18n'

const { t } = createFormatter({
  bundle,                  // the merged TranslationBundle
  activeLocale: 'fr',
  defaultLocale: 'en',
}).bind(null, 'byline-admin')

return { subject: t('email.invite.subject') }

In a TanStack Start host, resolveServerTranslator(namespace) (provided by @byline/host-tanstack-start) wraps this with the request's resolved locale.

Locale resolution cascade

resolveAdminLocale is the same pure cascade run on client and server, so SSR and hydration produce the same answer:

import { resolveAdminLocale } from '@byline/i18n'

const activeLocale = resolveAdminLocale({
  locales: ['en', 'fr'],
  defaultLocale: 'en',
  preferred: actor?.admin_user?.preferred_locale,
  cookie: getCookie('byline_admin_lng'),
  acceptLanguage: getRequestHeader('accept-language'),
})

Tier order: preferred → cookie → acceptLanguage → defaultLocale. Every signal is validated against the permitted locales set, so a stale cookie pointing at a removed locale falls through cleanly.

What's not here (yet)

  • Lazy locale loading. Locales currently bundle eagerly. With more than ~5 locales, switching to async loaders earns its complexity; see Phase 3 in docs/08-internationalization/index.md.
  • Authoring-time metadata (per-key descriptions, plural hints, deprecation markers). Phase 4.
  • RTL admin layout. Phase 5. Need a community-translated RTL locale first to surface the real list of CSS breakages.
  • Inline translation editing (CMS-as-translation-tool). Deliberately out of scope.

License

MPL-2.0.

版本与发布记录

@byline/i18n 共发布 107 个版本,首次发布于 2026/05/28;过去 12 个月发布了 107 个版本。

版本发布日期距今
6.13.0latest2026/11/044 天前
6.12.12026/11/026 天前
6.12.02026/10/318 天前
6.11.02026/10/2514 天前
6.10.12026/10/2217 天前
6.10.02026/10/2217 天前

关于 @byline/i18n 的常见问题

@byline/i18n 是什么?

Byline CMS admin interface i18n — translation registry, ICU formatter, React provider, language switcher

如何安装 @byline/i18n?

在项目目录运行 npm install @byline/i18n,或使用 pnpm add / yarn add / bun add。它还需要宿主项目提供 peer 依赖:react(^19.0.0)、react-dom(^19.0.0)。npm 7 及以上与 pnpm 8 及以上默认会自动安装必需的 peer 依赖,Yarn 需要手动安装。

@byline/i18n 支持 TypeScript 吗?

支持。包内自带 TypeScript 类型声明(package.json 声明了 types 入口),安装后即可获得类型提示,无需再安装 @types 包。

@byline/i18n 支持 ESM 还是 CommonJS?

以 ES Module 形式发布,推荐使用 import 引入。在 CommonJS 项目中,Node.js 20.19+ / 22.12+ 可以直接 require() 不含顶层 await 的 ES Module,更早的版本需要改用动态 import()。

@byline/i18n 需要什么版本的 Node.js?

package.json 的 engines 字段要求 Node.js >=22.22.2。版本不符时 npm 默认只给出警告,开启 engine-strict 后会拒绝安装。

@byline/i18n 可以商用吗?

@byline/i18n 使用 MPL-2.0 许可。弱 Copyleft:可以商用,也可以和闭源代码一起使用;但如果分发时修改了 MPL 授权的文件,这些文件的修改需以 MPL 开源。以上为概括,不构成法律意见,请以许可原文为准。

@byline/i18n 的下载量有多少?

根据 npm 官方下载统计,@byline/i18n 在 2026/10/09 – 2026/11/07 这 30 天内被下载 2,385 次,日均约 80 次。下载量会把 CI 构建、镜像同步和作为其他包依赖的安装一并计入,适合横向比较热度,不能直接等同于使用人数。

@byline/i18n 还在维护吗?

最新版本 6.13.0 发布于 2026/11/04(4 天前);至今共发布 107 个版本,其中过去 12 个月发布了 107 个;首次发布于 2026/05/28。发布频率只是参考信号:功能稳定的工具可能很少需要更新,建议结合源码仓库的 issue 和提交记录判断。