跳转至

业务组件 i18n 接入指南

📄 创建: 未记录

https://doc.xpku.com/raw/tech/frontend/i18n/guide.md

快速入门

核心概念

业务组件接入 i18n 的三个核心步骤:

  1. 定义模块defineBusinessI18n()
  2. 挂载组件attachI18n()
  3. 使用翻译useComponentI18n()

完整接入示例

1. 目录结构

ui-i18n-demo/
  ├── index.js              # 组件入口
  ├── src/
  │   ├── component.vue     # 主组件
  │   ├── i18n/
  │   │   ├── index.js    # i18n 模块定义
  │   │   ├── zh-CN.js    # 中文语言包
  │   │   └── en-US.js    # 英文语言包
  │   └── business/
  │       └── MainContent.vue

2. 创建语言包

// src/i18n/zh-CN.js
export default {
  common: {
    title: "i18n国际化功能演示",
    switchLang: "切换语言",
    confirm: "确定",
    cancel: "取消",
  },
  modules: {
    basic: {
      title: "基础翻译功能",
      simple: "这是一个简单的翻译文本",
      interpolation: "你好,{name}!",
    },
  },
};
// src/i18n/en-US.js
export default {
  common: {
    title: "i18n Internationalization Demo",
    switchLang: "Switch Language",
    confirm: "Confirm",
    cancel: "Cancel",
  },
  modules: {
    basic: {
      title: "Basic Translation",
      simple: "This is a simple translation text",
      interpolation: "Hello, {name}!",
    },
  },
};

3. 定义 i18n 模块 (src/i18n/index.js)

import { defineBusinessI18n } from '@bams-app/i18n';
import zhCN from './zh-CN.js';
import enUS from './en-US.js';

export default defineBusinessI18n({
  name: 'ui-i18n-demo', // 包名,会自动转成 namespace: "uiI18nDemo"
  messages: {
    'zh-CN': zhCN,
    'en-US': enUS,
  }
});

4. 组件入口文件 (index.js)

import Component from './src/component.vue';
import { attachI18n } from '@bams-app/i18n';
import i18nModule from './src/i18n/index.js';

export default attachI18n(Component, i18nModule);

5. 主组件 (src/component.vue)

<template>
  <ComponentContainer>
    <MainContent />
  </ComponentContainer>
</template>
<script setup>
  import { ComponentContainer } from "@bams-app/components";
  import MainContent from "./business/MainContent.vue";

  /** 注意:根组件不写业务逻辑代码,用于通用配置和work配置 */
  defineOptions({
    name: "uiI18nDemo",
    dicts: [],
    config: {},
  });
</script>

6. 业务组件 (src/business/MainContent.vue)

<script setup>
import { useComponentI18n } from "@bams-app/i18n";
import { useLocale } from "@bams-app/i18n";

// 使用业务组件的命名空间
const { t } = useComponentI18n();

// 也可以使用通用的 useLocale
const { setLocale } = useLocale();
</script>

<template>
  <div>
    <h2>{{ t('common.title') }}</h2>
    <p>{{ t('modules.basic.simple') }}</p>
    <p>{{ t('modules.basic.interpolation', { name: '张三' }) }}</p>
  </div>
</template>

进阶用法

异步加载语言包

// src/i18n/index.js
import { defineBusinessI18n } from '@bams-app/i18n';

export default defineBusinessI18n({
  name: 'ui-i18n-demo',
  // 使用 loaders 异步加载(减少首屏体积)
  loaders: {
    'zh-CN': () => import('./zh-CN.js'),
    'en-US': () => import('./en-US.js'),
  }
});

同步与异步结合使用

import { defineBusinessI18n } from '@bams-app/i18n';
import commonZhCN from './common-zh-CN.js';
import commonEnUS from './common-en-US.js';

export default defineBusinessI18n({
  name: 'ui-i18n-demo',
  // 常用翻译同步加载
  messages: {
    'zh-CN': commonZhCN,
    'en-US': commonEnUS,
  },
  // 不常用的翻译异步加载
  loaders: {
    'zh-CN': () => import('./zh-CN.js'),
    'en-US': () => import('./en-US.js'),
  }
});

自定义 namespace

