【Rails】各マイキャラ語尾をLocalizationする考察

この記事は [#プリッカソン](https://prickathon.github.io) アドベントカレンダー14日目の記事です。
https://adventar.org/calendars/5292

## 1. 概要
近年目まぐるしい速さで多様化が進んでいるキャラクターコンテンツにおいて、聴き手や読み手の立場に深く印象をつける「語尾（ごび）」に注目した。
日本国内では「～～ザマス」「～～にゃん」などをよく耳にする。~~←←この例ちょっと古いかもしれない。~~また表題の元ネタであるプリティーシリーズ内では「～～ダヴィンチ」「～～だよん」といったマニアックな語尾も登場している。

一般的にシステムは標準語しか使用しない。語尾以前に方言も出し分けしない。
しかし、今後も多様性の広がる世の中において、これは技術検討して損のない課題といえるだろう。

## 2. 目的
個人的な都合でRails前提とさせていただく。
システム利用者に紐づくマイキャラごとに語尾が選択されている仮定。マイキャラ同士の仮想のコミュニケーション（あらかじめ用意された単語を選択して文章をつくる）の文言を語尾に応じて出し分けしたい。
利用者が直感的に標準語を入力することで、マイキャラに応じた文章を出力できる状態を目指す。

[![語尾の変換](http://www.plantuml.com/plantuml/svg/SoWkIImgAStDuIhEpimhI2nAp5L8J2x9BmekgSn9LKWiJotEpqtbidhQiVBvb1Kl6bgj57HrxHGqeXytJttSjF9nyzh7pNjq51utDZrkwdhSsFEuSSNZnhPOJUp6UUJdvEINAt0L92Pd9wQdAlXMEcSa5e4L52jKSoaeM3rkdFAuSThZfkx7ZGiVDpK_Rffi1HPkxdtSj1b9h9QBe1D03iLk81P1gBUGZKGpy77ZShWsJU9intGsL8HuQrS3rMboEQJcfG3Z2000)](http://www.plantuml.com/plantuml/svg/SoWkIImgAStDuIhEpimhI2nAp5L8J2x9BmekgSn9LKWiJotEpqtbidhQiVBvb1Kl6bgj57HrxHGqeXytJttSjF9nyzh7pNjq51utDZrkwdhSsFEuSSNZnhPOJUp6UUJdvEINAt0L92Pd9wQdAlXMEcSa5e4L52jKSoaeM3rkdFAuSThZfkx7ZGiVDpK_Rffi1HPkxdtSj1b9h9QBe1D03iLk81P1gBUGZKGpy77ZShWsJU9intGsL8HuQrS3rMboEQJcfG3Z2000)

<details><summary>UML</summary><div>

``` uml
@startuml
skinparam actorStyle awesome
:利用者: --> ([フォロー, ありがとう])
skinparam actorStyle Hollow 
package MyCharactors {
  :ござるちゃん:
  :ネコちゃん:
}
([フォロー, ありがとう]) --> :ござるちゃん:
:ござるちゃん: --> (フォローありがとうでござる)
([フォロー, ありがとう]) -->:ネコちゃん:
:ネコちゃん: --> (フォローありがとにゃ)
@enduml
```

</div></details>

## 3. 言語の違い、地域の違い、方言の違い、語尾とは
〜〜中略〜〜
システムでよく議題に上がるのは多言語化や地域の違いまでである（経験則）。
語尾はこの区分ではなく「口ぐせ」の一つとしてとらえてよいだろう。

言語：英語、日本語など国際的な言語の種類
地域：アメリカはドル、イギリスはポンドのような同じ言語でも通貨が異なるなど
方言：「ありがとう」「おおきに」のように同じ国内でも使う言葉が異なるなど
口ぐせ：「すごくいいね」「マジでいいね」年代や人柄などで言葉が若干変わるなど

[![語尾とは](http://www.plantuml.com/plantuml/svg/SoWkIImgAStDuIf8JCvEJ4zLUB6YuSMgUOyRrxridFguiKMXcaj1eIG_FRKa8xK8n042ZTGyJGWZgm34g0LYcFQdSpOydJyVhXscGyLGny300Ahhq69pKnZRmiX4uL7J6X0FOSYpQJk1pa0r1ApwSMFJuwQknuqzZnjNF6viqqI8Tu5Hx-F6rL11X8lw5pzkdF2uSJRS68JSouO5GCz29M1cDQrxtBpuSVDge8mcYaJpiiVD2r54Qp5C14e_tR0FpSI6EI3KtFouQQSc5ed6vOyRPpvkwaSn5q5ZUbj1DpeZCoKbDI_M3itlXDJhsY720wos5Zb-K0r6H1E4ZIQDY3c40IWseKSZEAM0Km-oGU1411T6w4PFILorN0wfUId0f0e0)](http://www.plantuml.com/plantuml/svg/SoWkIImgAStDuIf8JCvEJ4zLUB6YuSMgUOyRrxridFguiKMXcaj1eIG_FRKa8xK8n042ZTGyJGWZgm34g0LYcFQdSpOydJyVhXscGyLGny300Ahhq69pKnZRmiX4uL7J6X0FOSYpQJk1pa0r1ApwSMFJuwQknuqzZnjNF6viqqI8Tu5Hx-F6rL11X8lw5pzkdF2uSJRS68JSouO5GCz29M1cDQrxtBpuSVDge8mcYaJpiiVD2r54Qp5C14e_tR0FpSI6EI3KtFouQQSc5ed6vOyRPpvkwaSn5q5ZUbj1DpeZCoKbDI_M3itlXDJhsY720wos5Zb-K0r6H1E4ZIQDY3c40IWseKSZEAM0Km-oGU1411T6w4PFILorN0wfUId0f0e0)

<details><summary>UML</summary><div>

``` uml
@startuml
package 言語の違い {
  together {
    (en)
    (jp)
  }
  package 地域の違い {
    ($1,000)
    (£750)
    (824 €)
    package 方言の違い {
      (ありがとう)
      (おおきに)
      package 口ぐせ {
        package 造語 {
          (ギャル語)
          (ユメ語)
        }
        package 語尾 {
          (〜〜ッス)
          (〜〜ですわ)
        }
      }
    (ありがとう) -[hidden]> (おおきに)
    (ありがとう) -[hidden]-> 口ぐせ
    }
    ($1,000) -[hidden]> (£750)
    (£750) -[hidden]> (824 €)
    ($1,000) -[hidden]-> 方言の違い
  }
  (en) -[hidden]> (jp)
  (en) -[hidden]-> 地域の違い
}
@enduml
```

</div></detail>

上記のレイヤー分けが常に正しいとは限らないが、各言葉を使用する人口の比率で考えた場合はおおよそ当てはまるのではないかと思う。
システムの国際化対応は言語の違いを主に吸収しており、地域の違いを考慮することがまれにある程度だ。
アメリカとイギリスが「英語(en)」としてまとめられているのが良い例だろう。

## 4. 考察 I18nをそのまま使用するべきか？
I18nは国際化・多言語化(internationalization)である。前述でいう言語の違いを翻訳する機能である。 ([参考](https://railsguides.jp/i18n.html))
I18nを読み込んで(といってもrdocに記載のものが全てのようだ)どのような実現方法があるのか考察してみる。

### 4-1. 多言語化での対応  
言語ごと語尾を分けてしまおうという考えである。さすがに却下。
日本語(ja)、英語(en)、ござる(gozaru)と同列に並べるのは違和感しか無い。
また、増え続ける語尾に対して全ての翻訳ファイルを更新する必要があるため保守性が悪すぎる。

``` ruby
I18n.locale = :en
I18n.t(:thanks)
# => "Thank you"

I18n.locale = :ja
I18n.t(:thanks)
# => "ありがとう"

I18n.locale = :gozaru
I18n.t(:thanks)
# => "ありがとうでござる"
```  

### 4-2. 言語+地域での対応
同一言語で通貨単位の異なる場合などでまれによく使われる方法である。
ただ、実装方法はこれといった正解があるわけではないので、他の案のヒントとなる程度か。
翻訳ファイルの更新問題は解決していない。
アメリカ円(ドル)とイギリス円(ポンド)の例：

``` ruby
I18n.t(:product_price, price: 1_000)
# => "$1,000"

I18n.t(:product_price, price: 750, locale: 'en-GB')
# => "£750"
```

### 4-3. ユーザーによるロケールの切り替え
しばしば見かける`ja/`がURLに含まれるような例。上記4-2の地域対応と合わせて使えるか。
コントローラーの基盤に組み込めばリクエストごとに出し分け可能だが、１リクエストで複数語尾キャラの会話は難しいか。
翻訳ファイルの更新問題は解決していない。

``` ruby
# config/routes.rb
scope '/:locale' do
  resources :gobi
end

# ApplicationController
def switch_locale(&action)
  locale = params[:locale] || I18n.default_locale
  I18n.with_locale(locale, &action)
end

# GET /ja/gobi?key=thanks
# => "ありがとう"

# GET /ja-gozaru/gobi?key=thanks
# => "ありがとうでござる"
```

### 4-4. `:default`オプションを仕込む
更に進化系。
翻訳ファイルの冗長化が心配なのであれば共通化すればいいと考えた案。定義していない語尾は標準語を返すことができる。
レスポンス速度やメモリ専有率も緩和される見込み。
`ja-語尾`のロケールをうまく解釈させることができるかが鍵となるか。

``` ruby
# GET /ja-gozaru/gobi?key=thanks

gobi_locale = params[:locale]
base_locale, *_ = gobi_locale.split('-')

default = I18n.t(params[:key], locale: base_locale)
I18n.t(params[:key], locale: gobi_locale, default: default)
# => "ありがとうでござる"
```

### 4-5. `:backend`オプションを設定する
ちょっと別の案。外部DBやAPIを追加の翻訳定義として設定する方法。
翻訳時間は長くなるかもだが、語尾付けを外部に切り出せるので管理がしやすくなりそう。
別のプリッカソンメンバーが以前製作していた[形態素解析](https://syn-station.hatenablog.com/entry/2018/12/22/220050)と繋ぐなんてユメも叶う（かも）。
翻訳の幅が広がればユーザからの自由入力にも対応できるか。
DBの使用やAPI使用には別途gemが必要なので要検証。

``` ruby
# @see {https://github.com/svenfuchs/i18n-active_record}
I18n.backend = I18n::Backend::Chain.new(I18n::Backend::ActiveRecord.new, I18n.backend)
```

### 4-6. `#transliterate`
日本語対応していない模様。\_(:3」∠)_

## 5. まとめと課題
「語尾」をテーマとしていたが、方言や造語もカバーできそうな形に少々脱線した。 :bow::bow::bow:
いずれ実践編も書いてみたい。
具体的に取る手法は上記の組み合わせで考えている。実現しようとしている課題は固定文言なので語尾定義のみで十分であることと、あまねく全ての語尾を定義していたら管理がしきれないため、[人気の高いプリセット語尾](https://pripara.jp/howto/howto_21.html)をYAML定義、マイナーな語尾をDB管理にする形で実践したいのが理由である。
~~定義をユーザー任せにもできるし~~

``` yaml:ja-***.yml
# ja.yml
ja:
  thanks: ありがとう
  sorry: ごめんね
  see_you_tomorrow: またあした

# ja-pri.yml
ja-pri:
  thanks: ありがとうぷり
  sorry: ごめんぷり
  see_you_tomorrow: またあしたぷり
```

`GobiTranslations` テーブル

| locale | key | value |
|:-:|:-:|:-:|
| ja-nanami | thanks | キュピコーン！ |
| ja-nanami | sorry | キュピコ〜ン |
| ja-nanami | see_you_tomorrow | キュピコーン！ |
| ja-chu | thanks | ありがとっCHU！ |
| ja-chu | sorry | ごめんっCHU |
| ja-chu | see_you_tomorrow | またあしたっCHU！ |

以下課題
:recycle: 活用形がほぼ無い語尾(例：「〜〜ネコ」など)を更に高速化する手法を検討。
:recycle: model.errors.messagesのようなrailsの深い部分で実装されている部分についての考慮。
:recycle: 韓国語で語尾がどのように表現されているのか調査。韓国でもプリティーシリーズは人気のようなので国際化も考えるのか。
:rocket: ユーザー(のマイキャラ)独自の定義を追加する場合、禁止用語のチェック

## 参考にしたサイト
* [gem i18n](https://github.com/ruby-i18n/i18n)
* [gem i18n-active_record](https://github.com/svenfuchs/i18n-active_record)
* [Railsガイド Rails 国際化 (i18n) API](https://railsguides.jp/i18n.html)
* [プリパラコインショッピング > 語尾ボイス](https://pripara.jp/howto/howto_21.html)
* [形態素解析を用いて、夢川ゆいのユメ語録を再現してみる試み](https://syn-station.hatenablog.com/entry/2018/12/22/220050)