Vue@3&Vue-i18n@9で<select>を使って言語切り替えをする方法

44086 ワード

はじめに(やりたいことと経緯)

ウェブページなどの多言語対応をしたい。そのときに、以下の2点が要件。

  • ブラウザの自然言語の種類を取得して、それに合わせて情報を表示する。
  • 利用者が<select>を使って自然言語の種類を切り替えて表示する。

今まで、i18next[1]というフレームワークを使っていたが、選択された言語(日本語や英語など)をHTMLのタグに流し込むときにdocument.getElementById("hoge").textContent = "This text is different!";を大量に書かないといけなくて面倒だし可読性が落ちます。もちろん、賢く工夫をして書けば、もう少し綺麗に単純に書くことは出来るけど、良い機会なのでVue.js[2]を使ったVue I18n[3]を使ってみようと思いました。

コード&実行結果

環境

利用している環境です。今回のコードはVue Ver.2 と Vue I18n Ver.8では上手く動かないはずです。Vue Ver.2&Vue I18n Ver.8の書き方は調べれば結構出てくるので、そちらを参考にしてください。

  • Vue Ver.3
  • Vue I18n Ver.9

ブラウザの自然言語の種類を取得して、それに合わせて情報を表示する

Vue I18nの公式ガイド[4]とリポジトリのIssue[5]を参考に、以下のコードを書いて動かしました。

test.html
<!DOCTYPE html>
<html lang="en">
  <head>
    <meta charset="utf-8" />
    <title>Vue I18n Test</title>
    <script src="https://cdn.jsdelivr.net/npm/vue@3"></script>
    <script src="https://unpkg.com/vue-i18n@9"></script>
  </head>
  <body>
    <div id="yokan">
      <span> {{ $t("message.hello") }} </span>
    </div>
  </body>
  <script>
    /**
     * ブラウザの設定言語を取得
     */
    const langBrowser = navigator.language.split("-")[0];
    console.log("使用されている言語は" + langBrowser + "です。");

    const messages = {
      en: {
        message: {
          hello: "Hi!",
        },
      },
      ja: {
        message: {
          hello: "やあ!",
        },
      },
    };

    const i18n = VueI18n.createI18n({
      locale: langBrowser,
      fallbackLocale: "en",
      messages,
    });

    const app = Vue.createApp({});
    app.use(i18n);
    app.mount("#yokan");
  </script>
</html>

実行結果はこんな感じ
browser-lang-detect

利用者が<select>を使って自然言語の種類を切り替えて表示する

後述しますが、これが少し、困りました。とりあえず、確実に動くであろうコードと、その実行結果を以下に貼り付けます。2つ方法があり、正攻法と思われるものから貼り付けます。実行結果は殆ど同じになるので、ひとつだけ最後に貼っています。

Vue.jsの双方向バインディングを利用した方法

Vue I18nのガイド[6]をベースに、Vue.jsの双方向バインディング[7]を利用したコードです。こちらが正攻法かと思われます。

test.html
<!DOCTYPE html>
<html lang="en">
  <head>
    <meta charset="utf-8" />
    <title>Vue I18n Test</title>
    <script src="https://cdn.jsdelivr.net/npm/vue@3"></script>
    <script src="https://unpkg.com/vue-i18n@9"></script>
  </head>
  <body>
    <div id="yokan">
      <span> {{ $t("message.hello") }} </span>
      <select
        v-model="selected"
        name="lang"
        class="legal-item"
        id="select-lang"
        v-on:change="switchLang"
      >
        <option value="en">English</option>
        <option value="ja">日本語</option>
      </select>
    </div>
  </body>
  <script>
    /**
     * ブラウザの設定言語を取得
     */
    const langBrowser = navigator.language.split("-")[0];
    console.log("使用されている言語は" + langBrowser + "です。");

    const binding = {
      data() {
        return {
          selected: langBrowser,
        };
      },
      methods: {
        switchLang() {
          this.$i18n.locale = this.selected;
        },
      },
    };

    const messages = {
      en: {
        message: {
          hello: "Hi!",
        },
      },
      ja: {
        message: {
          hello: "やあ!",
        },
      },
    };

    const i18n = VueI18n.createI18n({
      locale: langBrowser,
      fallbackLocale: "en",
      messages,
    });

    const app = Vue.createApp(binding);
    app.use(i18n);
    const vm = app.mount("#yokan");
  </script>
</html>

<select>をイベントハンドラで監視して、変更時にlocaleを変更

