Vue3 + i18n 实现国际化语言切换

180 阅读2分钟

前言

项目使用 Vue3 + TS,使用 vue-i18n 实现翻译切换,实现流程为: 安装 => main.ts 引入 => 配置文件 => 使用

tips: 本文将 locale 存储于 localStorage 中

实现

  • 安装 运行 pnpn install vue-i18n
  • main.ts 引入
import { createApp } from 'vue';
import { createPinia } from 'pinia';
import type { PiniaPluginContext } from 'pinia';
import { cloneDeep } from 'lodash-es';
import router from './router';
import App from './app.vue';
import i18n from '@/i18n';
import '@/styles/global.scss';

const pinia = createPinia();
const app = createApp(App);

function resetPlugin(ctx: PiniaPluginContext) {
  const store = ctx.store;
  const initialState = cloneDeep(store.$state);
  store.$reset = () => {
    store.$patch(($state) => {
      Object.assign($state, cloneDeep(initialState));
    });
  };

  store.$destroy = () => {
    store.$dispose();
    delete pinia.state.value[store.$id];
  };
}

pinia.use(resetPlugin);

app.use(i18n).use(pinia).use(router).mount('#app');

  • 配置文件
// i18n/locales/en_US.ts
export default {
    error: {
    404: 'Resource not found',
  },
}

// i18n/locales/zh-CN.ts
export default {
    error: {
    404: '资源未找到',
  },
}
import { type I18n, createI18n } from 'vue-i18n';
import type { AvailableLocale, MessageSchema } from '@/models/i18n';
import { STORAGE_KEYS, I18N_MESSAGES } from '@/constants';

function saveLocaleToLocalStorage(val: AvailableLocale) {
  localStorage.setItem(STORAGE_KEYS.LOCALE_KEY, val);
}

/* 从浏览器偏好设置读取的语言可能是 en, en-US, zh, zh-CN, zh-HK 等格式、最终返回的是"zh-CN"形式(这与message的设置有关) */
function getSimilarLocale(val: unknown) {
  if (typeof val !== 'string') {
    return;
  }

  // 校验语言格式
  const localeSplitArr = val.split('-');
  const lang = localeSplitArr[0];
  const country = localeSplitArr[localeSplitArr.length - 1];
  // lang 格式 e.g. en, zh
  if (!country) {
    // 根据 lang 找近似语言
    for (const key in I18N_MESSAGES) {
      const [itemLang] = key.split('-');
      if (itemLang.toLowerCase() === lang.toLowerCase()) {
        return key as AvailableLocale; // 推断不了字面量, 只能断言
      }
    }
    return;
  }

  // lang-country 格式, e.g. en-US, zh-CN
  for (const key in I18N_MESSAGES) {
    if (key.toLowerCase() === val.toLowerCase()) {
      return key as AvailableLocale; // 推断不了字面量, 只能断言
    }
  }
}

/* 获取默认语言:优先从缓存读,其次从浏览器偏好设置读 */
function getDefaultLocale() {
  // 从缓存读
  const cacheLocale = getSimilarLocale(localStorage.getItem(STORAGE_KEYS.LOCALE_KEY));
  if (cacheLocale) {
    return cacheLocale;
  }

  let result: AvailableLocale = 'en-US';
  // 从浏览器偏好设置读
  const browserLocale = getSimilarLocale(navigator.language);
  if (browserLocale) {
    result = browserLocale;
  }
  // 保存到缓存
  saveLocaleToLocalStorage(result);
  return result;
}
const defaultLocale = getDefaultLocale();
const i18n = createI18n<[MessageSchema], AvailableLocale>({
  locale: defaultLocale, // 默认使用的语言
  fallbackLocale: defaultLocale, // 未找到对应语言时使用的语言
  legacy: true, // 启用传统模式,这意味着插件将使用旧版的 Vue 2 API。
  globalInjection: false, // 是否将 $i18n 注入到全局 Vue 实例中
  fallbackRoot: true, // 如果未找到翻译,是否回退到根语言
  messages: I18N_MESSAGES, // 语言包
}) as I18n<typeof I18N_MESSAGES, {}, {}, AvailableLocale, true>; // createI18n 返回值类型推断有问题, 必须用断言

export const { t } = i18n.global;

/* 切换语言 */
export function toggle(val: AvailableLocale) {
  if (val === i18n.global.locale) {
    return;
  }
  saveLocaleToLocalStorage(val);
  i18n.global.locale = val;
  location.reload();
}

export const languages: Record<AvailableLocale, string> = {
  'en-US': 'English',
  'zh-CN': '简体中文',
};

export default i18n;

  • 使用
    • 正常使用
<script lang="ts" setup>
import { onMounted } from 'vue';
import { t } from '@/i18n';

onMounted(() => {});
</script>

<template>
  <div class="not-found-page">
    404({{ t('error.404') }})
  </div>
</template>
    • 语言切换 通过 i18n.global.locale 切换
/* 切换语言 */
export function toggle(val: AvailableLocale) {
  if (val === i18n.global.locale) {
    return;
  }
  saveLocaleToLocalStorage(val);
  i18n.global.locale = val;
  location.reload();
}
``