国际化
默认行为:组件自己不写文案,界面文字一律取语言包,没配语言包时取英文语言包 enUS;其余全部跟随 locale,locale 未提供时跟随宿主浏览器。
具体为四条:
- 文案(关闭按钮的读屏名称、分页的翻页说明、上传区的提示、色阶两端的
Less/More、表单校验报错、对话框服务的按钮、快捷键的键名与键帽字等)一律取自语言包。库附带十种常用语言的内建语言包,整份交给全局配置即可;个别说法经translations覆盖。 - 日期时间类组件的月份名、星期名、段位顺序、周首日与默认格式串全部由
locale决定,不写死。 locale的解析链是实例 props → 全局配置 → 宿主的navigator.language→en-US,calendar/date-picker/date-field/heatmap/time都使用它。因此中文浏览器中不做任何配置也显示中文日期;服务端渲染期或显式 Scope 没有活动 Window 时读不到宿主语言,使用en-US,不借用其他页面的 Window。- 例外一处:
time-picker/time-field的小时制不从宿主推断。hourCycle与locale都未提供时始终为 24 小时制,需要 12 小时制时提供其中之一。
服务端渲染的应用应在服务端与客户端显式注入同一个 locale。如果服务端依赖无 DOM 时的 en-US、客户端又依赖 navigator.language,两端可能生成不同的月份、星期与段位顺序,形成水合差异。
周首日跟随 locale
一周从周日还是周一开始由 locale 决定:en-US 周日起,zh-CN 与多数欧洲语言周一起。 未配置 locale 时它跟随读者的浏览器变化,同一份代码在两台机器上排出的月历首列不同。 要固定为一种排法,显式传入 locale(实例上传入,或全局配置一次),不依赖默认值。
各适配器都有一处全局出口,语义一致:Vue 侧是 provideXhConfig(走 provide/inject,可按子树覆盖),React 侧是 XhConfigProvider(同样可按子树覆盖),自定义元素侧是 setXhConfig(模块级,整个文档一份)。
内建语言包
每个适配器都有一个 locale 子入口,导出十份语言包:
| 导出名 | 语言 | locale |
|---|---|---|
zhCN | 简体中文 | zh-CN |
zhTW | 繁體中文(台灣) | zh-TW |
enUS | English | en-US |
jaJP | 日本語 | ja-JP |
koKR | 한국어 | ko-KR |
frFR | Français | fr-FR |
deDE | Deutsch | de-DE |
esES | Español | es-ES |
ptBR | Português (Brasil) | pt-BR |
ruRU | Русский | ru-RU |
一份语言包就是一份全局配置 { locale, translations },整份交给全局出口:
// Vue:应用根组件的 setup 中
import { provideXhConfig } from "@xihan-ui/vue";
import { zhCN } from "@xihan-ui/vue/locale";
provideXhConfig(zhCN);// React:包在应用根上
import { XhConfigProvider } from "@xihan-ui/react";
import { zhCN } from "@xihan-ui/react/locale";
<XhConfigProvider config={zhCN}>
<App />
</XhConfigProvider>;// 自定义元素:注册元素之后调用一次
import { setXhConfig } from "@xihan-ui/web-components";
import { zhCN } from "@xihan-ui/web-components/locale";
setXhConfig(zhCN);- 语言包同时带着
locale,日期时间类组件的月份名、星期名与周首日随之切换。只想换文案、日期仍跟随宿主浏览器时,只交translations:provideXhConfig({ translations: zhCN.translations })。 - 还要配其他项时先展开再加:
provideXhConfig({ ...zhCN, size: "sm" })。个别文案要换成自己的说法,在实例上写对应的translations键即可,它压在语言包之上;要在全局改,用各适配器导出的mergeXhConfig(zhCN, { translations: { dialog: { close: "关掉" } } })合并出一份新配置再交出去。 enUS与其余九份同形,是完整的英文语言包。组件没配语言包时用的就是它;运行时从其他语言切回英文时也交它。- 语言包放在子入口而不是主入口:每份都是一整张文案表,只引入用到的那几份,其余由打包器摇掉。
语言包覆盖组件的每一条文案。以下几类刻意不收,因为它们没有缺省,或缺省取自实例自己的内容,语言包给了就不只是换语言,而是改了行为:
- 作者自写的可见文字与标签:
approval的approve/deny/notePlaceholder、clipboard.copy、download-trigger.trigger、mention.input、menu.content、prompt-input.input、question-flow的notePlaceholder/skip/continue/send、steps.list、tool-call.ranFor、tour的next/finish。这些需要时在实例上提供。 - 条目自己的名字:拖拽播报里的
item(sortable/table/tabs/tree)缺省取条目的文字。 - 由
Intl按locale给出的说法:timestamp.justNow。
表单校验报错的模板在 translations.form 里({name} 换成字段名,{min} / {max} 换成规则界限):规则自己的 message 最优先,表单的 validateMessages 逐条压在语言包之上。
同理,实例自己的内容优先于任何 translations:直角坐标图数据表的列名先取轴标题,轴没有标题时才用 keyLabel / valueLabel;热力图发散色阶的对照条两端恒写数值,legendLow / legendHigh 只换顺序色阶两端的词;评分星里放了作者自己的内容(文字或带名字的节点),或作者直接在星上写了名字时以作者的为名,rating.item(语言包里的或实例给的)只在都没有时用。
要补一种语言,以任一份内建语言包为样板写一份同形的 XhLocale 对象,交给全局出口的方式不变。
全局注入一次
import { provideXhConfig } from "@xihan-ui/vue";
// 应用根组件的 setup 中
provideXhConfig({
locale: "zh-CN",
translations: {
breadcrumb: { root: "面包屑" },
pagination: { root: "分页", prevTrigger: "上一页", nextTrigger: "下一页" },
dialog: { close: "关闭" },
notification: { close: "关闭" },
},
});取值优先级从高到低:实例 props → 全局注入 → 英文语言包 enUS(locale 则向宿主浏览器读取一次)。translations 按键合并:全局提供整包,个别实例只覆盖需要修改的键。
运行时切语言
传入 ref 或 getter,切换时使用这些文案的组件随之重渲染:
import { provideXhConfig } from "@xihan-ui/vue";
import { enUS, zhCN } from "@xihan-ui/vue/locale";
import { computed, ref } from "vue";
const lang = ref<"zh" | "en">("zh");
provideXhConfig(computed(() => (lang.value === "zh" ? zhCN : enUS)));语言包是一个普通对象(XhLocale,可直接当 XhConfig 用),按组件名收纳各自的 Translations;每个组件页的 Props 表列有它的文案键。
接受 locale 的组件
locale 一律接受 BCP 47 语言标记(如 zh-CN、en-US),供以下组件使用:
| 组件 | locale 决定的内容 | 未提供时 |
|---|---|---|
calendar / date-picker | 月份名、星期名、周首日、标题中年月的顺序 | 宿主语言,再退 en-US |
date-field | 年月日三段的顺序、上午 / 下午的写法 | 宿主语言,再退 en-US |
heatmap | 月份名、星期名 | 宿主语言,再退 en-US |
time | 用词(zh 开头使用中文,其余英文)与默认格式串 | 宿主语言,再退 en-US |
time-picker / time-field | 上午 / 下午的写法、推断小时制 | 不读取宿主:小时制固定 24,上午 / 下午写 AM/PM |
实例上显式提供 locale 的不受全局影响。
作用域与边界
注入走 Vue 的 provide/inject:在应用根 provide 即全应用默认,也可以只在某个子树 provide 以局部切换语言。
createNotificationService/createDialogService自带宿主应用,无法接入组件树中的注入。它们的按钮与报错文字先取创建时的选项(okText/cancelText/actionErrorText),没给的取服务config里translations.dialog的ok/cancel/actionError,再退英文语言包。需要整个服务与应用同语言时通过config传入:tsimport { zhCN } from "@xihan-ui/vue/locale"; // 确定 / 取消与失败提示都随 zhCN 换成中文;个别服务要别的说法时再传 okText 等选项 const dialog = createDialogService({ config: zhCN });config是一份普通的XhConfig,内建语言包可直接传入,服务会在自己的子树中provideXhConfig一次;未提供时用英文语言包。自定义元素侧的服务没有config,按宿主所在处的setXhConfig取。不注入时组件按原路径运行,没有额外开销。
自定义元素无法接入 Vue 的注入,使用它自己的全局出口,见下文。
文档站的示例为什么逐实例传入
每条示例都是孤立片段,没有应用根可以注入,因此一律写成 :translations="{ … }"。 真实应用按上文全局配置一次即可,实例上只保留需要单独覆盖的键。
自定义元素侧
自定义元素无法获取 Vue 的 provide/inject,文案是对象,只能通过 property 而不能通过 attribute 设置,因此逐实例设置是原有的唯一路径。setXhConfig 提供一处全局出口:
import { setXhConfig } from "@xihan-ui/web-components";
import { defineXhElements } from "@xihan-ui/web-components/define";
defineXhElements();
setXhConfig({
locale: "zh-CN",
translations: {
dialog: { close: "关闭" },
pagination: { root: "分页", prevTrigger: "上一页", nextTrigger: "下一页" },
},
});取值优先级与 Vue 侧一致:元素上的 property → setXhConfig 的全局值 → 英文语言包 enUS(locale 则向宿主浏览器读取一次),translations 逐键合并。切换语言时再次调用,已挂载的元素随之重渲染:
import { enUS } from "@xihan-ui/web-components/locale";
setXhConfig(enUS);两处差别:
setXhConfig是整份替换,不做深合并;修改一处需要传入整份配置。Vue 侧语义相同。- 它是模块级的,整个文档一份。只在某棵子树切换语言时,用
<xh-config>包住那一部分:locale写在标签上,translations用 property 设置,与全局配置逐键合并,用法见全局配置的「自定义元素侧」。
