Edited at

Nuxtで言語別ルーティングを実装する

More than 1 year has passed since last update.


前振り: 言語別ルーティング

言語別にディレクトリを切ってコンテンツ表示するサイトをnuxtで実装します。

(コンテンツ内容が違うだけで、使用コンポーネントは同一とします)


  • /en/index.html

  • /en/posts/index.html

  • /en/posts/1/index.html

  • /ja/index.html

  • /ja/posts/index.html

  • /ja/posts/2/index.html


nuxtでのディレクトリ構成

ルーティング - Nuxt.js


  • pages/


    • _lang/


      • index.vue

      • posts/


        • index.vue

        • _id.vue







nuxtではpages下に配置したコンポーネントの構成からroutesが作られます。

今回は、langとpostIdが動的となるため、langとidにアンダースコアでプレフィックスをつけることで実装できます。


生成されるroutes


nuxt/router.js

    routes: [

{
path: '/:lang',
component: _4579e935,
name: 'lang'
},
{
path: '/:lang/posts',
component: _14cb8c8e,
name: 'lang-posts'
},
{
path: '/:lang/posts/:id?',
component: _08a5533e,
name: 'lang-posts-id'
}
]

ビルドして .nuxt/router.js を見るとディレクトリ構造から生成されたroutesを確認できます。

pathに : がついた部分が動的ルーティングとなり、params.lang、params.idとして値を受け取って表示を切り分けることができます。


本題: デフォルト言語の場合にはそのディレクトリを省略する構成

サイトのデフォルト言語の場合には、ディレクトリを切らずに直下に表示したい、とします。


  • /index.html

  • /posts/index.html

  • /posts/1/index.html

  • /ja/index.html

  • /ja/posts/index.html

  • /ja/posts/2/index.html

(enをデフォルトとした場合)


nuxtでのディレクトリ構成


  • pages/


    • _lang.vue

    • _lang/


      • posts/


        • index.vue

        • _id.vue







今回はlang部分が無いケースが出てくるため、lang部分を省略できるようにlang下にindex.vueを置かず、_lang.vueに変更してみます。


生成されるroutes


nuxt/router.js

    routes: [

{
path: '/:lang?',
component: _655c5f9c,
name: 'lang',
children: [
{
path: 'posts',
component: _14cb8c8e,
name: 'lang-posts'
},
{
path: 'posts/:id?',
component: _08a5533e,
name: 'lang-posts-id'
}
]
}
]

index.vueを持たない動的ルーティングのディレクトリは /:lang? のように ? がつくので省略された場合でもマッチするようになります。

ただしこれではうまく動きません。

/:lang? だと全てのrouteにマッチしてしまうからです。


本当に欲しいroutes


nuxt/router.js

    routes: [

{
path: '/',
component: _0ff1b892,
name: 'index'
},
{
path: '/posts',
component: _5e83ea8a,
name: 'posts'
},
{
path: '/posts/:id',
component: _4f95003a,
name: 'posts-id'
},
{
path: '/:lang',
component: _0ff1b892,
name: 'lang-index'
},
{
path: '/:lang/posts',
component: _5e83ea8a,
name: 'lang-posts'
},
{
path: '/:lang/posts/:id',
component: _4f95003a,
name: 'lang-posts-id'
}
]

最終的なroutesから考えるとこうならないといけません。


どうやるか


TL;DR


  • langディレクトリを作らない

  • extendで動的に生やす

  • nuxt-linkを拡張する


nuxtでのディレクトリ構成


  • pages/


    • index.vue

    • posts/


      • index.vue

      • _id.vue





とりあえずlang無しの構成を作ります。


nuxt/router.js

    routes: [

{
path: '/',
component: _0ff1b892,
name: 'index'
},
{
path: '/posts',
component: _5e83ea8a,
name: 'posts'
},
{
path: '/posts/:id',
component: _4f95003a,
name: 'posts-id'
}
]

この段階ではこれだけです。


extendRoutesで言語別ルーティングを追加


nuxt.config.js

const makeLangRoutes = (routes, paramName) =>

routes.map(route => ({
...route,
path: `/:${paramName}${route.path}`.replace(/\/$/, ''),
name: `${paramName}-${route.name}`
}))

const conf = {
router: {
extendRoutes(routes) {
routes.push(...makeLangRoutes(routes, 'lang'))
},
middleware: ['lang']
},
plugins: ['@plugins/langLink.js'],
}

module.exports = conf


routerのextendRoutesで現在のroutesをベースにして、pathとnameに paramNameとして 'lang' を加えたrouteを作って追加します。

routes生成についてはこれで対応できます。


nuxt-linkを拡張して表示中の言語で遷移

routesは作れましたが、言語別でrouteのnameが変わるため、ページ遷移する際にはnuxt-linkに対してnameとparamを適切に指定する必要が出てきます。

(デフォルト言語の場合)

<template lang="pug">

nuxt-link(
:to="{ name:'posts-id', params: {id: post.id, lang} }"
) {{post.title}}

(デフォルト言語以外の場合)

<template lang="pug">

nuxt-link(
:to="{ name:'lang-posts-id', params: {id: post.id, lang} }"
) {{post.title}}

いちいち場合分けして入れるのは大変なので、nuxt-linkを拡張して自動的に設定されるようにします。


middleware


middleware/lang.js

export default function({ params: { lang }, store }) {

store.commit('lang', { lang })
}

まずはページ遷移時にstoreにparamを保存します。


store


store/index.js

const state = () => ({

lang: null,
defaultLang: 'en'
})
const mutations = {
lang(state, { lang }) {
state.lang = lang || state.defaultLang
}
}
export default {
state,
mutations
}

store側ではstateに現在のlangを保持するように作っておきます。


link component


components/LangLink.js

const newData = (data, { lang, defaultLang }) => {

const to = data.attrs.to
const currentLang = (to && to.params && to.params.lang) || lang
if (currentLang && currentLang !== defaultLang) {
to.name = `lang-${to.name}`
}
return data
}

export default {
name: 'lang-link',
functional: true,
render(h, {data, children, parent}) {
return h('nuxt-link', newData(data, parent.$store.state), children)
}
}


nuxt-linkを拡張したコンポーネントです。

storeから現在の言語を取得して、デフォルト言語でなければroute先の名前を変更する処理を行います。

(functional componentからstoreへのアクセスはparent経由で行います)


plugin


plugins/langLink.js

import Vue from 'vue'

import LangLink from '@/components/LangLink.js'

Vue.component(LangLink.name, LangLink)


上で作ったコンポーネントをグローバルで使えるようにpluginで登録します。


コンポーネントで使用

<template lang="pug">

lang-link(
:to="{ name:'posts-id', params: {id: post.id} }"
) {{post.title}}

nuxt-linkだった部分をlang-linkに書き換えます。

これで各コンポーネント側からはlang情報を指定せずに遷移できるようになります。


参照


(generate時)


module.js

    const langs = await api.get('langs')

const posts = await api.get('posts')
const langRE = new RegExp(`^${defaultLang}$`)
const omitLangIfDefault = lang => lang.replace(langRE, '')
this.options.generate.routes = [
...langs.reduce(
(acc, lang) => [
...acc,
...['', 'posts'].map(route => ({
route: `${omitLangIfDefault(lang)}/${route}`
})),
],
[]
),
...posts.map(post => ({
route: `${omitLangIfDefault(post.lang)}/posts/${post.id}`
}))
]

generateする場合は、APIからコンテンツ取得後にroutesを構築しますが、その際にデフォルト言語であればpathを削るという処理をします。