来源：https://ui.docs.xihanfun.com/guide/i18n

# 国际化

默认行为：组件内建的文本是英文，其余全部跟随 `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`，两端可能生成不同的月份、星期与段位顺序，形成水合差异。

::: warning 周首日跟随 locale
一周从周日还是周一开始由 `locale` 决定：`en-US` 周日起，`zh-CN` 与多数欧洲语言周一起。
未配置 `locale` 时它跟随读者的浏览器变化，同一份代码在两台机器上排出的月历首列不同。
要固定为一种排法，显式传入 `locale`（实例上传入，或全局配置一次），不依赖默认值。
:::

各适配器都有一处全局出口，语义一致：Vue 侧是 `provideXhConfig`（走 provide/inject，可按子树覆盖），自定义元素侧是 `setXhConfig`（模块级，整个文档一份）。

## 全局注入一次

```ts
import { provideXhConfig } from "@xihan-ui/vue";

// 应用根组件的 setup 中
provideXhConfig({
  locale: "zh-CN",
  translations: {
    breadcrumb: { root: "面包屑" },
    pagination: { root: "分页", prevTrigger: "上一页", nextTrigger: "下一页" },
    dialog: { close: "关闭" },
    toast: { close: "关闭" },
  },
});
```

取值优先级从高到低：实例 props → 全局注入 → 组件内建默认（文案为英文，`locale` 再向宿主浏览器读取一次）。`translations` 按键合并：全局提供整包，个别实例只覆盖需要修改的键。

## 运行时切语言

传入 ref 或 getter，切换时使用这些文案的组件随之重渲染：

```ts
import { provideXhConfig } from "@xihan-ui/vue";
import { computed, ref } from "vue";
import { enUS, zhCN } from "./locales";

const lang = ref<"zh" | "en">("zh");
provideXhConfig(computed(() => (lang.value === "zh" ? zhCN : enUS)));
```

语言包是一个普通对象（`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 以局部切换语言。
- `createToastService` / `createDialogService` 自带宿主应用，无法接入组件树中的注入。它们的按钮文案使用各自创建时的选项（`okText` 默认 `OK`、`cancelText` 默认 `Cancel`、`translations`），需要整个服务与应用同语言时通过 `config` 传入：

  ```ts
  const dialog = createDialogService({
    config: { locale: "zh-CN", translations: { dialog: { close: "关闭" } } },
    okText: "确定",
    cancelText: "取消",
  });
  ```

  `config` 是一份普通的 `XhConfig`，服务会在自己的子树中 `provideXhConfig` 一次；未提供时使用内建默认。
- 不注入时组件按原路径运行，没有额外开销。
- 自定义元素无法接入 Vue 的注入，使用它自己的全局出口，见下文。

::: tip 文档站的示例为什么逐实例传入
每条示例都是孤立片段，没有应用根可以注入，因此一律写成 `:translations="{ … }"`。
真实应用按上文全局配置一次即可，实例上只保留需要单独覆盖的键。
:::

## 自定义元素侧

自定义元素无法获取 Vue 的 provide/inject，文案是对象，只能通过 property 而不能通过 attribute 设置，因此逐实例设置是原有的唯一路径。`setXhConfig` 提供一处全局出口：

```ts
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` 的全局值 → 组件内建默认（文案为英文，`locale` 再向宿主浏览器读取一次），`translations` 逐键合并。切换语言时再次调用，已挂载的元素随之重渲染：

```ts
setXhConfig({ locale: "en-US", translations: enUS });
```

两处差别：

- `setXhConfig` 是整份替换，不做深合并；修改一处需要传入整份配置。Vue 侧语义相同。
- 它是模块级的，整个文档一份，没有 Vue 那种只在某棵子树切换语言的能力。需要分区时，把该部分渲染为 Vue 组件，或逐实例设置 `.translations`。
