この記事をおすすめしたい人
- Nuxt.jsで多言語対応したい人
- Nuxt.jsで多言語対応しようとしたけどなんかイライラする人
- つまりオレ
何も知らない超初心者が脱WordPressしたくてNuxt.jsでサイト構築していくシリーズです。
えー、今回はNuxt.jsでi18nを使って多言語化する際に僕が気になった注意点なんかをまとめていくヨ。 いくつかのプロジェクトで実働させているので、いくらかその辺のお話も。
このページの目次
i18nってなんや?
i18nというのは作ってるアプリケーションやウェブサイトを多言語対応にするライブラリです。
今回はNuxt.jsで使ってますが、別にNuxtに限った話ではないみたいです。 Nuxt用の公式サイトはここ。
今はDeepL翻訳やらChatGPTやらで割と簡単に高精度な翻訳ツールがあるので、i18nとの併用でお手軽に多言語化できます。
Nuxtにi18nをインストールする時の注意
公式を見ると今のインストールコマンドはこれ↓。
npm install @nuxtjs/i18n@next --save-dev
これでインストールするとVer.8.0.0-rc.5がインストールされました。
(2023年10月9日現在)
「@next」がついているせいかRC版がインストールされるようです。 外すとVer.7.3.1がインストールされました。
公式がトップにRC版のインストール方法を書いてあるので十分にテストされてるんでしょう。
「でしょう」?試してないの?
そう!試してないんです!
というのも、v7から要求Node.jsのバージョンがv16になったようで、うちの開発環境がNode v16で動作させられていなくてv14なんですよぉ。
で、この場合のインストール方法がちょっと特殊で、公式にv7のドキュメントまでしかないんです。 v6をインストールしようとこの↓コマンドを入力すると、
npm install @nuxtjs/i18n@6
こんな↓結果になります。
npm ERR! code ETARGET
npm ERR! notarget No matching version found for @nuxtjs/i18n@6.
npm ERR! notarget In most cases you or one of your dependencies are requesting
npm ERR! notarget a package version that doesn't exist.
幸い、僕が最初に使い始めたのがv6だったので、その時のメモによるとコレ↓でいけました。
npm install nuxt-i18n
名前が微妙に変わったようです。 頭の「@」がなくて、「/」ではなく「-」。
これでインストールするとVer.6.28.1がインストールされました。 Node v14でもおk。
訳語定義の構造、管理しにくくない?
今回のメインコンテンツのつもりなので、i18nの使い方やら何やらより先にこちらを書きます。
i18nでは指定したキーに対して日本語用と英語用の文字列を定義、みたいな形で多言語化します。 「aisatsu1」というキーに対して「こんにちは」「Hello」を指定する感じ。
で、この定義の仕方がこういう↓構造なんですよね。
{
ja: {
aisatsu1: 'こんにちは',
aisatsu2: 'やぁ',
},
en: {
aisatsu1: 'Hello',
aisatsu2: 'Hi',
},
}
これ、プログラム側が管理する時は先に言語コードがきてて理解しやすいような気もしますが、追加する時、管理する時はぜっっっっっったいにこっち↓の方がいいと思うんですよね。
{
aisatsu1: {
ja: 'こんにちは',
en: 'Hello',
},
aisatsu2: {
ja: 'やぁ',
en: 'Hi',
},
}
このくらいの量ならアレだけど、30個とか定義すると、同じHelloの意味を書く場所がめっちゃ離れるんですよ。 新しく追加する時も、前者ならjaとenの末尾に追加しないといけないけど、後者なら1箇所に追加するだけ。 言語の数が中国語、フランス語、ドイツ語とか増えていったら作業量がダンチです。
キーとワードを逆の構造で書く方法
これ、i18n側にはあるんですかね? 公式のドキュメント眺めてる感じではなさそうでした。
で、ChatGPTに確認してみたら「こんな↓風に書けばいけるよ」と。
{
common: {
aisatsu1: {
ja: 'こんにちは',
en: 'Hello',
},
aisatsu2: {
ja: 'やぁ',
en: 'Hi',
},
},
}
commonオブジェクトの内側に入れればできるよと。
…って動かへんやないかい。
うちではダメでした。 i18nのバージョンが古いせいな可能性もあるので、新しいバージョンを使ってる人は試してみる価値はありますぜ。
無理矢理逆に書く方法
簡単です。
読み込み時にi18n用の構造に書き換えてやればいいんです。
まずプロジェクト内のどこかテキトーな場所(langフォルダとか)にテキトーなJSファイル(lang.jsとか)を作ります。
書きやすい書き方↓で言葉を定義します。
const data = {
aisatsu1: {
ja: 'こんにちは',
en: 'Hello',
},
aisatsu2: {
ja: 'やぁ',
en: 'Hi',
},
}
次に出力用の空オブジェクト↓を作ります。
const output = {
ja: {},
en: {},
}
元データを出力用に構造変換↓します。
for ( const key in data ) {
output.ja[key] = data[key].ja
output.en[key] = data[key].en
}
この結果、ワード定義オブジェクトがこの↓ような構造になります。
ja: {
aisatsu1: 'こんにちは',
aisatsu2: 'やぁ',
},
en: {
aisatsu1: 'Hello',
aisatsu2: 'Hi',
},
あとはこのoutput変数を出力する↓だけ。
export default {
lang: output,
}
これをnuxt.config.jsで読み込んで↓、
(相対パスは設置場所に応じて調整してください)
import messages from './lang.js';
i18n: {
...
vueI18n: {
messages: messages.lang,
},
}
「messages.lang」の「messages」の部分はimportした変数名、「lang」の部分は外部jsファイル内でexportした変数名です。 無名で出力すると元の変数名(上の例だとoutput)になるようでした。
これで同じキーの訳語を同じ場所で定義できます。
いやーこれでだいぶ捗るぞ。
i18nの初期設定
インストールした後の初期設定ですね。
nuxt.config.jsを開いて、modulesの項目に追加して、i18nという名前でモジュールの設定をします。
export default {
...
modules: {
...
//v7以降の場合は「@nuxtjs/i18n」
'nuxt-i18n',
},
i18n: {
//ここに設定
},
}
localesの設定
たぶん必須?対応する言語(日本語とか英語とか)を設定します。
簡単な書き方だとこう↓。
i18n: {
locales: ['ja','en'],
},
配列の中身をオブジェクト↓にすることもできます。
i18n: {
locales: [
{ code: 'ja', iso: 'ja_JP', file: 'ja.js' },
{ code: 'en', iso: 'en_US', file: 'en.js', dir: 'ltr' },
],
},
codeのみ必須だと思います。
isoはISO言語コードで、初期設定だとどこで使われてるんだろ。 うちではOGPのog:localeに使っています。 i18nのマニュアルではハイフンで言語と国コードを連結してたんですが、OGP側はアンダースコアで連結してたのでアンダースコアにしてます。
fileはワードを定義した外部ファイルの名前。 langDirで指定したフォルダから読み込みます。
dirはその言語だけ読み込み先を変える場合ですかね。
うちでは言語切り替えに使うnameプロパティを追加して、この↓ような形にしています。
i18n: {
locales: [
{ code: 'ja', iso: 'ja_JP', name: '日本語' },
{ code: 'en', iso: 'en_US', name: 'English' },
],
},
defaultLocaleの設定
デフォルトの言語コードを指定します。 localesで設定したcodeで指定します。
i18n: {
defaultLocale: 'ja',
},
これはおそらくURLに言語コードを付けない言語の指定で、上の例だとこんな↓感じのURL生成になります。
//日本語版
https://example.com/
//英語版
https://example.com/en/
defaultLocaleをenにするとこう↓なります。
//日本語版
https://example.com/ja/
//英語版
https://example.com/
messagesの設定
各キーに対する訳語を設定します。 メインの部分ですね。
一番基本の書き方がコレ↓だと思います。
i18n: {
vueI18n: {
messages: {
ja: {
aisatsu1: 'こんにちは',
aisatsu2: 'やぁ',
},
en: {
aisatsu1: 'Hello',
aisatsu2: 'Hi',
},
}
},
},
「i18n」の中の「vueI18n」の中の「messages」に言語ごとに定義します。
訳語の設定の仕方はけっこうな種類の方法があるみたいで、一例ということで。 先に説明した構造変換の方法を使うと外部ファイルで管理できるので、これで十分かなーという気もします。
最低限これくらい設定しとけばOKだと思います。
設定可能な項目は他にも山ほどあるので、興味ある人は公式を見てください。
i18nの使い方サンプル
ワードの定義は終わったものとして、それをどう使うかの例…というか僕が使ってる用途を書きます。
ワード単位の呼び出し
このページの表の項目や検索オプションなど、使い回す項目で、短い言葉に使っています。
HTML部分では$t関数にキーを指定↓して呼び出します。
<th>{{$t('name')}}</th>
<th v-html="$t('name')"></th>
script内ではthisを付けます。
return this.$t('name')
言語キーは指定せずとも現在表示している言語コードで自動的に呼び出してくれます。
文章単位の呼び出し
このページのように、そのページでしか使わず、長めの文章に使っています。
$i18n.localeに現在の言語コードが入っているので、この↓ようにv-ifを使います。
<h2 v-if="$i18n.locale == 'ja'">タイトル</h2>
<h2 v-if="$i18n.locale == 'en'">Title</h2>
<p v-if="$i18n.locale == 'ja'">
ここに文章を書きます。
</p>
<p v-if="$i18n.locale == 'en'">
Write a sentence here.
</p>
何度も条件分岐するには条件式が長いので、script部分でこの↓ようなプロパティを用意しておくとHTML部分を簡素化できます。
computed: {
isJa() {
return this.lang == 'ja'
},
lang() {
return this.$i18n.locale
},
},
実際の運用ではisJaとelseで管理してます。
セクション単位とかもっと大きな要素で条件分岐してしまえば条件式を書く回数は減りますが、上の書き方だと対応する日本語と英語が一目瞭然でメンテナンス性がいいので僕はこのやり方でやっています。
現在のページの別言語ページへのリンク
switchLocalePath()を使います。
<nuxt-link :to="switchLocalePath('ja')">日本語版のページへ</nuxt-link>
これは全ページに置いておいてもいいと思うので、コンポーネントにして現在の言語($i18n.locale)に対して表示を切り替えて(日本語ページなら英語版へのリンクなど)使い回しています。
言語を維持したまま別ページへ移動
えーと、意味が伝わるか。
例えば、今トップページの英語版にいるとして、aboutというページに移動するとします。 この時に /about/ でリンクを貼るとaboutの日本語版に飛んでしまうわけです。 英語版のリンクは /en/about/ なので。
なら現在の言語が英語なら~みたいにリンク先URLを変更しないとわけですが、そんなことせずともi18n側でそのような関数↓が用意されています。
<nuxt-link :to="localePath('/about/')">当サイトについて</nuxt-link>
これでおk。 第2引数に言語コードを指定すると特定の言語のリンクも得られます。
このタグは多言語で使い回すことになるので、リンクテキストを定義しておいてこんな↓感じで使うのがいいでしょう。
<nuxt-link :to="localePath('/about/')">{{$t('about')}}</nuxt-link>
メニューとかパンくずとかでゴリゴリ使ってます。
言語一覧へアクセス
$i18n.localesに入ってます。
nuxt.config.jsのlocalesで設定したまんまが配列で入っているので、文字列で指定したら文字列が、オブジェクトならオブジェクトが返ってきます。
メニューで各言語へのリンクを貼ったりするのに使えます。
コンボボックス(select)で言語の変更
日本語/英語くらいだと必要ないですが、もっと対応言語が多くなると全リンクを貼るのが難しくなります。 そこでselectタグを使って1要素で複数の言語へ飛べるようにする場合です。
nuxt.config.jsで設定したlocalesにnameプロパティを持たせているとして、まずはHTML部分↓でselectタグを設置します。
<select v-model="localeCode" @change="changeLocale">
<option v-for="row in $i18n.locales" :value="row.code">
{{row.name}}
</option>
</select>
値が変更されるとchangeLocale()関数を呼び、そっちでページ遷移を行います。
選択している言語を識別するために変数localeCodeをscript部分↓で用意。
export default {
data() {
return {
localeCode: this.$i18n.locale,
}
},
}
現在の言語コードで初期化しています。
最後にselectの値が変化した時に起こすイベント↓。
export default {
methods: {
changeLocale() {
this.$router.push(this.switchLocalePath(this.localeCode))
},
},
}
特に問題はなさそうですが、今と同じ言語ページへ飛ぼうとした場合を除外↓するとこんな感じ。
export default {
methods: {
changeLocale() {
if ( this.localeCode != this.$i18n.locale ) {
this.$router.push(this.switchLocalePath(this.localeCode))
}
},
},
}
i18nでのhead内の操作
HTMLのHEADタグ(とHTMLタグ)を出力する部分です。
head内ではけっこうやることがあります。
HTMLタグのlang属性
これはheadタグの話じゃないですが、HTMLタグのlang属性もhead()内で定義します。
export default {
head() {
return {
htmlAttrs: {
lang: this.$i18n.locale,
},
}
},
}
「ja」「en」形式でも「ja_JP」「en_US」形式のどちらでもいいっぽい。
titleとdescription
titleとmeta descriptionは単純に$i18n.localeでの条件分岐で言語ごとに設定すればおk。
og:titleとog:descriptionも同様。 og:site_nameを設定する場合もここで。
canonical
canonicalは$route.pathに言語コード込みで入っているので特別な処理はいらないです。 ルートのドメインと連結するだけ。
og:urlも同様。
og:locale
Facebookの開発ドキュメントに詳細があります。
ここは言語コードだけじゃなく国コードも必要なようで、アンダースコアで連結した「ja_JP」「en_US」みたいな形式みたいです。
nuxt.config.jsで設定したlocalesにisoプロパティを追加していればそれを使います。
hreflang
最後に、多言語対応独自の要素として、linkタグalternateにhreflangを追加します。 baseUrlがドメインを返すものとして、コードはこんな↓感じ。
export default {
head() {
let links = []
this.$i18n.locales.forEach(row => {
links.push({
rel: 'alternate',
hreflang: row.code,
href: this.baseUrl + this.switchLocalePath(row.code)
})
})
return {
link: links,
}
},
}
hreflangは「ja」形式でも「ja_JP」形式でもどっちでもいいそうです。 後者の場合はrow.codeではなくrow.isoを。
このコードだと現在のページもalternateとしてしまいますが、Googleのドキュメントによると「ページの言語や地域ごとのすべてのバージョンを Google に提示します」とのことなので入れておいていいみたい。
まとめ
えー、本当は「i18nモジュールの読み込み」で紹介したオブジェクトの構造を変換する手法を思いついた時に感動してしまって(外部ファイルをJSONにしていたので動的に出力する発想がなかった)、それだけ書こうと思ったのですが、せっかくなので自分が使っている方法などもついでにまとめました。
インストール時のバージョン指定も困ってる人いるかもしれないので、お役に立てると幸いです。
以上、WordPressからお届けしました!