超絶手軽に多言語対応を行う事が出来る「shutto翻訳」概要と、Nuxt.jsに導入する上での躓いたところ備忘録

はじめに

お久しぶりです! 普段は広島に住んでいるのですが、色々予定を組んで東京に着いてすぐのところで滑って転けて大腿骨の上らへんを折り入院中です!

フロント領域を主に担当しています。クロスマート株式会社のナイスガイ福留(@fal_engineer)です。

今回は弊社プロダクトにも最近導入する運びとなりました、shutto翻訳についての記事となります。

以下から弊社のエンジニアチームによって投稿している記事一覧が閲覧可能です。

xmart-techblog.hatenablog.com

ぜひ読者になるもクリックいただけると嬉しいです!

書くこと

  • shutto翻訳の概要
  • Nuxt.jsにshutto翻訳を導入する上で詰まったこと、原因と対策

書かないこと

  • Nuxt.js、vue.jsの基本的な書き方、構造

shutto翻訳とは?

shutto翻訳|さぶみっと!|株式会社イー・エージェンシー

shutto翻訳とは株式会社イー・エージェンシー によってリリースされた、JavaScript導入タグを埋め込むだけで導入可能なUI翻訳ツールです。

管理画面よりURLを登録し、設定を行うことで、サービスにアクセスしたユーザーに対しAIによって自動的に翻訳されたページを提供する事が可能となります。

またユーザーは、ページにデフォルトで表示される言語切り替えバーによって、英語・韓国語・タイ語・中国語を始めとした様々な言語への翻訳も可能です。

他に提供されている機能として、辞書登録によって意図に沿っていない翻訳がされている箇所を修正・カスタマイズ可能な「翻訳機能」、翻訳をshutto管理画面上でプロの翻訳者に依頼出来る「プロ翻訳機能」があります。

ランタイムを読み込むよう一行追加するだけでサービスを多言語対応出来る手軽さと、カスタマイズ性の高さ、管理画面の見やすさがとても魅力的なツールです。

導入方法

メールアドレスによる無料のアカウント登録を行い、管理画面より翻訳したいサービスのURLを登録します。

サイト設定画面より表示可能な導入タグをコピーし、

サービスのHTMLに貼り付けます。

  <head>
    ...
    <script src="https://d.shutto-translation.com/trans.js?id=xxxxxx"></script>
    ...
  </head>

その後、言語切り替えバーの表示設定、どの言語への翻訳を有効とするか、等の設定を必要に応じて変更することで、導入が完了します。

めっちゃ簡単でビビります

料金形態

※ 2022/11時点でのプラン表です。

www.submit.ne.jp

サイトの自動翻訳

shutto翻訳を導入している場合、管理画面からデフォルトでどの言語を表示するかの設定が可能です。

例えば以下のように設定をしている場合、

  • ブラウザや、本体の言語設定が日本語、英語、韓国語、タイ語、中国語(簡体)、中国語(繁体)の場合は自動的に翻訳して表示する
  • それ以外の場合は日本語で表示する

といった設定となります。

翻訳精度・辞書機能

基本的に翻訳精度が気になることはありませんが、おや?となることは当然有り得ます。

また、特定の文章を文ままではなく、意訳したい場合等です。

その場合は辞書機能を使い、予め設定しておいた文章を表示可能です。

その他

特定の画面に対し、画面を直接書き換えるような感覚で翻訳の辞書設定が可能だったり、

同様に、翻訳時の画像の挿し替えの設定を行う事が可能です。(英語で表示されているときには、英語用のロゴ画像に表示を差し替える、といった設定が可能です)

Nuxt.jsでshutto翻訳を利用する際の注意点

今回Nuxt.jsによって実装されている弊社プロダクト、クロスオーダーに対しshutto翻訳を導入するにあたり、躓く箇所が数点あったのでピックアップして紹介したいと思います。

言語切り替えバーのデザインをカスタムする場合、ページ遷移した時に翻訳リンクが無効になってしまう

現象

言語切り替えバーはカスタマイズが可能です。

www.submit.ne.jp