import { defineBusinessI18n } from '@bams-app/i18n';
import zhCN from './zh-CN.js';
import enUS from './en-US.js';

export default defineBusinessI18n({
  name: 'ui-i18n-demo',
  namespace: 'customNamespace', // 显式指定 namespace
  messages: {
    'zh-CN': zhCN,
    'en-US': enUS,
  }
});

局部翻译 - 完整能力详解

局部翻译适用于以下场景: - 弹窗、对话框的临时文案 - 某个功能模块的独立字典 - 不想污染全局命名空间的文案 - 需要局部优先,全局回退的场景

方式一:useLocalI18n - 函数式使用(单组件内)

<script setup>
import { useLocalI18n, useComponentI18n } from "@bams-app/i18n";

const { t } = useComponentI18n();

// 定义局部消息字典
const localMessages = {
  "zh-CN": {
    localTitle: "这是局部翻译的标题",
    localContent: "这是局部翻译的内容",
    onlyLocal: "这个翻译只存在于局部字典中",
    welcome: "欢迎,{name}!",
  },
  "en-US": {
    localTitle: "This is local translation title",
    localContent: "This is local translation content",
    onlyLocal: "This translation only exists in local dictionary",
    welcome: "Welcome, {name}!",
  },
};

// 基本用法 - 继承父组件的 namespace
const localT = useLocalI18n({
  messages: localMessages,
  // 可选:指定局部 namespace
  // namespace: 'myLocalModule',
  // 可选:关闭回退到全局(默认 true)
  // fallbackToGlobal: false,
});
</script>

<template>
  <div>
    <!-- 方式1:直接调用 translator -->
    <p>{{ localT('localTitle') }}</p>
    <p>{{ localT('localContent') }}</p>
    <p>{{ localT('welcome', { name: '张三' }) }}</p>

    <!-- 方式2:通过 .t 调用 -->
    <p>{{ localT.t('localTitle') }}</p>

    <!-- 检查翻译是否存在 -->
    <p v-if="localT.te('onlyLocal')">{{ localT('onlyLocal') }}</p>

    <!-- 回退到全局:局部找不到时自动使用全局 -->
    <p>{{ localT('common.title') }}</p>
  </div>
</template>

方式二:LocalI18nProvider + useLocalI18nProvided - 跨组件共享

<!-- 父组件:提供局部翻译 -->
<template>
  <LocalI18nProvider 
    :messages="localMessages" 
    namespace="myDialog"
    :fallback-to-global="true"
  >
    <template #default="{ t: providerT }">
      <!-- 通过 slot 直接使用 -->
      <div>
        <h3>{{ providerT('dialogTitle') }}</h3>
      </div>

      <!-- 子组件通过 useLocalI18nProvided 消费 -->
      <DialogContent />
    </template>
  </LocalI18nProvider>
</template>

<script setup>
import { LocalI18nProvider } from "@bams-app/i18n";
import DialogContent from './DialogContent.vue';

const localMessages = {
  "zh-CN": {
    dialogTitle: "确认对话框",
    dialogContent: "您确定要执行此操作吗?",
    confirmBtn: "确定",
  },
  "en-US": {
    dialogTitle: "Confirm Dialog",
    dialogContent: "Are you sure you want to do this?",
    confirmBtn: "Confirm",
  },
};
</script>
<!-- 子组件:消费父组件提供的局部翻译 -->
<script setup>
import { useLocalI18nProvided } from "@bams-app/i18n";

// 获取父组件 provide 的局部翻译
// 如果找不到,会自动回退到全局翻译并警告
const localT = useLocalI18nProvided();
</script>

<template>
  <div class="dialog-content">
    <p>{{ localT('dialogContent') }}</p>
    <a-button type="primary">{{ localT('confirmBtn') }}</a-button>
  </div>
</template>

方式三:createLocalTranslator - 纯 JS 环境使用

// utils/helper.js
import { createLocalTranslator } from "@bams-app/i18n";

// 定义局部消息
const localMessages = {
  "zh-CN": {
    errorTitle: "操作失败",
    successTitle: "操作成功",
  },
  "en-US": {
    errorTitle: "Operation Failed",
    successTitle: "Operation Succeeded",
  },
};

