多语言(i18n)
TokUI 的可见文案分两层,多语言职责必须切开:
| 层 | 内容 | 归谁管 | 怎么切 |
|---|---|---|---|
| L1 组件 chrome | aria-label、placeholder、空态、默认按钮字、分页总数、日期星期等"骨架文案" | TokUI | setLocale() |
| L2 业务文案 | DSL 里的 tt:/l:/tx:/opt: 等属性值(卡片标题、表单标签、选项文本) | 你的应用 | 后端按 locale 发不同 DSL |
关键:TokUI 不翻译 DSL 文本。
[btn tx:提交]的「提交」原样渲染。框架只负责 L1 骨架文案的多语言——内置zh-CN+en-US,其余语种可注入。
切换语言
1. 构造时指定(推荐)
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. 运行时切换
import { setLocale, getLocale } from '@jboltai/tokui';
setLocale('en-US'); // 接受别名:'en' / 'en-GB' → 'en-US','zh' / 'zh-TW' → 'zh-CN'
getLocale(); // → 'en-US'3. 自动探测(默认行为)
不传 locale 时,按 document.documentElement.lang → navigator.language → zh-CN 顺序探测。给 <html lang="en"> 即可让全站 chrome 走英文。
已渲染的 DOM 不会自动刷新——
setLocale只影响后续渲染。要热切就调ui.rerender()(见下节),库会就地重画已渲染内容。
在应用中接入(就地切换)
切换语言后让已渲染的 DOM 即时跟随,用实例方法 rerender()。TokUI 实例自动缓存最近一次 render() / feed() 的 DSL,rerender() 清容器后按当前 locale/theme 重新一次性渲染——应用无需自己缓存 DSL。
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]');
// 切语言 + 就地刷新(无网络、不清周围 DOM)
setLocale('en-US');
ui.rerender(); // → 分页/共42条 变 Pagination/42 items流式渲染同样适用——实例累积 feed 的 chunk,rerender() 重放完整内容:
ui.startStream();
controller.on('chunk', c => ui.feed(c)); // _lastDsl 自动累积
controller.on('end', () => ui.endStream());
// 流结束后切语言
setLocale('en-US');
ui.rerender();配合 localStorage 持久化(首次访问恢复上次语言):
const LANG_KEY = 'app-lang';
const ui = new TokUI({
container: '#app',
locale: localStorage.getItem(LANG_KEY) || 'zh-CN', // 启动即用上次语言
});
function switchLang(locale) {
setLocale(locale);
localStorage.setItem(LANG_KEY, locale);
ui.rerender();
}多容器场景(如聊天,每条消息一个实例):rerender() 是实例级——每个容器对应一个 TokUI 实例,切换时遍历各实例调用:
const instances = []; // 每条消息 push 一个 ui
function switchLang(locale) {
setLocale(locale);
instances.forEach(ui => ui.rerender());
}
rerender()无缓存内容(从未渲染过)或无容器时返回false。流式进行中调用会按已到达的 DSL 重画,一般待endStream()后调用。业务文案(L2,DSL 里的
tt:/l:等)rerender()不会翻译——它们是 DSL 字面量。要切业务文案,后端按 locale 发不同 DSL,或应用层在 feed 前翻译(见下节)。
注册新语种
内置仅 zh-CN + en-US。其余语种按需注入,可只传缺失的 key(未覆盖的回退 zh-CN):
import { registerLocale, setLocale } from '@jboltai/tokui';
registerLocale('ja-JP', {
'common.close': '閉じる',
'common.ok': 'OK',
'pagination.aria': 'ページネーション',
'pagination.totalCount': '全{count}件',
'chart.empty': 'データなし',
// ... 未列出的 key 自动回退 zh-CN
});
setLocale('ja-JP');
registerLocale增量合并:多次调用对同一 locale 累加,不覆盖未传的 key。
实时演示
下面这份 DSL 的卡片标题与选项是业务文案(固定中文),但分页总数、aria-label、空态、select placeholder 是组件 chrome——用右上角的语言开关(setLocale)切换,chrome 文案即时跟随:
内置文案目录(L1)
约 80 个 key,按 组件.位置 语义命名。完整清单见源码 src/core/i18n.js 的 STRINGS。常用分组:
| 分组 | 示例 key | 中文 | 英文 |
|---|---|---|---|
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 |
插值用 {name} 占位符,如 t('pagination.totalCount', { count: 42 }) → 共42条 / 42 items。
业务文案(L2)怎么做多语言
业务文案在 DSL 里,由后端生成。三种常见范式:
1. 后端按 locale 分发(推荐)
// 后端根据请求的 Accept-Language 或用户设置,发对应语言的 DSL
const dsl = locale === 'en'
? '[card tt:"Order Detail"][p Total: ¥128][/card]'
: '[card tt:"订单详情"][p 合计: ¥128][/card]';
ui.render(dsl);2. 前端词表预翻译
维护一份业务词表,feed 前替换 DSL 中的 key:
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 中立的 key + 前端翻译(复杂场景,需约定 key 规范)
TokUI 不强加任何一种——DSL 是数据,业务翻译策略由你的架构决定。
性能
t() 是单次对象属性查找 + 可选 {name} 替换;setLocale 仅替换内部字典引用(O(1)),单态查表可被引擎内联。相对硬编码字面量,渲染期开销不可测量。
下一步
- 主题系统 —— 同样是"构造时指定 / 运行时切换"的设计
- 快速开始 —— 引入与渲染
- 源码:
core/i18n.js