如何更改 Vue 文件中 VueI18n 实例的语言环境,并将其应用于应用程序中的所有其他 Vue 文件?

How can I change the locale of a VueI18n instance in a Vue file, and have it apply to all other Vue files in the application?

我目前正在尝试实现一项功能,用户可以从“设置”页面 (SettingsDialog.vue) 的下拉菜单中 select 一种语言,更新所有文本以匹配新语言.此应用程序有多个 Vue 文件,例如 MenuBar.vue、HelpDialog.vue,每个文件都来自 translation.ts 的英文翻译。但是,我注意到 select 从下拉菜单中选择一种语言只会更改我的 SettingsDialog.vue 文件中的元素,而不是我拥有的所有其他 Vue 文件。

我尝试使用 Vue-I18n 文档实现在文件中全局更改语言环境。在 select 在 SettingsDialog.vue 中使用一种语言,将我在 translation.ts 中的英语翻译应用到菜单栏、帮助页面等之后,我期望整个应用程序的区域设置发生变化。发生了什么来自 translation.ts 的翻译只适用于 SettingsDialog.vue 页面,其他地方没有。

我想补充一下这是一个 Electron 应用程序,项目中的 Vue 文件使用 Quasar 会有所帮助。每个文件都有正确的导入语句。

main.ts:

// ...
window.datalayer = [];

const i18n = createI18n({
  legacy: false,
  locale: "",
  messages,
});

createApp(App)
  .use(store, storeKey)
  .use(router)
  .use(
    createGtm({
      id: process.env.VUE_APP_GTM_CONTAINER_ID ?? "GTM-DUMMY",
      vueRouter: router,
      enabled: false,
    })
  )
  .use(Quasar, {
    config: {
      brand: {
        primary: "#a5d4ad",
        secondary: "#212121",
      },
    },
    iconSet,
    plugins: {
      Dialog,
      Loading,
    },
  })
  .use(ipcMessageReceiver, { store })
  .use(markdownItPlugin)
  .use(i18n)
  .mount("#app");

SettingsDialog.vue

// ...
            <!-- Language Setting Card -->
            <q-card flat class="setting-card">
              <q-card-actions>
                <div id="app" class="text-h5">{{ $t("言語") }}</div>
              </q-card-actions>
              <q-card-actions class="q-px-md q-py-sm bg-setting-item">
                <div id="app">{{ $t("言語を選択する") }}</div>
                <q-space />
                <q-select
                  filled
                  v-model="locale"
                  dense
                  emit-value
                  map-options
                  options-dense
                  :options="[
                    { value: 'ja', label: '日本語 (Japanese)' },
                    { value: 'en', label: '英語 (English)' },
                  ]"
                  label="Language"
                >
                  <q-tooltip
                    :delay="500"
                    anchor="center left"
                    self="center right"
                    transition-show="jump-left"
                    transition-hide="jump-right"
                  >
                    Test
                  </q-tooltip>
                </q-select>
              </q-card-actions>
            </q-card>
// ...
<script lang="ts">
import { useI18n } from "vue-i18n";
// ...
  setup(props, { emit }) {
    const { t, locale } = useI18n({ useScope: "global" });
// ...
    return {
      t,
      locale,
      // ...
    };

MenuBar.vue

<template>
  <q-bar class="bg-background q-pa-none relative-position">
    <div
      v-if="$q.platform.is.mac && !isFullscreen"
      class="mac-traffic-light-space"
    ></div>
    <img v-else src="icon.png" class="window-logo" alt="application logo" />
    <menu-button
      v-for="(root, index) of menudata"
      :key="index"
      :menudata="root"
      v-model:selected="subMenuOpenFlags[index]"
      :disable="menubarLocked"
      @mouseover="reassignSubMenuOpen(index)"
      @mouseleave="
        root.type === 'button' ? (subMenuOpenFlags[index] = false) : 
        undefined
      "
    />
// ...
<script lang="ts">
import { defineComponent, ref, computed, ComputedRef, watch } from "vue";
import { useStore } from "@/store";
import MenuButton from "@/components/MenuButton.vue";
import TitleBarButtons from "@/components/TitleBarButtons.vue";
import { useQuasar } from "quasar";
import { HotkeyAction, HotkeyReturnType } from "@/type/preload";
import { setHotkeyFunctions } from "@/store/setting";
import {
  generateAndConnectAndSaveAudioWithDialog,
  generateAndSaveAllAudioWithDialog,
  generateAndSaveOneAudioWithDialog,
} from "@/components/Dialog";
import { useI18n } from "vue-i18n";
import messages from "../translation";

type MenuItemBase<T extends string> = {
  type: T;
  label?: string;
};

export type MenuItemSeparator = MenuItemBase<"separator">;

export type MenuItemRoot = MenuItemBase<"root"> & {
  onClick: () => void;
  subMenu: MenuItemData[];
};

export type MenuItemButton = MenuItemBase<"button"> & {
  onClick: () => void;
};

export type MenuItemCheckbox = MenuItemBase<"checkbox"> & {
  checked: ComputedRef<boolean>;
  onClick: () => void;
};

export type MenuItemData =
  | MenuItemSeparator
  | MenuItemRoot
  | MenuItemButton
  | MenuItemCheckbox;

export type MenuItemType = MenuItemData["type"];

export default defineComponent({
  name: "MenuBar",

  components: {
    MenuButton,
    TitleBarButtons,
  },

  setup() {
    const { t } = useI18n({
      messages,
    });
  // ...
  };
    const menudata = ref<MenuItemData[]>([
      {
        type: "root",
        label: t("ファイル"),
        onClick: () => {
          closeAllDialog();
        },
    // ...
    ]);

translation.ts

const messages = {
    en: {
        // MenuBar.vue
        ファイル: "File",
        エンジン: "Engine",
        ヘルプ: "Help",
        // SettingDialog.vue
        言語: 'Language',
        言語を選択する: 'Select Language',
        オフ: 'OFF',
        エンジンモード: 'Engine Mode',
        // HelpDialog.vue
        ソフトウェアの利用規約: 'test',
    }
};


export default messages;

也许还有更多问题,但现在我看到了两个:

  1. 您的 menudata 应该是 computed 而不是 ref。现在您正在创建一个 JS 对象并将其 label 属性 设置为 t() 调用的结果。当全局语言环境更改时,不会再次创建此对象。它仍然保持相同的值 t() 函数在唯一一次执行时返回 - 当 setup() 是 运行

// 正确

const menudata = computed<MenuItemData[]>(() => [
      {
        type: "root",
        label: t("ファイル"),
        onClick: () => {
          closeAllDialog();
        },
    // ...
    ]);

这样,每当 i18n.global.locale 发生变化时,您的 menudata 就会用新的翻译重新创建

作为替代方案,将 label 设置为键并在模板内使用 t(label)。但是 computed 是更有效的解决方案...

  1. 您不需要在每个组件中都将 messages 传递给 useI18n()。仅适用于全局实例。通过将配置对象传递到您正在创建的组件中的 useI18n() Local scope,如果您将所有翻译存储在一个全局位置,那么这毫无意义