介绍
默认采用中文作为默认语言,允许切换不同语言。
语言切换
sard 提供了 setLocale 函数来切换当前使用的语言。传入完整的语言包对象(包含 _sard 字段),sard 所有组件会立即更新文案。
import { setLocale } from 'sard'
import enUS from 'sard/locale/lang/en-US'
setLocale(enUS)TIP
setLocale 仅切换 sard 组件内部的文案。如果需要管理业务翻译和获取当前语言,请参考下方的 使用 sard 的翻译机制 或 与 vue-i18n 一起使用。
支持的语言列表
- 英语(en-US)
- 简体中文(zh-CN)
- 印地语(hi-IN)
- 西班牙语(es-ES)
- 阿拉伯语(ar-SA)
- 波斯语(fa-IR)
- 印尼语(id-ID)
- 孟加拉语(bn-BD)
- 乌尔都语(ur-PK)
- 葡萄牙语(pt-BR)
- 越南语(vi-VN)
- 土耳其语(tr-TR)
如果你需要使用其他的语言,欢迎贡献 PR,只需在 这里 添加一个语言配置文件即可。
与 vue-i18n 一起使用
如果项目中已经使用了 vue-i18n,可以将 sard 的语言包集成进去,统一管理。
安装
npm install vue-i18n集成
将 sard 语言包导入并与业务语言包合并,然后通过 watch 在切换语言时同步调用 setLocale。
import { createApp, watch } from 'vue'
import { createI18n } from 'vue-i18n'
import { setLocale } from 'sard'
import zhCN from 'sard/locale/lang/zh-CN'
import enUS from 'sard/locale/lang/en-US'
import App from './App.vue'
// 业务语言包
const messages = {
zhCN: {
...zhCN,
hello: '你好,世界',
},
enUS: {
...enUS,
hello: 'Hello, world',
},
}
const i18n = createI18n({
legacy: false,
locale: 'zhCN',
messages,
})
// 动态切换时同步 sard
watch(
() => i18n.global.locale.value,
(locale) => {
setLocale(messages[locale])
},
{ immediate: true },
)
const app = createApp(App)
app.use(i18n)
app.mount('#app')注意
以上同步是单向的(vue-i18n → sard)。sard 组件内部使用自己的 useTranslate 读取 currentLocale,而不是 vue-i18n 的 $t。因此通过 sard 的 useLocale 切换语言时,vue-i18n 不会自动同步,建议始终以 vue-i18n 为唯一语言状态源。
使用 sard 的翻译机制
如果对项目打包体积敏感,且只需要简单的翻译功能,可以使用 sard 的翻译机制。它只依赖 Vue 本身,零额外依赖。
定义语言包
语言包就是一个普通的 JavaScript 对象,按模块划分层级。
需要将 sard 的内部语言包展开合并,否则 sard 组件自身的文案会丢失:
// locales/zh-CN.ts
import sardZhCN from 'sard/locale/lang/zh-CN'
export default {
...sardZhCN,
app: {
title: '我的应用',
greeting: '你好,{name}!',
},
button: {
submit: '提交',
cancel: '取消',
},
}// locales/en-US.ts
import sardEnUS from 'sard/locale/lang/en-US'
export default {
...sardEnUS,
app: {
title: 'My App',
greeting: 'Hello, {name}!',
},
button: {
submit: 'Submit',
cancel: 'Cancel',
},
}提示
- 使用
{key}占位符可以在翻译中插入动态数据,语法见下方 在组件中使用 的示例。 - sard 语言包导出
{ _sard: {...} }结构,展开后组件内部通过_sard.xxx路径读取文案,自定义字段应避免与_sard重名。
注册语言包
在入口文件使用 provideLocale 注册所有语言包并设置默认语言:
import { createApp } from 'vue'
import { provideLocale } from 'sard'
import zhCN from './locales/zh-CN'
import enUS from './locales/en-US'
import App from './App.vue'
const app = createApp(App)
provideLocale(
app,
{
zhCN,
enUS,
},
'zhCN',
)
app.mount('#app')在组件中使用
通过 useTranslate 获取翻译函数,传入链式路径即可获取对应文案。该函数是响应式的,切换语言时视图会自动更新。
<script setup>
import { useTranslate } from 'sard'
const { t, select } = useTranslate('app')
// t('greeting', { name: '小明' }) → '你好,小明!'
// select('button') → { submit: '提交', cancel: '取消' }
</script>
<template>
<div>{{ t('title') }}</div>
<div>{{ t('greeting', { name: '小明' }) }}</div>
</template>useTranslate 返回值:
t / translate(同一个函数):获取翻译文案,支持{key}占位插值。若未找到对应字符串则返回链式路径本身。select:获取语言对象中任意嵌套数据(不强制转字符串),类似lodash的get。
切换语言
在任意组件中通过 useLocale 获取当前语言并切换:
<script setup>
import { useLocale } from 'sard'
const locale = useLocale()!
</script>
<template>
<select v-model="locale">
<option value="zhCN">中文</option>
<option value="enUS">English</option>
</select>
</template>局限
sard 的翻译机制仅支持 {key} 命名插值,不支持复数化(pluralization)、日期数字格式化、语言回退链等高级特性。如果你的应用需要这些能力,请使用 vue-i18n。
封装成 vue-i18n 风格接口
如果业务只需要简单的翻译能力(链式 key、插值、语言切换、模板 $t),但希望拥有一套接近 vue-i18n 的写法——例如在 vue-i18n 与 sard 之间保留低成本切换的可能——可以用“包装模式”给 sard 的翻译机制 套一层薄壳。它不引入额外依赖,且与 sard 组件内部文案共用同一状态源。
TIP
该封装适合已按上文 使用 sard 的翻译机制 接入的项目。不要期望它完整复刻 vue-i18n:复数化、$d / $n、动态 rt、语言回退链、组件级作用域、多实例等能力均不支持,需要这些时请使用真正的 vue-i18n。
封装实现
创建独立模块,例如 locales/compat.ts:
import { ref, watch, type App } from 'vue'
import { assignDeep, chainGet, currentLocale, interpolate, setLocale } from 'sard'
type Pack = Record<string, any>
type Messages = Record<string, Pack>
type Named = Record<string, any>
/** 消息函数的最小 ctx(对齐 vue-i18n 的 named / list / values,不含 plural / linked) */
export interface MessageContext {
named(key: string): any
list(index: number): any
values: Named
}
type MessageFunction = (ctx: MessageContext) => string
function translateMessage(message: MessageFunction, data?: Named | any[]): string {
const list = Array.isArray(data) ? data : []
const values: Named = Array.isArray(data)
? Object.fromEntries(list.map((value, index) => [index, value]))
: (data ?? {})
return message({
named: (key) => values[key],
list: (index) => list[index],
values,
})
}
/* 语言注册表 + 当前语言:唯一数据源 */
const registries: Messages = {}
const locale = ref<string>('zhCN')
const fallbackLocale = ref<string | undefined>(undefined)
// 当前实际生效的语言 key:locale 无语言包时回退到 fallbackLocale
function effectiveLocale(): string | undefined {
return registries[locale.value]
? locale.value
: fallbackLocale.value && registries[fallbackLocale.value]
? fallbackLocale.value
: undefined
}
function sync(): void {
const name = effectiveLocale()
if (name) setLocale(registries[name]!)
}
watch(locale, sync)
watch(fallbackLocale, sync)
/** $i18n:暴露给模板的全局对象形状(读写 locale / fallbackLocale,读取 availableLocales) */
export interface I18nScope {
locale: string
fallbackLocale: string | undefined
readonly availableLocales: string[]
}
/* $i18n:暴露给模板的全局对象,可读写 locale / fallbackLocale,读取 availableLocales */
const i18nScope: I18nScope = {
get locale() {
return locale.value
},
set locale(value: string) {
locale.value = value
},
get fallbackLocale() {
return fallbackLocale.value
},
set fallbackLocale(value: string | undefined) {
fallbackLocale.value = value
},
get availableLocales() {
return Object.keys(registries)
},
}
/* vue-i18n 风格接口 */
export interface Composer {
locale: typeof locale
fallbackLocale: typeof fallbackLocale
t(key: string, data?: Named | any[]): string
te(key: string): boolean
tm(key?: string): any
setLocaleMessage(name: string, pack: Pack): void
mergeLocaleMessage(name: string, pack: Pack): void
getLocaleMessage(name: string): Pack | undefined
}
const composer: Composer = {
locale,
fallbackLocale,
// 字符串 → 插值;函数 → 消息函数;找不到 → 返回 key 本身
t: (key, data) => {
const value = chainGet(currentLocale.value, key)
if (typeof value === 'string') return interpolate(value, data)
if (typeof value === 'function') return translateMessage(value, data)
return key
},
te: (key) => {
const value = chainGet(currentLocale.value, key)
return (
typeof value === 'string' ||
typeof value === 'function' ||
(value != null && typeof value === 'object')
)
},
tm: (key) => (key ? chainGet(currentLocale.value, key) : currentLocale.value),
setLocaleMessage: (name, pack) => {
registries[name] = pack
if (name === effectiveLocale()) setLocale(pack)
},
mergeLocaleMessage: (name, pack) => {
// assignDeep 返回新对象且数组整体覆盖,不会改动用户导入的语言包
registries[name] = assignDeep(registries[name] ?? {}, pack)
if (name === effectiveLocale()) setLocale(registries[name])
},
getLocaleMessage: (name) => registries[name],
}
/** createI18nCompat 产物:满足 app.use 的最小 vue-i18n 风格插件实例 */
export interface I18nCompat {
readonly global: Composer
install(app: App): void
useI18n(): Composer
}
export function createI18nCompat(
options: {
locale?: string
fallbackLocale?: string
messages?: Messages
globalInjection?: boolean
} = {},
): I18nCompat {
if (options.messages) Object.assign(registries, options.messages)
if (options.locale) {
locale.value = options.locale
} else {
const first = Object.keys(registries)[0]
if (first) locale.value = first
}
if (options.fallbackLocale) fallbackLocale.value = options.fallbackLocale
sync() // 首次注入词条时也同步一次
return {
global: composer,
install(app: App) {
if (options.globalInjection !== false) {
app.config.globalProperties.$i18n = i18nScope
app.config.globalProperties.$t = composer.t
app.config.globalProperties.$te = composer.te
app.config.globalProperties.$tm = composer.tm
}
},
useI18n: () => composer,
}
}TIP
locale 与 fallbackLocale 均可在 composer 上动态修改。切换语言时,若 locale 对应的语言包未注册,会整包回退使用 fallbackLocale 的语言包;若 fallbackLocale 也未注册,则保持当前语言不变(不会报错)。注意此回退是整包替换,不做逐词条回退。
另 globalInjection 会注入 $i18n,暴露 locale / fallbackLocale / availableLocales,模板中可直接 v-model="$i18n.locale" 读写当前语言。与 vue-i18n 相比,仅实现 $t / $te / $tm / $i18n,未含 $rt / $d / $n。
语言包的值除了字符串,还支持函数消息:链式取值得到函数时,会以最小 ctx(named / list / values,对齐 vue-i18n 消息函数,不含 plural / linked)调用并返回其字符串结果:
// locales/zh-CN.ts(片段)
{
...sardZhCN,
cart: {
total: ({ named }) => `共 ${named('count')} 件商品`,
},
}t('cart.total', { count: 3 })→共 3 件商品te('cart.total')→true
注册
创建共享实例并导出,与 vue-i18n 的用法一致。语言包需像 定义语言包 那样展开合并 sard 语言包,否则组件文案会丢失:
import { createI18nCompat } from './compat'
import zhCN from './zh-CN'
import enUS from './en-US'
export const i18n = createI18nCompat({
locale: 'zhCN',
fallbackLocale: 'enUS', // locale 未注册时回退到此语言包
messages: { zhCN, enUS },
globalInjection: true, // 开启模板 $t
})
// 便于组件中直接解构使用
export const useI18n = i18n.useI18n在入口文件安装:
import { createApp } from 'vue'
import { i18n } from './locales'
import App from './App.vue'
createApp(App).use(i18n).mount('#app')在模板与组件中使用
useI18n 返回的 t 与模板 $t 均可用,且随语言切换响应式更新:
<script setup>
import { useI18n } from '../locales'
const { t, te } = useI18n()
</script>
<template>
<p>{{ $t('app.title') }}</p>
<p v-if="te('app.greeting')">{{ t('app.greeting', { name: '小明' }) }}</p>
<!-- 切换语言:v-model="$i18n.locale",用法与 vue-i18n 一致,sard 组件文案会随之更新 -->
<select v-model="$i18n.locale">
<option value="zhCN">中文</option>
<option value="enUS">English</option>
</select>
</template>命令式访问与覆盖词条:
import { i18n } from './locales'
i18n.global.locale.value = 'enUS'
i18n.global.t('app.title') // 'My App'
i18n.global.mergeLocaleMessage('zhCN', {
_sard: { calendar: { start: '始' } },
})注意
封装本身是单一状态源。接入后请统一通过 composer.locale / setLocaleMessage 切换,不要再混用 setLocale 或 provideLocale,避免两套语言状态互不同步。该封装为模块级单例,不支持多实例。
覆盖语言包
语言包是普通的对象,可直接修改其属性来覆盖 sard 组件默认文案:
import zhCN from 'sard/locale/lang/zh-CN'
zhCN._sard.calendar.start = '始'或者使用内置 extend 工具函数进行深层合并:
import { extend } from 'sard'
extend(zhCN, {
_sard: {
calendar: {
start: '始',
},
},
})如果需要不可变覆盖(不改动原始导入对象,数组等整体覆盖),可用 assignDeep 得到新对象:
import { assignDeep } from 'sard'
const customZhCN = assignDeep(zhCN, {
_sard: {
calendar: {
start: '始',
},
},
})TIP
修改语言包对象是全局持久的(模块单例)。如果需要在不同页面使用不同的文案变体,应在注册语言包时传入多个副本(可用 assignDeep 生成),而非直接修改原始导入对象。
更多字段,可参考 zh-CN.ts。
API
setLocale
切换 sard 当前使用的语言包,所有组件文案立即更新。
function setLocale(locale: Record<string, any>): void| 参数 | 类型 | 说明 |
|---|---|---|
locale | Record<string, any> | 语言包对象,需包含 _sard 字段 |
useTranslate
获取翻译函数,支持链式路径和 {key} 占位插值。响应式,切换语言时模板自动更新。
function useTranslate(prefix?: string): {
t: LocaleTranslate
translate: LocaleTranslate
select: (chain?: string) => any
}| 参数 | 类型 | 默认值 | 说明 |
|---|---|---|---|
prefix | string | '' | 路径前缀,减少样板代码 |
返回值:
| 属性 | 类型 | 说明 |
|---|---|---|
t / translate | LocaleTranslate | 同一个函数,获取翻译文案,找不到时返回链式路径本身 |
select | (chain?: string) => any | 获取嵌套数据,不强制转字符串,类似 lodash.get |
interface LocaleTranslate {
(
chainOrData?: string | Record<string, number | string>,
data?: Record<string, number | string>,
): string
}useTranslateWithPrefix
useTranslate 的便捷封装,自动添加 _sard. 前缀。sard 所有组件内部均使用此函数。
function useTranslateWithPrefix(prefix?: string): {
t: LocaleTranslate
translate: LocaleTranslate
select: (chain?: string) => any
}provideLocale
注册所有语言包并注入当前语言,供 useLocale 消费。通常在 main.ts 中调用。
function provideLocale<T extends Record<string, any>>(
app: App,
languages: T,
defaultLocale: keyof T,
): void| 参数 | 类型 | 说明 |
|---|---|---|
app | App | Vue 应用实例 |
languages | T | 所有语言包对象,key 为语言标识(如 zhCN) |
defaultLocale | keyof T | 默认语言标识 |
useLocale
获取当前语言的响应式引用,读写均可。
function useLocale(): Ref<string> | undefined注意: 必须在 provideLocale 调用后的组件树中使用,否则返回 undefined。