前振り: 言語別ルーティング
言語別にディレクトリを切ってコンテンツ表示するサイトを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でのディレクトリ構成
- pages/
- _lang/
- index.vue
- posts/
- index.vue
- _id.vue
- _lang/
nuxtではpages下に配置したコンポーネントの構成からroutesが作られます。
今回は、langとpostIdが動的となるため、langとidにアンダースコアでプレフィックスをつけることで実装できます。
生成されるroutes
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
- posts/
今回はlang部分が無いケースが出てくるため、lang部分を省略できるようにlang下にindex.vueを置かず、_lang.vueに変更してみます。
生成されるroutes
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
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無しの構成を作ります。
routes: [
{
path: '/',
component: _0ff1b892,
name: 'index'
},
{
path: '/posts',
component: _5e83ea8a,
name: 'posts'
},
{
path: '/posts/:id',
component: _4f95003a,
name: 'posts-id'
}
]
この段階ではこれだけです。
extendRoutesで言語別ルーティングを追加
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
export default function({ params: { lang }, store }) {
store.commit('lang', { lang })
}
まずはページ遷移時にstoreにparamを保存します。
store
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
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
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情報を指定せずに遷移できるようになります。
参照
- Facing difficulties with nuxt routing and i18n - Nuxt Community
- Define custom routes on nuxt.config.js · Issue #112 · nuxt/nuxt.js
(generate時)
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を削るという処理をします。