こちらはVue I18nのガイド[6:1]をベースに、stack overflowの記事[8]を参考に書きました。
下のコードにしれっとコメントで書いていますが、VueとVue I18nの設定は、今回の紹介したコードと全く同じなのに、僕の場合

vm.$forceUpdate(); // なぜか必要な場合と、不要な場合がある。

これがないと上手く反映されませんでした。vm.$forceUpdate();が必要な時と不要な時の違いとしては、必要な時の環境では使用しているVueとVue I18nはnpmで引っ張ってきた*.prod.jsであるということです。もしかしたら、そういう仕様なのかもしれません。
なかなか、思うような参考記事が見つからず、苦労しました。

test.html
<!DOCTYPE html>
<html lang="en">
  <head>
    <meta charset="utf-8" />
    <title>Vue I18n Test</title>
    <script src="https://cdn.jsdelivr.net/npm/vue@3"></script>
    <script src="https://unpkg.com/vue-i18n@9"></script>
  </head>
  <body>
    <div id="yokan">
      <span> {{ $t("message.hello") }} </span>
    </div>
    <select name="lang" class="legal-item" id="select-lang">
      <option value="en">English</option>
      <option value="ja">日本語</option>
    </select>
  </body>
  <script>
    /**
     * ブラウザの設定言語を取得
     */
    const langBrowser = navigator.language.split("-")[0];
    if (langBrowser === "en" || langBrowser === "ja")
      document.getElementById("select-lang").value = langBrowser;
    else document.getElementById("select-lang").value = "en";
    console.log("使用されている言語は" + langBrowser + "です。");

    const messages = {
      en: {
        message: {
          hello: "Hi!",
        },
      },
      ja: {
        message: {
          hello: "やあ!",
        },
      },
    };

    const i18n = VueI18n.createI18n({
      locale: langBrowser,
      fallbackLocale: "en",
      messages,
    });

    const app = Vue.createApp({});
    app.use(i18n);
    const vm = app.mount("#yokan");

    /**
     * <select>のイベントハンドラ
     */
    document.getElementById("select-lang").addEventListener("change", () => {
      vm.$i18n.locale = document.getElementById("select-lang").value;
      vm.$forceUpdate(); // なぜか必要な場合と、不要な場合がある。
      console.log(
        "表示言語を" +
          document.getElementById("select-lang").value +
          "に変更しました。"
      );
    });
  </script>
</html>

実行結果

Select language

さいごに

双方向バインディングを使った方法は、手元の環境では問題なく動いたので、こちらで書いたら良いと思います。
7年前ぐらいに、Polymer + Typescriptでフレームワークのバージョンアップや整合性で痛い目にあって以来、やはり基本の基本で動くものが不変で安心だと思ってしまい、フレームワークは気安く使わずWebの標準規格(HTML Living standard、CSS3、Javascript、Web Componentsなどブラウザだけで動くもの)を使おうと決めていましたが、最近は利用者が多く安定したフレームワークも多いし、この機会に慣れて使ってみようと思います。

脚注
  1. i18next documentation https://www.i18next.com/ (2022-04-22閲覧) ↩︎

  2. Vue.js(Ver.3) 日本語 https://v3.ja.vuejs.org ↩︎

  3. Vue I18n 日本語 https://vue-i18n.intlify.dev/ja/index.html (2022-04-22閲覧) ↩︎

  4. はじめよう / Vue I18n https://vue-i18n.intlify.dev/ja/guide/#javascript (2022-04-22閲覧) ↩︎

  5. Default locale based on browser settings? / (kazupon /
    vue-i18n) Issue https://github.com/kazupon/vue-i18n/issues/220 (2022-04-22閲覧) ↩︎

  6. Scope and Locale Changing / Vue I18n https://vue-i18n.intlify.dev/guide/essentials/scope.html#global-scope-1 (2022-04-22閲覧) ↩︎ ↩︎

  7. ユーザー入力の制御 / Vue.js https://v3.ja.vuejs.org/guide/introduction.html#%E5%AE%A3%E8%A8%80%E7%9A%84%E3%83%AC%E3%83%B3%E3%82%BF%E3%82%99%E3%83%AA%E3%83%B3%E3%82%AF%E3%82%99 (2022-04-22閲覧) ↩︎

  8. How can I translate app without reloading page in vue.js? / adib on stack overflow https://stackoverflow.com/questions/55361211/how-can-i-translate-app-without-reloading-page-in-vue-js (2022-04-22閲覧) ↩︎