JavaScriptライブラリを使ったWebサイトのローカライズ

Quantum
Quest
Algorithms, Math, and Physics

JavaScriptライブラリを使ったWebサイトのローカライズ

本稿では、Webサイトのローカライズを簡略化するために開発した再利用可能なJavaScriptライブラリをご紹介します。以前取り組んだHTML5アプリケーションのローカライズの経験を活かし、このライブラリでは翻訳管理をシンプルにし、動的な言語切り替えを実現しています。ライブラリのアーキテクチャについて解説し、JSONファイルから翻訳データを取得してマージし、ページの内容を更新し、メタデータを扱う方法を示します。さらに、i18nextとLodashを活用することでパフォーマンスと柔軟性を高めています。また、ローカライズプロセスを自動化するために作成した補助スクリプトについてもご紹介します。具体的には、HTMLから翻訳可能なコンテンツを抽出するBashスクリプトと、LLMsを用いた自動翻訳を行うPythonスクリプトです。

このライブラリは、翻訳データを効率的に読み込み、反映させることを中心に設計されています。まず、HTML内の<meta>タグからロケールファイルへのパスを取得します。


<meta name="locale-path" content="/path/to/locales/">

この設定により、スクリプトはさまざまなプロジェクト構造で柔軟に利用することができます。ライブラリの中核となる関数loadLanguage(lang)は、翻訳データを取得・処理する役割を担っています。


  const loadLanguage = async function(lang) {
    if (!i18next.hasResourceBundle(lang)) {
      try {
        const fetchPromises = [];
        if (optionalBaseEnabled) {
          fetchPromises.push(
            fetchJson(`/data/json/loc/base/${lang}.min.json?id=fdc3af`).catch(() => {
              console.log(`Resource at /data/json/loc/base/{lang}.min.json?id=fdc3af for language {lang} was not found`);
              return null;
            })
          );
        }
        fetchPromises.push(
          fetchJson(`{localesPath}{lang}.min.json?id=fe5cf3`).catch(() => {
            console.log(`Resource at {localesPath}{lang}.min.json?id=fe5cf3 for language ${lang} was not found`);
            return null;
          })
        );
        const results = await Promise.all(fetchPromises);
        const resources = optionalBaseEnabled ? _.merge({}, results[0] || {}, results[1]) : results[0];
        if (!resources) {
          console.log(`Skipping loading language ${lang} due to missing resource.`);
          return;
        }
        Object.keys(resources).forEach(namespace => {
          i18next.addResourceBundle(lang, namespace, resources[namespace], true, true);
        });
      } catch (error) {
        console.error(`An error occurred while loading resources for lang: ${lang}`, error);
        return;
      }
    }
    await i18next.changeLanguage(lang);
    updateContent();
};

まず、この関数では、指定した言語のリソースがすでに読み込まれているかどうかを確認します。未読み込みの場合は、Promise.allを使ってベースファイルとパス固有の翻訳ファイルを同時に取得します。その後、Lodashの_.mergeを使ってこれらのデータをマージし、i18nextaddResourceBundle機能を利用してリソースを登録します。最後にupdateContent()を呼び出し、ページに翻訳を反映します。

updateContent()関数はページ上の要素に翻訳を適用する役割を担っています。例としては以下のようにメタデータを更新します。


document.documentElement.lang = i18next.t('htmlLang');
document.querySelector('meta[http-equiv="content-language"]').content = i18next.t('contentLanguage');
document.title = i18next.t('title');
document.querySelector('meta[name="description"]').content = i18next.t('description');

さらに、要素のテキストや属性を更新します。


const text = i18next.getResourceBundle(i18next.language, 'text');
Object.keys(text).forEach(key => {
    const element = document.getElementById(key);
    if (element) {
        element.innerHTML = i18next.t(`text:${key}`);
    }
});

// リンク属性を更新する場合も同様のロジックを使用

ワークフローの自動化

ローカライズ作業を効率化するために、2つの補助スクリプトを開発しました。1つ目のBashスクリプトは、HTMLからすべてのdata-local-*属性を抽出して、翻訳対象のコンテンツをリストにまとめます。たとえば、以下のようなHTMLスニペットがあるとします。


<p id="my_paragraph" data-local-text="welcomeMessage"></p>

このコードからはwelcomeMessageが抽出され、JSON翻訳ファイルのキーとして利用できるようになります。

もう1つのスクリプトはPythonで書かれており、LLMsを利用してJSONファイルのコンテンツを自動翻訳します。これにより、手動で翻訳する手間が大幅に削減されます。通常のワークフローとしては、Bashスクリプトでキーを生成し、ベースとなるen.jsonファイルを作成してから、このPythonスクリプトを使って英語から他の言語ファイルを生成します。

JSON構造の例

英語のJSONファイルの例は以下のようになります。


{
  "text": {
    "welcomeMessage": "Welcome to my website"
  },
  "links": {
    "aboutLink": {
      "href": "/about",
      "alt": "About us page"
    }
  }
}

さらに詳しい情報については、こちらでご覧いただけます。