2019/03/19

Transifexとvue-i18nで国際化対応のコラボレーション環境を構築する

国内向けのサービスであっても在日外国人が使うこともある。そういった背景から昨今、国際化対応(多言語化対応)の重要度が増している。とはいえフロントエンドエンジニアがウェブアプリケーションを開発しながらメッセージ翻訳を行うには限界がある。できれば翻訳作業はローカリゼーションチームに任せたい。

しかし、ローカリゼーションチーム(非エンジニア)にgitを使って言語ファイルをダウンロード、JSONやYamlファイルを見ながら翻訳、終わったらPull Requestを作ってもらうというのは少し酷だろう。もちろんJSON→Excel→翻訳→JSONなんてことはしたくない。

そこで当記事ではTransifexとvue-i18nを使って、国際化対応におけるフロントエンドチームとローカリゼーションチームのコラボレーション環境の構築方法について紹介する。あわせて実運用で得たノウハウを共有しようと思う。
また、当記事ではNuxt.jsをメインに紹介するが、Vue.jsでも同様のことはできるので読み替えてほしい。

環境は以下の通り。
  • macOS Mojave
  • Nuxt.js@2.4.5 / Vue.js@2.6.8
  • vue-i18n@8.8.2
  • transifex-client@0.13.6

Transifex とは



Transifexはウェブ上で翻訳やレビューをしたり、翻訳したメッセージをJSONやYaml、XMLなどの形式でダウンロードしたりできる翻訳プラットフォームだ。Python製の公式クライアントも提供されているので、コマンドラインから言語ファイルのアップロード、ダウンロードができる。

弊社では以下のようなフローで翻訳作業を行っている。
  1. フロントエンド: キーワードと日本語メッセージを追加
  2. ローカリゼーション: Trasifex上で翻訳
  3. フロントエンド: 言語ファイルをダウンロード&コミット
  4. フロントエンド: デプロイ


Transifexはオープンソースプロジェクトなら無料で利用することもできる。仕事では有料プランを使っているが割と良い値段で、最安プランで年間契約なら月$139、月間契約なら月$179する。



vue-i18n とは


vue-i18nは、Vue.js用の国際化対応プラグインで、日本のVue.jsコアチームのかずぽんさんが開発されている。以下のようにJSON形式のメッセージで用意するだけで、簡単に国際化対応できる。
{
  "ja": {
    "Hello": "こんにちは"
  },
  "en": {
    "Hello": "Hello"
  }
}
<p>{{ $t('Hello') }}</p>






なぜnuxt-i18nではなくvue-i18nを選んだか


Nuxt.jsで国際化する方法を調べていたところ、nuxt-i18nというプラグインを見つけた。nuxt-i18nは、vue-i18nをNuxt用にラップしたもので、ちょっとした設定だけで自動ルーティングや言語設定を変更したときのCallbackなどの機能が使えるようになる。

しかし、nuxt-i18nは採用しなかった。

理由は、自動ルーティングだ。いろいろやってくれるのは便利なのだが、言語を切り替えるとURLもexample.com/ja/docsからexample.com/en/docsのように変わってしまう。また直接this.$i18n.localeを編集しても、ページが切り替わると元の言語に戻ってしまう。

今回はURLを変更せずに、JavaScript内部に持ったlocaleの値によって表示言語を切り替えたかったので、よりシンプルに使えるvue-i18nを採用した。



Transifexのセットアップ


国際化対応のコラボレーション環境の構築方法について解説する。

アカウント登録し、プロジェクトを作成する

Transifexのサイト(www.transifex.com)にアクセスし、Try for freeからアカウントを作成する。


ダッシュボードを表示し、左下の「プロジェクトを作成」ボタンをクリックする。1プロジェクトが1つの言語ファイルを管理するイメージだ。1プロジェクト、1アプリケーションという使い方が楽だろう。もちろんすべてを一元管理して複数アプリケーションで利用することもできるがオススメはしない。


次にプロジェクトの情報を入力する。




Transifex Clientをインストールとセットアップをする

