Skip to content

国际化 ​

组件内部自带的提示文本(如搜索占位符、复制成功提示、清空按钮等)走统一的语言环境管理,没有把中文或英文硬编码在每个组件里。随包默认内置的语言是英文(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 仅管理组件库内置的通用文案(如「复制」、「清除」等)。你在业务中自己写进组件的文字(例如 <Button label="提交表单" /> 传入的 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。
  • 长文案下按钮不换行,表格列不塌,标题不溢出色块。
  • 表格里的数字列仍然右对齐(数字本身从右往左写会很怪)。

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