Internationalization (i18n)
TokUI splits visible text into two layers — keeping i18n responsibilities clean:
| Layer | Content | Owner | How to switch |
|---|---|---|---|
| L1 Component chrome | aria-labels, placeholders, empty states, default button text, pagination totals, date weekdays — the "skeleton" text | TokUI | setLocale() |
| L2 Business text | DSL attribute values like tt: / l: / tx: / opt: (card titles, form labels, option text) | Your app | Backend ships a different DSL per locale |
Key point: TokUI does not translate DSL text.
[btn tx:Submit]rendersSubmitverbatim. The framework only localizes the L1 skeleton —zh-CN+en-USbuilt in, more injectable.
Switching language
1. At construction (recommended)
import { TokUI } from '@jboltai/tokui';
import '@jboltai/tokui/css';
const ui = new TokUI({
container: '#app',
locale: 'en-US', // 'zh-CN' | 'en-US' | 'en' | 'zh' | ...
});2. At runtime
import { setLocale, getLocale } from '@jboltai/tokui';
setLocale('en-US'); // aliases accepted: 'en' / 'en-GB' → 'en-US', 'zh' / 'zh-TW' → 'zh-CN'
getLocale(); // → 'en-US'3. Auto-detection (default)
Without locale, TokUI probes document.documentElement.lang → navigator.language → zh-CN. Setting <html lang="en"> is enough to flip all chrome to English.
Already-rendered DOM does not auto-refresh —
setLocaleonly affects subsequent renders. For a live switch, callui.rerender()(next section) — the library re-renders in place.
Wiring into an app (live switch)
To make already-rendered DOM follow a language switch, use the instance method rerender(). A TokUI instance auto-caches the DSL of its last render() / feed(), and rerender() clears the container and re-renders one-shot with the current locale/theme — no need for the app to cache DSL itself.
import { TokUI, setLocale } from '@jboltai/tokui';
import '@jboltai/tokui/css';
const ui = new TokUI({ container: '#app', locale: 'zh-CN' });
ui.render('[pagination total:5 count:42 show-total clk:noop]');
// Switch language + refresh in place (no network, no surrounding DOM touched)
setLocale('en-US');
ui.rerender(); // → 分页/共42条 becomes Pagination/42 itemsStreaming works too — the instance accumulates fed chunks and rerender() replays the full content:
ui.startStream();
controller.on('chunk', c => ui.feed(c)); // _lastDsl accumulates
controller.on('end', () => ui.endStream());
// After the stream ends, switch language
setLocale('en-US');
ui.rerender();Persist across reloads with localStorage (restore on first visit):
const LANG_KEY = 'app-lang';
const ui = new TokUI({
container: '#app',
locale: localStorage.getItem(LANG_KEY) || 'zh-CN', // apply last choice at startup
});
function switchLang(locale) {
setLocale(locale);
localStorage.setItem(LANG_KEY, locale);
ui.rerender();
}Multiple containers (e.g. chat, one instance per message): rerender() is per-instance — keep a reference to each and iterate:
const instances = []; // push one ui per message
function switchLang(locale) {
setLocale(locale);
instances.forEach(ui => ui.rerender());
}
rerender()returnsfalsewhen there is no cached content (never rendered) or no container. Calling it mid-stream re-renders from the DSL arrived so far — usually you call it afterendStream().Business text (L2, DSL values like
tt:/l:) is not translated byrerender()— those are DSL literals. To switch business text, have the backend emit a different DSL per locale, or translate before feeding (next section).
Registering a new locale
Only zh-CN + en-US ship built-in. Inject others on demand; you may pass only the keys you need (uncovered keys fall back to zh-CN):
import { registerLocale, setLocale } from '@jboltai/tokui';
registerLocale('ja-JP', {
'common.close': '閉じる',
'common.ok': 'OK',
'pagination.aria': 'ページネーション',
'pagination.totalCount': '全{count}件',
'chart.empty': 'データなし',
// ... keys not listed fall back to zh-CN
});
setLocale('ja-JP');
registerLocalemerges incrementally: repeated calls for the same locale accumulate and never overwrite keys you didn't pass.
Built-in catalog (L1)
About 80 keys, named by component.position semantics. Full list in src/core/i18n.js (STRINGS). Common groups:
| Group | Example key | 中文 | English |
|---|---|---|---|
common.* | common.close / common.ok / common.loading | 关闭 / 确定 / 加载中 | Close / OK / Loading |
pagination.* | pagination.totalCount | 共{count}条 | {count} items |
lightbox.* | lightbox.zoomIn / lightbox.rotateLeft | 放大 / 左旋90° | Zoom in / Rotate 90° left |
chart.* | chart.empty / chart.seriesDefault | 暂无数据 / 系列 | No data / Series |
datepicker.* | datepicker.title / datepicker.weekday.1 | {y}年{m}月 / 一 | {m}/{y} / Mon |
status.* | status.running / status.done | 运行中 / 完成 | Running / Done |
command.* | command.placeholder / command.noResult | 输入关键词搜索... / 没有找到匹配结果 | Type to search... / No results found |
bubble.* | bubble.you / bubble.ai / bubble.system / bubble.assistant | 你 / AI / 系统 / 助手 | You / AI / System / Assistant |
Interpolation uses {name} placeholders, e.g. t('pagination.totalCount', { count: 42 }) → 共42条 / 42 items.
Localizing business text (L2)
Business text lives in the DSL and is generated by your backend. Three common patterns:
1. Backend dispatches per locale (recommended)
// Backend picks DSL by Accept-Language or user setting
const dsl = locale === 'en'
? '[card tt:"Order Detail"][p Total: ¥128][/card]'
: '[card tt:"订单详情"][p 合计: ¥128][/card]';
ui.render(dsl);2. Frontend pre-translation
Keep a business dictionary and replace keys before feeding:
const BIZ = {
'en': { '订单详情': 'Order Detail', '合计': 'Total' },
'ja': { '订单详情': '注文詳細', '合计': '合計' },
};
function translate(dsl, locale) {
const dict = BIZ[locale] || {};
return Object.keys(dict).reduce((s, k) => s.split(k).join(dict[k]), dsl);
}
ui.feed(translate(chunk, getLocale()));3. Locale-neutral keys from backend + frontend translation (advanced; needs a key convention)
TokUI imposes none of these — the DSL is data, and your architecture decides the translation strategy.
Performance
t() is a single object property lookup plus an optional {name} replace; setLocale only swaps the internal dict reference (O(1)), and the monomorphic lookup is engine-inlinable. Versus hardcoded literals, the render-time cost is unmeasurable.
Next
- Theming — same "set at construction / switch at runtime" design
- Quick Start — import and render
- Source:
core/i18n.js