element-plus的国际化:element-plus.org/zh-CN/guide…
语言包定义 → ConfigProvider 提供 → provide/inject 注入 → useLocale 获取 → translate 翻译 → 组件使用
一、语言包定义
Object.assign:Object.assign() - JavaScript | MDN
二、ConfigProvider 组件(配置提供者)
packages/components/config-provider/src/config-provider.ts
其中: renderSlot是vue3提供的函数,用于在渲染函数中渲染插槽内容
在.vue文件中
<template>
<!-- 渲染默认插槽,并传递 config 数据 -->
<slot :config="config" />
</template>
但在 .ts 文件中(使用渲染函数),没有 <template>,所以用 renderSlot 来渲染插槽:
return () => renderSlot(slots, 'default', { config: config?.value })
// 功能完全一样!
renderSlot(slots, 'default', { config: config?.value })
// ↑ ↑ ↑
// | | └─ 传递给插槽的数据(作用域插槽)
// | └─ 插槽名称('default' 是默认插槽)
// └─ 插槽对象(从 setup 的 { slots } 获取)
el-config-provider 组件
- 统一配置:为被它包裹的所有子组件提供统一的配置(语言、尺寸、消息位置等)
- 避免重复:不需要在每个组件上单独传配置
const ConfigProvider = defineComponent({
name: 'ElConfigProvider',
props: configProviderProps,
setup(props, { slots }) {
// 将组件接收的props作为全局配置提供给所有子组件
// 将配置通过 Vue 的 provide/inject 机制传递给所有子组件
// 子组件通过 inject 获取配置
const config = provideGlobalConfig(props)
// 单独监听 message 配置的变化
// 因为 ElMessage 是全局方法(不是组件),需要特殊处理
// 将配置合并到全局的 messageConfig 对象中
watch(
() => props.message,
(val) => {
Object.assign(messageConfig, config?.value?.message ?? {}, val ?? {})
},
{ immediate: true, deep: true }
)
// 渲染被包裹的子组件(<el-config-provider> 标签内的内容)
return () => renderSlot(slots, 'default', { config: config?.value })
},
})
<el-config-provider :size="large">
<el-config-provider :locale="zhCn">
<!-- 这里会同时使用 large 尺寸和中文 -->
</el-config-provider>
</el-config-provider>
三、provideGlobalConfig(全局配置提供)
位置:packages/components/config-provider/src/hooks/use-global-config.ts
- 通过 provide 将语言配置注入到 Vue 的依赖注入系统
- 使用 localeContextKey 作为注入键
- 使用 computed 保持响应式
/**
* 从父级组件或全局获取配置
* @param key 配置项的键
* @param defaultValue 默认值
* @returns 返回配置项的值
*/
export function useGlobalConfig(
key?: keyof ConfigProviderContext,
defaultValue = undefined
) {
// 检查是否在setup中
// 在组件中 → 从父级获取配置
// 不在组件中 → 使用全局配置
const config = getCurrentInstance()
? inject(configProviderContextKey, globalConfig)
: globalConfig
// 如果提供了键,返回计算属性,计算配置项的值
if (key) {
return computed(() => config.value?.[key] ?? defaultValue)
} else {
return config
}
}
/**
* 这个函数用于将配置通过vue的provide/inject机制传递给所有子组件
* @param config 全局配置对象
* @param app Vue 应用实例
* @param global 是否是全局配置
* @returns 返回配置对象
*/
export const provideGlobalConfig = (
config: MaybeRef<ConfigProviderContext>,
app?: App,
global = false
) => {
// 检查环境
const inSetup = !!getCurrentInstance()
// 如果有旧配置就获取
const oldConfig = inSetup ? useGlobalConfig() : undefined
// 确定使用哪个provide函数
// 优先使用app.provide(应用级),否则在setup中用provide(组件级),都不满足则为undefined
const provideFn = app?.provide ?? (inSetup ? provide : undefined)
// 如果没有可用的 provide 函数,警告并退出
if (!provideFn) {
debugWarn(
'provideGlobalConfig',
'provideGlobalConfig() can only be used inside setup().'
)
return
}
const context = computed(() => {
// 获取配置的实际值(如果是 ref)
const cfg = unref(config)
// 没有旧配置,直接返回新配置
if (!oldConfig?.value) return cfg
// 有旧配置,合并它
return mergeConfig(oldConfig.value, cfg)
})
provideFn(configProviderContextKey, context)
provideFn(
localeContextKey,
computed(() => context.value.locale)
)
if (global || !globalConfig.value) {
globalConfig.value = context.value
}
return context
}
四、useLocale Hook(获取语言配置)
位置:packages/hooks/use-locale/index.ts
export const useLocale = (localeOverrides?: Ref<Language | undefined>) => {
const locale = localeOverrides || inject(localeContextKey, ref())!
return buildLocaleContext(computed(() => locale.value || English))
}
/**
* 构建一个语言环境上下文对象,包含语言信息和翻译函数
* @param locale
* @returns
*/
export const buildLocaleContext = (
locale: MaybeRef<Language>
): LocaleContext => {
// 提取语言名称 如zh-cn、en,如果是ref则取值,否则直接使用
const lang = computed(() => unref(locale).name)
// 统一转换为响应式引用,如果已经是 ref,直接使用,如果不是,用 ref() 包装
const localeRef = isRef(locale) ? locale : ref(locale)
return {
lang, // 语言名称(如 'zh-cn')
locale: localeRef, // 完整的语言对象(响应式)
t: buildTranslator(locale), // 翻译函数
}
}
五、translate 函数(翻译核心)
位置:packages/hooks/use-locale/index.ts
/**
* 根据路径从语言对象中获取翻译文本,并替换其中的占位符。
* @param path
* @param option
* @param locale
* @returns
*
* translate('el.colorpicker.confirm', undefined, locale)
* 步骤1: get(locale, 'el.colorpicker.confirm', 'el.colorpicker.confirm')
* → 找到 '确定'
* 步骤2: '确定'.replace(/\{(\w+)\}/g, ...)
* → 没有占位符,直接返回 '确定'
* 结果: '确定'
*/
export const translate = (
path: LocaleKeys,
option: undefined | TranslatorOption,
locale: Language
): string =>
(get(locale, path, path) as string).replace(
/\{(\w+)\}/g,
(_, key) => `${option?.[key] ?? `{${key}}`}`
)
六、组件中使用
位置:packages/components/input-number/src/input-number.vue
const { t } = useLocale()
:aria-label="t('el.inputNumber.decrease')"
七、完整流程图
┌─────────────────────────────────────────────────────────────┐
│ 1. 定义语言包 │
│ packages/locale/lang/zh-cn.ts │
│ { name: 'zh-cn', el: { inputNumber: { ... } } } │
└──────────────────────┬──────────────────────────────────────┘
│
▼
┌─────────────────────────────────────────────────────────────┐
│ 2. 用户使用 ConfigProvider │
│ <el-config-provider :locale="zhCn"> │
│ <el-input-number /> │
│ </el-config-provider> │
└──────────────────────┬──────────────────────────────────────┘
│
▼
┌─────────────────────────────────────────────────────────────┐
│ 3. ConfigProvider 调用 provideGlobalConfig │
│ provideGlobalConfig(props) │
│ ↓ │
│ provideFn(localeContextKey, computed(() => locale)) │
└──────────────────────┬──────────────────────────────────────┘
│
▼
┌─────────────────────────────────────────────────────────────┐
│ 4. 子组件通过 useLocale 获取 │
│ const { t } = useLocale() │
│ ↓ │
│ inject(localeContextKey) → 获取语言包 │
│ ↓ │
│ buildLocaleContext() → 构建翻译函数 │
└──────────────────────┬──────────────────────────────────────┘
│
▼
┌─────────────────────────────────────────────────────────────┐
│ 5. 使用 t() 函数翻译 │
│ t('el.inputNumber.decrease') │
│ ↓ │
│ translate() → 从语言包中查找 │
│ ↓ │
│ 返回: '减少' (中文) 或 'Decrease' (英文) │
└─────────────────────────────────────────────────────────────┘