vben admin 之语言配置、使用、切换

思路

语言导入逻辑

初始化

在 src / locales / setupI18n 内的根语言文件中 import(`./lang/${locale}.ts`)

import type { App } from 'vue';
import type { I18n, I18nOptions } from 'vue-i18n';

import { createI18n } from 'vue-i18n';
import { loadLocalePool, setHtmlPageLang, setLoadLocalePool } from './helper';
import { localeSetting } from '/@/settings/localeSetting';
import { useLocaleStoreWithOut } from '/@/store/modules/locale';

const { fallback, availableLocales } = localeSetting;

export let i18n: ReturnType<typeof createI18n>;

async function createI18nOptions(): Promise<I18nOptions> {
  // 使用pinia中的defineStore创建store
  const localeStore = useLocaleStoreWithOut();
  // 获取当前语言环境locale
  const locale = localeStore.getLocale;
  // 导入对应文件的语言包,此文件会导入对应语言下的所有文件。然后提取其中的message
  const defaultLocal = await import(`./lang/${locale}.ts`);
  const message = defaultLocal.default?.message ?? {};

  // 将html文件的lang设置为所选择的语言
  setHtmlPageLang(locale);
  // 设置loadLocalPool为对应的语言:'zh_CN' | 'en' | 'ru' | 'ja' | 'ko'
  setLoadLocalePool((loadLocalePool) => {
    loadLocalePool.push(locale);
  });

  return {
    legacy: false,
    locale,
    fallbackLocale: fallback,
    messages: {
      [locale]: message,
    },
    availableLocales: availableLocales,
    sync: true, //If you don’t want to inherit locale from global scope, you need to set sync of i18n component option to false.
    silentTranslationWarn: true, // true - warning off
    missingWarn: false,
    silentFallbackWarn: true,
  };
}

// setup i18n instance with glob
export async function setupI18n(app: App) {
  const options = await createI18nOptions();
  i18n = createI18n(options) as I18n;
  app.use(i18n);
}

会异步导入 src / locales / lang / ${locale}.ts 文件语言包。

举例说明,当import zh-CN.ts 文件时,最终export的对象中的message为一个对象,包含了自定义的字段和antd库中的字段。zh-CN.ts文件内代码如下:

import { genMessage } from '../helper';
import antdLocale from 'ant-design-vue/es/locale/zh_CN';

// import.meta.glob函数从文件中导入多个模块,匹配到的文件都是懒加载的,通过动态导入实现,并会在构建时分离为独立的chunk
// 如果倾向于直接引入所有的模块(例如依赖于这些模块中的副作用首先被应用),可以传入{eager:true}作为第二个参数
// import.meta.glob返回一个对象 {./zh-CN/sys.ts, ./zh-CN/commom.ts......}
const modules = import.meta.glob('./zh-CN/**/*.ts', { eager: true });

// 输出所有自定义和antd库中的对应语言字段
export default {
  message: {
    ...genMessage(modules as Recordable<Recordable>, 'zh-CN'),
    antdLocale,
  },
};

此时 src / locales / lang / zh-CN 文件夹下的所有ts文件内容被转化为一个对象输出,因此在使用时直接使用  t('sys.login.signInFormTitle') 进行获取

我们来看一下lang文件夹的文件目录,这样做的好处是更容易管理大型项目的多语言,如果不需要分模块划分,可以直接自己手动导入即可:

|-- lang

    |-- en.ts

    |-- zh_CN.ts

    |-- en

    |   |-- common.ts

    |   |-- component.ts

    |   |-- layout.ts

    |   |-- sys.ts

    |   |-- routes

    |       |-- basic.ts

    |       |-- dashboard.ts

    |       |-- demo.ts

    |-- zh-CN

        |-- common.ts

        |-- component.ts         # 组件相关

        |-- layout.ts                 # 布局相关

        |-- sys.ts                     # 系统页面相关

        |-- antdLocale

        |   |-- DatePicker.ts

        |-- routes                     # 路由菜单相关

            |-- basic.ts

            |-- dashboard.ts

            |-- demo.ts

具体文件内容转化为对象的过程通过 src / locales / lang / helper.ts 模块实现,代码如下:

import type { LocaleType } from '/#/config';

import { set } from 'lodash-es';

export const loadLocalePool: LocaleType[] = [];

export function setHtmlPageLang(locale: LocaleType) {
  document.querySelector('html')?.setAttribute('lang', locale);
}

export function setLoadLocalePool(cb: (loadLocalePool: LocaleType[]) => void) {
  cb(loadLocalePool);
}

