Q. 多言語対応をしたNuxt製のサイトでタイトルが部分的にしか表示されない

概要#

Nuxtの標準的な多言語対応ライブラリである@nuxtjs/i18nvue-i18n)はパイプ(|)区切りを特殊文字として複数形の表現に利用するため、この仕様を知らずに利用していた箇所でタイトルが部分的にしか表示されない問題が発生していた。

問題の発見#

普段関わりのないNuxt製のプロダクトで、英語版のタイトルの{ここ} | 私のサイトのような箇所に項目を追加してほしいといった依頼があった。

しかし、翻訳ファイルを見ると既に要望の形で登録されていた。しかし実際にサイトを確認すると、確かに私のサイトとしか表示されていない。

さらに、他のページのタイトルや文章でも同様に一部分しか表示されていない箇所があることを見つけた。

原因の推測#

問題の発生している全ての箇所で共通して、文中にパイプ(|)が含まれていたため、 利用しているi18nライブラリの仕様である可能性を疑った。

vue-i18nの仕様#

実際に仕様を確認すると、vue-i18nでは、パイプ(|)区切りの文字列を用いて複数形の表現をサポートしていることが分かった。

const messages = {
en: {
car: 'car | cars',
apple: 'no apples | one apple | {count} apples'
}
}

これは次のように解釈される。

$t('car', 1)
// car
$t('car', 2)
// cars
$t('apple', 0)
// no apples
$t('apple', 1)
// one apple
$t('apple', 10, { count: 10 })
// 10 apples

参考:Message Format Syntax | Vue I18n

原因#

問題の発生していたサイトでは、次のような翻訳ファイルが作成されていた。

locales/en.json
{
"site.title": "Super contents | My Site"
}

@nuxtjs/i18nの仕様により、このキーの値は次のように解釈されるため、タイトルの一部しか表示されないようになっていた。

t('site.title')
// en: MySite
// ja: 私のサイト

このプロダクトでは、多言語対応がほぼ機能が完成してから行われたため、問題が発覚せず残り続けていたものと思われる。

対応#

対応方法はいくつか考えられる。

パイプのエスケープ#

vue-i18nのissueでは、次のようにパイプをエスケープする方法が提案されている。

"hello {'|'} world!"

出展:https://github.com/intlify/vue-i18n/issues/401#issuecomment-801543471

ちなみに、\u007Cではエスケープ出来ないことを確認した。

文字列を分割する#

パイプを使っているケースの多くは意味的に区切りを表現したい場合が多いと思われるため、次のように分割して表現する方が適切な可能性がある。

locales/en.json
{
"site.title": "My Site",
"page.superContents.title": "Super contents"
}
$t('page.superContents.title') | $t('site.title')
// Super contents | My Site

エスケープを使わない#

対応が面倒な場合は、パイプを使わないようにするのも一つの手かもしれない。

{
"site.title": "Super contents - My Site",
}

余談、i18nextおよびICU MessageFormatにおける複数形対応#

利用する機会の多いi18next系列のライブラリでは、パイプを特殊文字として扱うことはないため、かなり驚きがあった。

i18next系列では次のように複数形に対応することが多いように思う。専用の記法ではないため、認知負荷は低い。ただし、スラブ語派の言語などもサポートする際は恐らく対応が難しい。

{
"key_one": "{{count}} item",
"key_other": "{{count}} items",
"key_zero": "no items"
}

また、Intl.MessageFormatなどで採用しているICU MessageFormatでは次のように表現する。ICU MessageFormatは非常に強力で柔軟な表現が可能だが、学習コストや認知負荷が高い。

'{count, plural, =0 {no items} one {one item} other {{count} items}}'

個人的には仕様がある程度信頼できるICU MessageFormatを利用していきたい。 ちなみに、i18next系列のライブラリはi18next-icuによってICU MessageFormatをサポートできる。