// 创建局部翻译器
const localT = createLocalTranslator({
  messages: localMessages,
  namespace: 'myUtils',
  fallbackToGlobal: true,
});

// 使用方式
export function showSuccess() {
  console.log(localT('successTitle'));
  console.log(localT.t('successTitle'));
  console.log(localT.te('successTitle'));
}

局部翻译的回退机制详解

局部翻译的查找顺序(优先级从高到低):

  1. 局部字典查找 → 当前语言的对应 key
  2. 全局回退 → 如果找不到且 fallbackToGlobal=true,查找全局翻译
  3. 兜底语言查找 → 当前语言找不到,尝试 fallbackLocale 的局部字典
<script setup>
import { useLocalI18n } from "@bams-app/i18n";

const localMessages = {
  "zh-CN": {
    localKey: "中文局部翻译",
  },
  "en-US": {
    localKey: "English local translation",
  },
};

const localT = useLocalI18n({
  messages: localMessages,
  fallbackToGlobal: true, // 默认 true
});
</script>

<template>
  <div>
    <!-- 1. 优先使用局部翻译 -->
    <p>{{ localT('localKey') }}</p>

    <!-- 2. 局部找不到,回退到全局(假设全局有 'common.title') -->
    <p>{{ localT('common.title') }}</p>

    <!-- 3. 都找不到,返回 key 本身 -->
    <p>{{ localT('nonExistentKey') }}</p>
  </div>
</template>

useLocalI18n 的完整返回值

const translator = useLocalI18n({
  messages: localMessages,
  namespace: 'myModule',
  fallbackToGlobal: true,
});

// 可以直接调用 translator()
translator('key');

// 也可以通过属性访问:
translator.t;         // 翻译函数
translator.te;        // 检查翻译是否存在
translator.locale;    // 当前语言(响应式)
translator.setLocale; // 设置语言
translator.antdLocale;// Ant Design 的 locale 对象
translator.loading;   // 加载状态
translator.state;     // 完整的 i18n 状态
translator.namespace; // 当前 namespace

响应式的 messages 和 options

useLocalI18n 支持响应式的配置:

<script setup>
import { ref, computed } from 'vue';
import { useLocalI18n } from "@bams-app/i18n";

const isDarkMode = ref(false);

// 响应式的 messages
const dynamicMessages = computed(() => ({
  "zh-CN": {
    theme: isDarkMode.value ? "深色模式" : "浅色模式",
  },
  "en-US": {
    theme: isDarkMode.value ? "Dark Mode" : "Light Mode",
  },
}));

const localT = useLocalI18n({
  messages: dynamicMessages, // 传入 ref 或 computed
});
</script>

<template>
  <div>
    <a-switch v-model:checked="isDarkMode" />
    <p>{{ localT('theme') }}</p>
  </div>
</template>

使用 provideComponentI18n

<script setup>
import { provideComponentI18n } from "@bams-app/i18n";

// 显式提供 namespace
provideComponentI18n("myCustomNamespace");
</script>

常见问题

问题 解决方案
useComponentI18n() 提示未找到 namespace 确保在 index.js 中正确调用 attachI18n()
翻译显示路径不对 检查 defineBusinessI18n()name 是否正确
子组件无法获取不到 namespace 确保子组件在被 attachI18n() 的组件内部使用
切换语言组件不更新 确保使用了 useComponentI18n() 返回的 t 函数
useLocalI18nProvided() 警告找不到 provider 确保父组件确实用 LocalI18nProvider 包裹
局部翻译不生效 检查 messages 的语言 key 是否和当前语言匹配

最佳实践

  1. 语言文件独立存放于 src/i18n/ 目录
  2. src/i18n/index.js 中统一导出 i18n 模块
  3. 使用 kebab-case 命名包名
  4. 按功能模块组织翻译 key
  5. 常用翻译用 messages 同步加载,不常用翻译用 loaders 异步加载
  6. 组件库或大型组件使用业务组件接入方式
  7. 临时弹窗/对话框文案优先考虑局部翻译
  8. 共享的局部翻译用 LocalI18nProvider 提供给子孙组件
  9. 纯 JS 工具函数用 createLocalTranslator