// 将对应语言文件下的ts的内容转换为对象形式
export function genMessage(langs: Record<string, Record<string, any>>, prefix = 'lang') {
  const obj: Recordable = {};
  Object.keys(langs).forEach((key) => {
    // 各ts文件中的内容,对象形式
    const langFileModule = langs[key].default;
    // 获取文件名,不要后一个replace效果一样
    // let fileName = key.replace(`./${prefix}/`, '').replace(/^\.\//, '');
    let fileName = key.replace(`./${prefix}/`, '');
    // 去掉.ts后缀
    const lastIndex = fileName.lastIndexOf('.');
    fileName = fileName.substring(0, lastIndex);
    // 获取文件夹名和文件名
    const keyList = fileName.split('/');
    const moduleName = keyList.shift();
    const objKey = keyList.join('.');

    // 将文件内容赋给对象
    if (moduleName) {
      if (objKey) {
        // 若存在objKey,则该ts文件是文件夹下的文件,则需要嵌套赋值
        // 首先设置obj中的moduleName,如果已经存在,则obj.moduleName=已经存在的obj[moduleName],若不存在,则赋为空对象
        // 然后将对应文件夹下的文件内容赋给obj.moduleName.objKey
        set(obj, moduleName, obj[moduleName] || {});
        set(obj[moduleName], objKey, langFileModule);
      } else {
        // 若不存在objKey,则该ts文件是第一层文件夹下的文件,直接将langFileModule赋给moduleName即可
        set(obj, moduleName, langFileModule || {});
      }
    }
  });
  return obj;
}

从 src / locales / lang / ${locale}.ts 文件可以看到,最终export的对象中的message为一个对象,包含了自定义的字段和antd库中的字段。

src / locales 文件夹:封装了有关语言设置的各种定义及操作

|-- locales

    |-- helper.ts                            # setHtmlPagePool / setLoadLocalePool / genMessage

    |-- setupI18n.ts                      # createI18nOptions / setupI18n

    |-- useLocale.ts                     # setI18nLanguage / useLocale / changeLocale / getLocale..

    |-- en文件夹                          # 各展示变量对应的英文

    |-- zh_CN文件夹                   # 各展示变量对应的中文

如何进行使用

项目将vue-i18n进行了二次封装。

在需要使用的组件中引入项目自带的 useI18n 注意不要引入 vue-i18n 的 useI18n

import { useI18n } from '/@/hooks/web/useI18n';

const { t } = useI18n();

const title = t('components.modal.title');

useI18n的代码如下,export了二次封装的 t 函数 和 i18n 中的一些methods,所以就可以直接使用 t('components.modal.title') 来获取想要的字段:

import { i18n } from '/@/locales/setupI18n';

type I18nGlobalTranslation = {
  (key: string): string;
  (key: string, locale: string): string;
  (key: string, locale: string, list: unknown[]): string;
  (key: string, locale: string, named: Record<string, unknown>): string;
  (key: string, list: unknown[]): string;
  (key: string, named: Record<string, unknown>): string;
};

type I18nTranslationRestParameters = [string, any];

function getKey(namespace: string | undefined, key: string) {
  if (!namespace) {
    return key;
  }
  if (key.startsWith(namespace)) {
    return key;
  }
  return `${namespace}.${key}`;
}

export function useI18n(namespace?: string): {
  t: I18nGlobalTranslation;
} {
  const normalFn = {
    t: (key: string) => {
      return getKey(namespace, key);
    },
  };

  if (!i18n) {
    return normalFn;
  }

  // 这里的t为一个函数
  // function t(...args) {
  //   return wrapWithDeps(context => Reflect.apply(coreBase.translate, null, [context, ...args]), () => coreBase.parseTranslateArgs(...args), 'translate', root => Reflect.apply(root.t, root, [...args]), key => key, val => shared.isString(val));
  // }
  // methods为i18n中的一些属性和方法?
  const { t, ...methods } = i18n.global;

  const tFn: I18nGlobalTranslation = (key: string, ...arg: any[]) => {
    if (!key) return '';
    if (!key.includes('.') && !namespace) return key;
    return t(getKey(namespace, key), ...(arg as I18nTranslationRestParameters));
  };
  return {
    ...methods,
    t: tFn,
  };
}

// Why write this function?
// Mainly to configure the vscode i18nn ally plugin. This function is only used for routing and menus. Please use useI18n for other places

// 为什么要编写此函数?
// 主要用于配合vscode i18nn ally插件。此功能仅用于路由和菜单。请在其他地方使用useI18n
export const t = (key: string) => key;

如何切换语言

在 src / locales / useLocale.ts 中定义了多个语言设置函数

/**
 * Multi-language related operations
 */
import type { LocaleType } from '/#/config';

import { i18n } from './setupI18n';
import { useLocaleStoreWithOut } from '/@/store/modules/locale';
import { unref, computed } from 'vue';
import { loadLocalePool, setHtmlPageLang } from './helper';

interface LangModule {
  message: Recordable;
  dateLocale: Recordable;
  dateLocaleName: string;
}

