@ubean/i18n
| Install | |
|---|---|
npm install @ubean/i18n |
|
| Latest Version: | 0.7.8 |
| Last Updated: | Oct 24, 2026 |
| Links: | npm |
@ubean/i18n
English | 中文
i18n engine for ubean — compact locale routing compiled once and shared by Hono and vue-router, plus AsyncLocalStorage-scoped t() / d() / n() for handlers.
import { compileLocalePaths } from '@ubean/i18n';
const compiled = compileLocalePaths('/about', {
defaultLocale: 'en',
locales: ['en', 'zh'],
strategy: 'prefix_except_default'
});
// compiled.vuePath → '/:locale(zh)?/about'
// compiled.hono → [{ path: '/about', locale: 'en', isDefault: true }, { path: '/zh/about', locale: 'zh', isDefault: false }]
Table of Contents
- Package Positioning
- Install
- Quick Start
- Locale Routing Strategies
- Path Compilation & Helpers
- Request Middleware
- Messages in AsyncLocalStorage
- Locale Head (hreflang / canonical)
- Browser-Safe Subpath
- Type Safety
- Architecture Notes
Package Positioning
@ubean/i18n is the single owner of locale routing in ubean, and the home of the request-scoped translation API:
| Layer | Responsibility |
|---|---|
@ubean/i18n (this package) |
The four compact locale-routing strategies, compileLocalePaths, URL helpers, locale detection (cookie / Accept-Language), createI18nMiddleware, ALS t() / d() / n(), locale catalogs, and the hreflang/canonical head builder |
@ubean/client (framework runtime) |
Vue-side wiring over the vue-i18n 11 composer: setLocale, useLocalePath, useSwitchLocalePath, useLocaleHead, getI18nRuntimeConfig |
@ubean/app |
Mounts createI18nMiddleware inside createUbeanApp — you do not wire the middleware yourself |
@ubean/config |
Resolves the i18n field of ubean.config.ts into a LocaleRoutingConfig (resolveI18nConfig) |
Included:
compileLocalePaths(pagePath, cfg)— the shared vue-router + Hono path compiler (CompiledLocalePath)getVueLocaleParam/toVueRouterLocalePath/extractLocaleFromPath/localizePath/switchLocalePathcreateI18nMiddleware(options)+getRequestLocale(c)/getPathWithoutLocale(c)- ALS translation scope:
t/d/n,runWithI18n,getI18nScope,createRequestContext - Locale catalog management:
setLocaleMessages/getLocaleMessages/mergeLocaleMessages,setLocaleMeta/getLocaleMeta,setFallbackLocale/getFallbackLocale,registerLocaleLoader/ensureLocaleMessages - Locale metadata readers:
listLocaleCodes,getRegisteredLocalesMeta,getLocaleDir,getLocaleName - Detection helpers:
detectLocaleFromAcceptLanguage,parseLocaleCookie,serializeLocaleCookie - SEO head builder:
buildLocaleHead(htmlAttrs/link/meta) - Browser-safe subset at
@ubean/i18n/browser
NOT included (owned next door):
- The vue-i18n composer itself —
useI18nis imported fromvue-i18ndirectly (Composition API,legacy: false) - App construction and middleware mounting (
createUbeanAppin@ubean/app) - SEO meta rendering /
<Head>(@ubean/seo); this package only produces the locale tag descriptors - Locale file discovery and catalog loading on disk (
@ubean/scan+ the build pipeline, surfaced asloadMessages)
Runtime dependency whitelist (main entry): @intlify/core, @ubean/shared, hono. No vue and no vue-i18n static import — the Vue composer is wired by @ubean/client.
This package declares no optional peers: nothing degrades at runtime when an optional package is absent.
Install
pnpm add @ubean/i18n
# Vue side (installed by the application, not by this package):
pnpm add vue-i18n # vue-i18n 11, Composition API (legacy: false)
Quick Start
1. Configure the i18n field in ubean.config.ts — the middleware is mounted automatically:
// ubean.config.ts
import { defineConfig } from 'ubean';
export default defineConfig({
i18n: {
defaultLocale: 'en',
locales: [
{ code: 'en', language: 'en', name: 'English' },
{ code: 'zh', language: 'zh-CN', name: '简体中文' }
],
strategy: 'prefix_except_default',
detectBrowserLanguage: { cookieName: 'ubean_locale', redirectOn: 'root' }
}
});
2. Translate in handlers — t() reads the request AsyncLocalStorage scope:
import { t, getRequestLocale } from 'ubean/i18n';
// src/routes/hello.ts
import { defineHandler } from 'ubean/server';
export const GET = defineHandler(c => {
return c.json({ locale: getRequestLocale(c), hello: t('hello') });
});
3. Compile paths once and let both routers consume the same result:
import { compileLocalePaths } from '@ubean/i18n';
const cfg = { defaultLocale: 'en', locales: ['en', 'zh'], strategy: 'prefix_except_default' } as const;
compileLocalePaths('/about', cfg);
// → { vuePath: '/:locale(zh)?/about', hono: [{ path: '/about', … }, { path: '/zh/about', … }] }
Locale Routing Strategies
Four compact strategies; prefix_except_default is the framework default. With defaultLocale: 'en', locales en, zh, page /about:
| Strategy | vue-router | Hono |
|---|---|---|
prefix_except_default |
/:locale(zh)?/about |
/about, /zh/about (no /en/about) |
prefix |
/:locale(en|zh)/about |
/en/about, /zh/about |
prefix_and_default |
/:locale(en|zh)?/about |
/about, /en/about, /zh/about |
no_prefix |
/about |
/about |
import { getVueLocaleParam } from '@ubean/i18n';
const cfg = { defaultLocale: 'en', locales: ['en', 'zh'], strategy: 'prefix_except_default' } as const;
getVueLocaleParam(cfg); // ':locale(zh)?'
Locale codes are reserved URL segments — pages/[id].vue will not match /zh when zh is a locale code.
Path Compilation & Helpers
import {
compileLocalePaths,
extractLocaleFromPath,
localizePath,
switchLocalePath,
toVueRouterLocalePath
} from '@ubean/i18n';
const cfg = { defaultLocale: 'en', locales: ['en', 'zh'], strategy: 'prefix_except_default' } as const;
const { vuePath, hono } = compileLocalePaths('/about', cfg);
extractLocaleFromPath('/zh/about', cfg.locales);
// → { locale: 'zh', pathWithoutLocale: '/about' }
localizePath('/about', 'zh', cfg); // '/zh/about'
switchLocalePath('zh', '/about', cfg); // '/zh/about'
toVueRouterLocalePath('/about', getVueLocaleParam(cfg)); // '/:locale(zh)?/about'
compileLocalePaths(pagePath, cfg)returns{ vuePath, hono: HonoLocalePath[] };honocarries{ path, locale, isDefault }per locale.switchLocalePath(locale, currentPath, cfg)islocalizePath(currentPath, locale, cfg)— pass the current route path to get the same route in another locale.- These helpers are pure functions: no ALS, no request state, safe in the browser bundle.
Request Middleware
createI18nMiddleware(options) is mounted by createUbeanApp; call it directly only in a custom app.
Detection order for prefixed strategies: URL prefix → cookie → Accept-Language → defaultLocale. The cookie is written back on every resolved request.
import { createI18nMiddleware } from '@ubean/i18n';
const middleware = createI18nMiddleware({
defaultLocale: 'en',
locales: ['en', 'zh'],
strategy: 'prefix_except_default',
detectBrowserLanguage: { cookieName: 'ubean_locale', redirectOn: 'root', alwaysRedirect: false }
});
Behavior details:
- Defaults:
cookieName: 'ubean_locale',redirectOn: 'root',alwaysRedirect: false. /api/,/_,/__paths still enter the ALS scope (cookie /Accept-Language) but the URL is never rewritten.prefix_except_default302s/en/...to the unprefixed path;prefix302s unprefixed content to the preferred prefix regardless ofredirectOn.redirectOn: 'root'302s only/;redirectOn: 'all'also redirects deeper paths.- The middleware sets
c.set('locale'),c.set('pathWithoutLocale')and aContent-Languageresponse header, and appendsSet-Cookie.
import { getRequestLocale, getPathWithoutLocale } from 'ubean/i18n';
import { defineHandler } from 'ubean/server';
export const GET = defineHandler(c => {
return c.json({ locale: getRequestLocale(c), path: getPathWithoutLocale(c) });
});
The middleware stores the resolved locale and the unprefixed path on the context; getPathWithoutLocale(c) reads the latter.
Messages in AsyncLocalStorage
Handler-side translation lives in an ALS scope. t() throws outside ALS — there is no process-global locale.
import { t, d, n, getI18nScope } from 'ubean/i18n';
import { defineHandler } from 'ubean/server';
export const GET = defineHandler(c => {
const scope = getI18nScope();
return c.json({
hello: t('hello'),
greeting: t('greeting', { name: 'World' }),
when: d(new Date()),
count: n(1200)
});
});
Catalog and metadata APIs:
import {
ensureLocaleMessages,
getFallbackLocale,
getLocaleDir,
getLocaleMeta,
registerLocaleLoader,
setFallbackLocale,
setLocaleMessages,
setLocaleMeta
} from '@ubean/i18n';
setFallbackLocale('en');
setLocaleMessages('en', { hello: 'Hello' });
registerLocaleLoader(async locale => {
const messages = await import(`../locales/${locale}.json`);
setLocaleMessages(locale, messages.default);
});
await ensureLocaleMessages('zh', getFallbackLocale());
setLocaleMeta('zh', { code: 'zh', language: 'zh-CN', name: '简体中文', dir: 'ltr' });
Also exported: getLocaleMessages / mergeLocaleMessages, listLocaleCodes, getRegisteredLocalesMeta, getLocaleName. createRequestContext(locale, fallback) builds the scope object consumed by runWithI18n(scope, fn) for tests and internal callers; getAlsLocale() (the index alias of getRequestLocale()) reads the current locale without a Hono context.
Locale Head (hreflang / canonical)
buildLocaleHead turns the resolved routing state into head tags — no Vue, no unhead dependency:
import { buildLocaleHead } from '@ubean/i18n';
const tags = buildLocaleHead({
path: '/about',
locale: 'zh',
locales: [
{ code: 'en', language: 'en', name: 'English', isDefault: true },
{ code: 'zh', language: 'zh-CN', name: '简体中文', dir: 'ltr' }
],
routing: { defaultLocale: 'en', locales: ['en', 'zh'], strategy: 'prefix_and_default' },
baseUrl: 'https://example.com'
});
tags.htmlAttrs; // { lang, dir }
tags.link; // rel=alternate hreflang entries (incl. x-default) + canonical
tags.meta; // og:locale / og:locale:alternate
hreflangalternates include the language-group catch-all entry andx-default.- With
prefix_and_default, the default locale's canonical points at the unprefixed URL. - Set
baseUrlto emit absolute hrefs.
Browser-Safe Subpath
@ubean/i18n/browser exports the request-free subset, so client code never pulls in Hono or the ALS runtime:
import {
buildLocaleHead,
compileLocalePaths,
detectLocaleFromAcceptLanguage,
extractLocaleFromPath,
getVueLocaleParam,
localizePath,
parseLocaleCookie,
serializeLocaleCookie,
switchLocalePath,
toVueRouterLocalePath
} from '@ubean/i18n/browser';
Also exported there: the routing types (I18nRoutingStrategy, LocaleRoutingConfig, CompiledLocalePath, HonoLocalePath, I18nLocaleMeta) and the head types (LocaleHeadInput, LocaleHeadTags).
Type Safety
- Routing types:
I18nRoutingStrategy('prefix' | 'prefix_except_default' | 'prefix_and_default' | 'no_prefix'),LocaleRoutingConfig,CompiledLocalePath,HonoLocalePath,I18nMiddlewareOptions,I18nDetectOptions,I18nLocaleMeta. - Runtime types:
LocaleMessages,I18nRequestScope,I18nCoreContext. - Head types:
LocaleHeadInput,LocaleHeadTags. LocaleRoutingConfigis structurally compatible with the resolvedi18nconfig, so aas constconfig object can be passed to the pure helpers without casts.- Message-key typing is generated by the build pipeline into
.ubean/i18n.d.ts, which augments vue-i18n'sDefineLocaleMessagefrom the default locale catalog. Include.ubean/*.d.tsintsconfig.json.
Architecture Notes
- One compiler, two routers.
compileLocalePathsis the only place that knows the prefix rules; Hono route registration and vue-router route generation both consume its output. Changing a strategy changes one function, not two route tables. - No process-global locale.
t()/d()/n()read ALS strictly; outside a request scope they throw instead of silently falling back to a default locale. Cloudflare Workers neednodejs_compatforAsyncLocalStorage. - Server engine vs Vue engine. Handlers use
@intlify/core; the Vue side uses vue-i18n 11. The two share catalogs, not instances — this package never importsvue-i18n. Middleware is framework-mounted bycreateUbeanApp, sosrc/middleware/02.i18n.tsis not part of the convention. - Removed, with no compatibility layer:
defineLocale,addLocale,mergeLocale,clearLocales,formatDate,formatNumber,formatCurrency,formatRelativeTime,formatList, and a process-globaluseI18n()from this package. Use vue-i18n'sd/nand the ALSt()instead. - Decision record:
docs/adr/0009-i18n-engine-and-compact-locale-routing.md. A multilingual SSG walkthrough lives inexamples/ssg-catchall.
Related Packages
Lean Vue client kernel & page-routing owner for ubean — plugin-first page routin...
Version History
| Version | Released | License |
|---|---|---|
| 0.7.8 | ||
| 0.7.7 | ||
| 0.7.6 | ||
| 0.7.5 | ||
| 0.7.4 | ||
| 0.7.3 | ||
| 0.7.2 | ||
| 0.7.1 | ||
| 0.7.0 | ||
| 0.6.9 | ||
| 0.6.8 | ||
| 0.6.7 | ||
| 0.6.6 | ||
| 0.6.5 | ||
| 0.6.4 | ||
| 0.6.3 | ||
| 0.6.2 | ||
| 0.6.1 | ||
| 0.6.0 | ||
| 0.5.2 | ||
| 0.5.1 | ||
| 0.5.0 | ||
| 0.4.10 | ||
| 0.4.9 | ||
| 0.4.8 | ||
| 0.4.7 | ||
| 0.4.6 | ||
| 0.4.5 | ||
| 0.4.4 | ||
| 0.4.3 |
Showing the latest 30 of 58. See every release on npm
Pre-releases (8)
| Version | Released | License |
|---|---|---|
| 0.6.1-beta.3 | ||
| 0.6.1-beta.2 | ||
| 0.6.0-beta.5 | ||
| 0.6.0-beta.4 | ||
| 0.6.0-beta.3 | ||
| 0.6.0-beta.2 | ||
| 0.6.0-beta.1 | ||
| 0.1.0-beta.1 |