# 国际化

组件内部自带的提示文本（如搜索占位符、复制成功提示、清空按钮等）走统一的语言环境管理，没有把中文或英文硬编码在每个组件里。随包默认内置的语言是英文（`en`）和简体中文（`zh-CN`）。在 TypeScript 类型定义中，`AstryxLocale` 指的就是这两种内置语言代码。

通常只需在应用的顶层根组件提供一次语言配置，下层所有子组件就可以自动通过 `useAstryxLocale` 和 `useAstryxMessages` 读取当前的语言文本。未配置时，默认使用英文。

```ts
import { provideAstryxLocale } from '@astryx-vue/core/locale'

// 全局配置为简体中文
provideAstryxLocale('zh-CN')
```

与国际化相关的常用函数（如 `provideAstryxLocale`、`useAstryxLocale`、`useAstryxMessages`、`useLocale`、`useTranslator`、`normalizeLocale`、`loadAstryxCatalog` 和 `useCollator`）均可以从 `@astryx-vue/core/locale` 引入，在包根 `@astryx-vue/core` 也有重新导出。仅日历相关的底层格式化工具 `formatCalendarMonth` 和 `weekdayNames` 专属于 `@astryx-vue/core/locale`。

`normalizeLocale` 函数会自动标准化常见的语言标签：
- 把 `zh`、`zh-CN`、`zh-Hans` 等归一化为 `zh-CN`，对上内置的简体中文文案；
- 其他标签只规范大小写，地区信息会保留，例如 `en-gb` 变成 `en-GB`、`ja-jp` 变成 `ja-JP`。日期、数字、排序因此按这个地区的习惯来；
- 内置文案只分「简体中文」和「其他」两种：`en`、`en-GB`、`fr-FR` 这些没有交 `messages` 的语言，组件文字都用英文；
- 空值或无法解析的标签会自动回退到英文 `en`。

## 在组件之外读语言和翻译（useLocale、useTranslator）

组件里的格式化（日期、数字、相对时间、排序）都读最近一层 `InternationalizationProvider` 的语言，不读 `navigator.language`。你自己写的格式化函数也应该这样：

```ts
import { useLocale, useTranslator } from '@astryx-vue/core/locale'

const { locale } = useLocale()
const { t } = useTranslator()

// 语言变了，这两个值会跟着变
const label = computed(() => new Intl.DateTimeFormat(locale.value).format(date))
const announce = () => t('@astryx.pagination.pageAnnounce', { current: 3 })
```

`t` 收的是原版的 `@astryx.*` 键，返回当前语言的成品文案。键认不出来时原样返回这个键，方便一眼看出写错了。在事件回调里也可以直接调用。

## 支持中文和英文以外的更多语言

原版 Astryx 在 `packages/core/locales` 目录下存放了约 30 份基于 React Intl 格式的语言 JSON 文件（包含 `ja-JP`、`zh-TW`、`ar-SA` 等）。我们将这些官方多语言资源完整同步在包内的 `locales/` 目录下。

你可以借助 `loadAstryxCatalog` 函数，将这些原版 JSON 转换映射为组件库可用的 `AstryxMessages` 结构，然后作为第二个参数传递给 `provideAstryxLocale`：

```ts
import { loadAstryxCatalog, provideAstryxLocale } from '@astryx-vue/core/locale'
import catalog from '@astryx-vue/core/locales/ja-JP.json'

// 加载日语语言包
const loaded = loadAstryxCatalog('ja-JP', catalog)
provideAstryxLocale(loaded.locale, loaded.messages)
```

加载器具备容错处理：
- 如果原版 JSON 中某些键名在 Vue 版中未被使用，会保存在返回对象的 `unmapped` 属性中，避免数据丢失；
- 如果某项文本在 JSON 中缺失，会自动回退到内置英文，涉及的字段路径记录在 `fallbackFields` 中；
- 如果组件需要某项文本但外部 JSON 根本没有提供，会记录在 `missingKeys` 中。

> **提示**：内置英文和原版 `en.json` 逐字一致，`pnpm check:i18n-catalog` 会守着这一点。内置的 `zh-CN` 是我们自己写的中文，比原版 `zh-CN.json` 更口语一些，请不要用原版的那份去覆盖它。其他语言才建议通过该加载器引入。
>
> 如果只调用 `provideAstryxLocale('ja-JP')` 而没有传入转换后的 `messages`，那么组件内部的日期和字母排序会按日语规则运行，但组件自带的提示语仍会保持英文；同理，传入 `zh-TW` 也不会自动偷懒降级为简体中文。

如果你是维护者，需要同步原版最新语言包，运行 `node scripts/vendor-astryx-locales.mjs` 即可，它默认拉原版 `main` 的最新提交；需要复现某一次抓取时，把提交号当参数传进去。实际用到的提交号写在 `locales/SOURCE.json`，来源与开源许可证说明记录在 `packages/core/THIRD_PARTY_NOTICES.md` 中。

拉完后跑一次 `pnpm check:i18n-catalog`（`pnpm check` 里也会跑）。它会检查：

- 内置 `en` 和 `zh-CN` 的每个字段都有，而且形状一样；
- 内置文案用上了原版的每个占位符，内置英文和原版英文逐字一致；
- 29 种语言的每句译文都能解析，占位符的名字和写法（`{name}`、`number`、`plural`、`select`）和英文原文一致，没有过期的键；
- 原版新增的每个键，要么已经接进 `packages/core/src/locale/upstream/`，要么在 `scripts/i18n-catalog/coverage.mjs` 里写明了为什么暂不接入。

## 按当前语言排序字符串（useCollator）