function setI18nLanguage(locale: LocaleType) {
  const localeStore = useLocaleStoreWithOut();

  if (i18n.mode === 'legacy') {
    i18n.global.locale = locale;
  } else {
    (i18n.global.locale as any).value = locale;
  }
  localeStore.setLocaleInfo({ locale });
  setHtmlPageLang(locale);
}

export function useLocale() {
  const localeStore = useLocaleStoreWithOut();
  const getLocale = computed(() => localeStore.getLocale);
  const getShowLocalePicker = computed(() => localeStore.getShowPicker);

  const getAntdLocale = computed((): any => {
    return i18n.global.getLocaleMessage(unref(getLocale))?.antdLocale ?? {};
  });

  // Switching the language will change the locale of useI18n
  // And submit to configuration modification
  async function changeLocale(locale: LocaleType) {
    const globalI18n = i18n.global;
    const currentLocale = unref(globalI18n.locale);
    if (currentLocale === locale) {
      return locale;
    }

    if (loadLocalePool.includes(locale)) {
      setI18nLanguage(locale);
      return locale;
    }
    const langModule = ((await import(`./lang/${locale}.ts`)) as any).default as LangModule;
    if (!langModule) return;

    const { message } = langModule;

    globalI18n.setLocaleMessage(locale, message);
    loadLocalePool.push(locale);

    setI18nLanguage(locale);
    return locale;
  }

  return {
    getLocale,
    getShowLocalePicker,
    changeLocale,
    getAntdLocale,
  };
}

其中 changeLocale 函数就时进行语言切换的,分三种情况:

①所选择的语言和当前语言相同

②所选择的语言和当前语言不同,且在loadLocalePool中存在

③所选择的语言和当前语言不同,且在loadLocalePool中不存在

使用 changeLocale 函数切换语言。

import { useLocale } from '/@/locales/useLocale';

const { changeLocale } = useLocale();

changeLocale('en');

新增

语言文件

在 src/locales/lang/ 增加对应语言的文件即可

新增语言

目前项目自带的语言只有 zh_CN 和 en 两种

如果需要新增,按以下操作即可

  1. 在 src/locales/lang/ 下新增相应的语言目录及语言文件并引入 引入 ant-design-vue 和 moment 对应的语言包
  2. 在 types/config.d.ts 内加上预览类型定义
  3. 在 src/settings/localeSetting.ts 修改语言配置

实际页面中如何进行切换

项目在 src / components / Application / src 下定义了 AppLocalePicker 组件

AppLocalePicker 组件中使用了 antd 库的 DropDown 组件

并且传入了localeList进行展示,给 item 绑定的 click 事件为 handleMenuEvent,在handleMenuEvent 中调用了changeLocale函数实现语言的切换。

<template>
  <Dropdown
    placement="bottom"
    :trigger="['click']"
    :dropMenuList="localeList"
    :selectedKeys="selectedKeys"
    @menu-event="handleMenuEvent"
    overlayClassName="app-locale-picker-overlay"
  >
    <span class="cursor-pointer flex items-center">
      <Icon icon="ion:language" />
      <span v-if="showText" class="ml-1">{{ getLocaleText }}</span>
    </span>
  </Dropdown>
</template>
<script lang="ts" setup>
  import type { LocaleType } from '/#/config';
  import type { DropMenu } from '/@/components/Dropdown';
  import { ref, watchEffect, unref, computed } from 'vue';
  import { Dropdown } from '/@/components/Dropdown';
  import Icon from '@/components/Icon/Icon.vue';
  import { useLocale } from '/@/locales/useLocale';
  import { localeList } from '/@/settings/localeSetting';

  const props = defineProps({
    /**
     * Whether to display text
     */
    showText: { type: Boolean, default: true },
    /**
     * Whether to refresh the interface when changing
     */
    reload: { type: Boolean },
  });

  const selectedKeys = ref<string[]>([]);

  const { changeLocale, getLocale } = useLocale();

  const getLocaleText = computed(() => {
    const key = selectedKeys.value[0];
    if (!key) {
      return '';
    }
    // localeList:DropMenu类型数据
    // [{text: '简体中文',event: LOCALE.ZH_CN,},{text: 'English',event: LOCALE.EN_US,}]
    return localeList.find((item) => item.event === key)?.text;
  });

  watchEffect(() => {
    selectedKeys.value = [unref(getLocale)];
  });

  async function toggleLocale(lang: LocaleType | string) {
    await changeLocale(lang as LocaleType);
    selectedKeys.value = [lang as string];
    props.reload && location.reload();
  }

  function handleMenuEvent(menu: DropMenu) {
    if (unref(getLocale) === menu.event) {
      return;
    }
    toggleLocale(menu.event as string);
  }
</script>
  • 1
    点赞
  • 4
    收藏
    觉得还不错? 一键收藏
  • 1
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值