この通りに独自に言語切り替えバーをコンポーネントとして実装し、layoutファイルに配置しました。

結果、リロード後独自に作成した言語切り替えバーは期待通りの場所に表示されており、リンクをクリックすることで「日本語」⇨「英語」の翻訳が実行されました。

しかし、別の画面にページ遷移したのち、再度リンクをクリックすると、翻訳が機能しませんでした。

activeはクリックした行が有効となっているため、HTMLやCSS、js等、実装した箇所によるエラーではない事が伺えます。

原因

どうやら、shutto翻訳を利用する上で言語切り替えバーをデザインカスタマイズしてHTML上に配置する場合、画面が読み込まれたのちにdata-stt-changelang属性を持っている要素がHTML上から削除された場合動作が無効になる、という仕様があるみたいでした。 (公式ドキュメントからは見つかりませんでした。どこに上記の仕様についての記載があるのか知っている方がいらっしゃいましたらページリンクいただきたいです。)

弊社プロダクトはレンダリングSSRを採用しています。

独自実装の言語切り替えバーを配置したlayoutファイルではは<client-only>タグ内にありましたので、それ以下はCSRによってレンダリングが行われていることになります。

SPAでは画面が切り替わった際に仮想DOMを比較し、差分だけを更新するため、同一のlayout上で以下のみが変化しているはずなのに何故一度それがHTML上から消えていることになっているのか気になりました。

しかしこれは私の根本的な理解不足からなる問題でした。

Nuxt.jsの2系では、nuxt-router, Vue.routerを使ったページ遷移は全てCSRによって行われます。

CSRでは

以下が空のHTMLをキャッシュするため、layoutファイルが同一のページ間を遷移したとしてもlayoutはページ遷移中に一度完全に削除される形になります。

Nuxt.jsの排出するHTMLの構造

<html>
  <head> ... </head>
  <body>
    <div id="__nuxt">
      <div id="__layout">  <-- layoutファイルの内容はここ以下 -->
        ...
          <-- <Nuxt /> が入る -->
        ...
      </div>
    </div>
  </body>
</html>

以上の事柄から、layoutファイルに独自実装した言語切り替えバーを配置したことが原因となっていることが判明しました。

対策

対策としては二つあり、

  • ページ遷移をnuxt-router, Vue.routerでは無くaタグを使用する
  • と同階層に言語切り替えバーを配置する

となります。

弊社プロダクトでは前者はシステムの仕様上制約があるため、後者を採用しました。

Nuxt.jsではapp.htmlをプロジェクトルート(srcDirを採用している場合はsrc直下)に配置することで、Nuxtが排出するHTMLを上書きする事が可能です。

nuxtjs.org

こちらの機能を利用し、全ページ共通で独自実装の言語切り替えバーを有効化させることに成功しました。

プログラムによって制御の文字列が翻訳中に、制御不能となる

現象

<template>
  <div>
    <button @click="buttonClick()"> increment </button>
    {{ num }}
  </div>
</template>
<script>
export default {
  auth: false,
  name: 'Health',
  data() {
    return {
      num: 1,
    }
  },
  methods: {
    buttonClick: () {
      alert('increment click')
      this.num ++
    }
  }
}
</script>

雑ですが、この数値を表示している部分が、翻訳中はうまく動かない、というものです。

原因

こちらも公式ドキュメントから原因と思しき記述を見つける事が出来ませんでした。

しかしこちらについては、num変数をwatchしたところ、ボタンが押下され表示が切り替わっていなくとも、numは加算されている事がわかりました。

  watch: {
    num(n) {
      // console.log(`ちくわ大明神スレ part.${n}`)
    }
  }

よって、shuttoの翻訳APIの結果を描画している箇所の評価タイミングによるものと仮定し、dev toolsからネットワークを監視したところ、翻訳APIがいちいち飛んでいない事がわかりました。

対策

www.submit.ne.jp

公式ドキュメントにあるやり方で、jsによって表示制御される箇所を翻訳除外しました。

  <div>
    <button @click="buttonClick()"> increment </button>
    <p data-stt-ignore> {{ num }} </p>
  </div>