在不同国家和语言下，文字的排序习惯差异很大（例如拼音排序与注音、假名排序）。`useCollator` 函数返回一个基于当前语言环境的 `Intl.Collator` 实例，并包装在 Vue 的 `computed` 中。未设置语言时默认按英文处理：

```ts
import { useCollator } from '@astryx-vue/core/locale'

const collator = useCollator({ numeric: true })
// 按照当前语言环境自然排序
items.sort((a, b) => collator.value.compare(a.name, b.name))
```

## 区分组件内置文案与业务文案

`AstryxMessages` 仅管理组件库内置的通用文案（如「复制」、「清除」等）。你在业务中自己写进组件的文字（例如 `` 传入的 `label`），是由你的业务代码完全掌控的，切换组件库语言不会去修改你的业务文字。

文档站顶部切换的中英文，是通过 VitePress 路由实现的两个完全独立的页面树（分别位于根路径 `/` 和 `/en/`），属于文档网站层面的多语言；而文档中的实时组件示例，则会自动根据当前页面的文档语言调用 `provideAstryxLocale`。

## 从右往左排版与伪语言测试

阿拉伯语、希伯来语、波斯语这些语言从右往左写。这件事组件库已经做了大半，但有两步得你自己做。

### 第一步：告诉组件方向

`InternationalizationProvider` 有一个 `dir` 属性，取 `'ltr'` 或 `'rtl'`。不传的话，方向由 `locale` 推出来：`ar-SA`、`he` 得到 `rtl`，其余得到 `ltr`。

推导靠 `getLocaleDirection()`。它先问浏览器要 `Intl.Locale` 的书写方向；浏览器还不支持时，退回一张内置的从右往左语言清单（`ar`、`he`、`fa`、`ur`、`ps`、`sd` 等）。认不出来的标签按 `ltr` 处理，不会报错。

```vue
<script setup lang="ts">
import { InternationalizationProvider } from '@astryx-vue/core/InternationalizationProvider'
</script>

<template>
  <!-- 方向由 locale 推出来 -->
  <InternationalizationProvider locale="ar-SA">
    <App />
  </InternationalizationProvider>

  <!-- 语言是英文，但强制按从右往左排版：用来测布局 -->
  <InternationalizationProvider locale="en" dir="rtl">
    <App />
  </InternationalizationProvider>
</template>
```

自己在组合式函数里要读方向，用 `useLocaleDirection()`，它返回一个 `ref`，语言变了会跟着变：

```ts
import { useLocaleDirection } from '@astryx-vue/core/InternationalizationProvider'

const direction = useLocaleDirection()
// 'ltr' 或 'rtl'
```

### 第二步：自己设置 DOM 的 dir

`dir` 属性只决定 Astryx 组件读到的方向，它不会去改 DOM。这一步必须你来做，通常写在 `<html>` 上：

```ts
import { getLocaleDirection } from '@astryx-vue/core/InternationalizationProvider'

// 浏览器里切换语言时同步一次
document.documentElement.dir = getLocaleDirection(locale.value)
document.documentElement.lang = locale.value
```

漏掉这一步的后果：文字仍然从左往右排，标点和混排文本的方向不对，滚动条位置也反。组件内部的镜像是对的，页面上看着还是反的。

### 组件做了什么

- **内外边距和定位。** 组件样式大量使用逻辑属性：`padding-inline-start`、`margin-inline-end`、`inset-inline-start`。方向一反，这些值自动换边，不需要第二套样式。
- **文字对齐。** 用的是 `text-align: start` 和 `end`，不是 `left` 和 `right`。
- **少数位置单独处理。** 提示框、悬停卡片的 `start`、`end` 弹层在 `[dir='rtl']` 下会翻边；面包屑的分隔斜杠会跟着改方向；滑杆的握把用 `:dir(rtl)` 单独修正。
- **键盘方向键。** 日历读 `document.documentElement.dir`：从右往左时，左右方向键的作用是反的。滚动相关的计算也会按书写方向换轴。

这也解释了为什么第二步不能省：日历和滚动这类逻辑读的就是 DOM 上的 `dir`，不是 provider 的值。

### 伪语言测试

最省事的做法不是装语言包，而是两步：

1. 把 `dir` 设成 `rtl`，`<html dir="rtl">` 也设上，看整个页面还站不站得住。
2. 把文案换成一段更长的假文字，看布局会不会被撑破。

第二步用 `overrides` 属性。它按语言覆盖指定键，只改你写的那几条：

```vue
<script setup lang="ts">
import { InternationalizationProvider } from '@astryx-vue/core/InternationalizationProvider'

// 键和原版语言包一致。这里故意写长，用来挤布局
const pseudo = {
  '@astryx.table.noData': '没有找到任何数据，这句话被故意写得很长很长，用来检查单元格会不会被撑破',
}
</script>

<template>
  <InternationalizationProvider locale="zh-CN" :overrides="pseudo">
    <App />
  </InternationalizationProvider>
</template>
```

### 检查清单

方向改成 `rtl`、文案拉长以后，逐条看这几件：

- 数字、价格、日期还是从左往右读，没有跟着整段反过来。
- 图标和文字的顺序对了：返回箭头指向右边，下拉箭头在文字的左边。
- 输入框里的光标从右边开始，占位文字靠右对齐。
- 侧边栏和侧面板换到了右边，拖拽手柄的方向也跟着反了。哪一处没换，就是那一处写死了 `left` 或 `right`。
- 长文案下按钮不换行，表格列不塌，标题不溢出色块。
- 表格里的数字列仍然右对齐（数字本身从右往左写会很怪）。

这套检查在深色模式、窄屏下各跑一遍，差不多就够用了。