Pythonが入っている環境でpipやeasy_installを使ってインストールする。
$ pip install transifex-client
# or
$ easy_install transifex-client

# バージョン確認
$ tx --version
0.13.4, py 2.7, x86_64

コマンドラインから言語ファイルをpush/pullできるようにするため、ユーザーのホームディレクトリに.transifexrcというファイルを作成する。このファイルの中にユーザー名やパスワードを記載しておく。

中身は以下のとおり。
[https://www.transifex.com]
api_hostname = https://api.transifex.com
hostname = https://www.transifex.com
username = {YOUR_USERNAME}
password = {YOUR_PASSWORD}
token =


言語ファイルをアップロードする

言語ファイルをアップロードする前に、プロジェクトディレクトリにいくつかファイルを作成する。
./
 ├ .tx
 |  └ config  // 言語ファイルのフォーマットや場所を指定する
 └ assets
     └ locales
         └ ja_JP.yaml  // 日本語ファイル

まずは.tx/configから説明する。ここにはTransifex上にあるどのプロジェクトを使うかを指定する。先ほどTransifex上で作成したプロジェクトを指定することになる。また、言語ファイルのパスやファイルフォーマットなどを指定する。
[main]
host = https://www.transifex.com

[your-project.ja_JP]
file_filter = assets/locales/<lang>.yaml // 多言語化されたファイル(英語とか)
source_file = assets/locales/ja_JP.yaml  // 日本語ファイル
source_lang = ja_JP               // プロジェクトのメイン言語
type = YAML_GENERIC               // 言語ファイルのフォーマット

Transifexはかなりの数のファイルフォーマットに対応しているので、詳細は公式ドキュメントを参照してください。


ファイル形式はYAML_GENERICを使う。

以前はJSONを使っていたのだが、毎回ダブルクォーテーションで括る必要があること、言語ファイル内にコメントが書けないことなどの不満があった。それを解消するために今回はYAML_GENERICを選択した。

次に言語ファイルをキーワードとメッセージ(Key-Value)のセットで作成する。
Transifex、vue-i18nともに階層構造に対応しているので、$t('parent.child')のような書き方も可能だ。
# word
名前: 名前
作成日時: 作成日時

# page
'{0}作成': '{0}作成'
'{0}一覧': '{0}一覧'

# action
操作: 操作
追加: 追加
編集: 編集
削除: 削除
キャンセル: キャンセル
'{0}の追加': '{0}の追加'
'{0}の編集': '{0}の編集'
'{0}の削除': '{0}の削除'

# confirm
'{0}を{1}しますか?': '{0}を{1}しますか?'

# info
'{0}を作成しました': '{0}を作成しました'

# error
'{0}を作成できませんでした': '{0}を作成できませんでした'


ちなみに言語ファイルのキーワードは母国語(日本語)にするのがオススメだ。
英語のキーワードは考えるときに開発の手が止まってしまう。そのため、英語に関してはローカリゼーションチームに一任し、フロントエンドエンジニアは開発に注力するため日本語キーワードを使うようにした。またコード内にメッセージが現れたときも日本語になっているのですぐに読むことができる。


ここまでできたらtxコマンドを使って言語ファイルをTransifexにアップロードする。
# ソールファイルのみをアップロード
$ tx push -s

tx pushを使うときはソールファイルだけどアップロードする-sオプション(--source)を常につけておいたほうが良い。sオプションをつけずにpushすると、他言語ファイルのメッセージが翻訳済みとしてアップロードされてしまうためだ。

またTransifexはコンフリクトを通知してくれない。そのため、古い言語ファイルでpushしてしまうと新しいメッセージなどが消えてしまう。このあたりは開発チームでのルール決めが必要だろう。弊社では「キーワード追加前に必ずpullする。追加したらすぐpushする。」という簡単なルールを決めている。


日本語→英語に翻訳する

Transifex上で日本語→英語の翻訳作業を行う。

翻訳フローは以下のような感じ。
  1. 翻訳先の言語を選択する(Japanese → English)
  2. 翻訳したいキーワードを選択する
    • 絞り込み機能を使うことで、キーワード検索、翻訳後の単語検索、未翻訳キーワード検索などができる
  3. 右ペインに翻訳結果を入力する
    • 翻訳が進んでいくと、画面右下の「提案」や「用語集」などから半自動で翻訳できる
    • 履歴管理もされるので誰がどういった翻訳したかもわかる
  4. 「変更を保存」ボタンを押して保存する
  5. 必要に応じてレビューボタンを押す
    • Transifexの機能で、未レビューの言語はダウンロードしないなどができる



言語ファイルをダウンロードする

txコマンドを使って、Transifex上で翻訳したファイルを取得する。
# 言語ファイルを取得する
$ tx pull

# もしエラーが出た場合は強制取得する 
$ tx pull -sf

# ファイルフォーマットにYAMLを選択している場合
$ tx pull --mode sourceastranslation

ファイルフォーマットにJSONを選択している場合は問題ないのだが、YAMLなどを選択している場合は未翻訳キーワードが空で取得されてしまう。未翻訳キーワードは日本語のまま取得したい場合はsourceastranslationモードを使う必要がある。

ダウンロードが終わると.tx/configfile_filterで指定したパスに言語ファイル(en_US.yaml)が作成される。
# word
名前: Name
作成日時: Created at

# page
'{0}作成': 'Create {0}'
'{0}一覧': 'List of {0}'

# action
操作: Operation
追加: Add
編集: Edit
削除: Delete
キャンセル: Cancel
'{0}の追加': 'Add {0}'
'{0}の編集': 'Edit {0}'
'{0}の削除': 'Delete {0}'

# confirm
'{0}を{1}しますか?': '{0}を{1}しますか?'

# info
'{0}を作成しました': '{0}を作成しました'

# error
'{0}を作成できませんでした': '{0}を作成できませんでした'



vue-i18nを使って国際化対応する


今回はNuxt.jsの設定方法を紹介するが、Vue.jsの場合はplugins/i18n.jsの設定をmain.jsに書くことで対応できる。

# vue-i18nをインストールする
$ npm i vue-i18n

# yamlをロードするため
$ npm i -D js-yaml-loader

plugins/i18n.jsを作成し、初期化を行う。
import Vue from 'vue'
import VueI18n from 'vue-i18n'
import ja from '~/assets/locales/ja_JP.yaml'
import en from '~/assets/locales/en_US.yaml'
 Vue.use(VueI18n)
 export default ({ app }) => {
  app.i18n = new VueI18n({
    locale        : 'ja',
    fallbackLocale: 'ja',
    messages      : { ja, en },
  })
}

JavaScriptではYamlファイルをimportできないのでjs-yaml-loaderの設定と、plugins/i18n.jsの設定をnuxt.config.jsに書く。
// nuxt.config.js
export default {
  // 略
  build  : {
    vendor: ['vue-i18n'],
    extend(config) {
      config.module.rules.push({
        test: /\.ya?ml$/,
        use : 'js-yaml-loader',
      })
    },
  },
  // 略
  plugins: ['~plugins/i18n.js'],
  // 略
}


SFCは以下のようになる。このあたりはNuxt.js、Vue.jsともに変わらない。
<template>
  <div>
    <h1>{{ $t('{0}の追加', [ $t('名前') ]) }}</h1>

    <p>{{ $t('{0}を{1}しますか?', [ $t('名前'), $t('追加') ]) }}</p>

    <button type="submit">{{ $t('追加') }}</button>
    <button type="button">{{ $t('キャンセル') }}</button>

    <div>
      <a @click="switchLocale('ja')">日本語</a>
      <span>/</span>
      <a @click="switchLocale('en')">English</a>
    </div>
  </div>
</template>

<script>
export default {
  methods: {
    switchLocale(locale) {
      this.$i18n.locale = locale
    }
  }
}
</script>

弊社では、このようにコラボレーションしながら翻訳作業を進めている。



以上

written by @bc_rikko

1 件のコメント :

  1. more appreciating blog! Great internet site! It looks extremely good! Maintain a good job!| you are rocking man…!

    返信削除