Nuxt.js: i18n example broken on server load

Created on 31 Aug 2017  路  13Comments  路  Source: nuxt/nuxt.js

How to reproduce:
download https://github.com/nuxt/nuxt.js/tree/dev/examples/i18n
yarn
and then any:

yarn dev

```sh
yarn generate
hs -p 3000 dist/

```sh
yarn build
yarn start

and load http://localhost:3000/fr/about/

In all of the cases, the server will send the French html,
but upon hydration it will silently (nothing in console) be replaced with English.

Furthermore, the active base state in the Vue devtool will be

locales:Array[2]
0:"en"
1:"fr"
locale:"fr"

while $vm0.$i18n.locale is 'en';

This question is available on Nuxt.js community (#c1367)
help-wanted

Most helpful comment

It should be fixed with rc9, I moved back store hydration before plugins execution

All 13 comments

tl;dr:
after server load and hydration at non-default lang, for example /fr/about/
$vm0.$i18n.locale is not equal to $vm0.$store.state.localewhich is fr, but to the default en instead.

Wildly enough, it does not happen at https://i18n.nuxtjs.org/fr/about

Facing this issue after updated to rc8 today.

@wushan what version did you update from? I will see if downgrading to that fixes my issue or the example.

@qm3ster I am facing the same issue, but I noticed that if you commit to the store the lang parameter in the plugin code too it starts working. Don't know if it could be a valid solution though...

In plugin/i18n.js

import Vue from 'vue'
import VueI18n from 'vue-i18n'

Vue.use(VueI18n)

export default ({ app, store, route }) => {
  const locale = route.params.lang || 'en'
  store.commit('SET_LANG', locale)
  // Set i18n instance on app
  // This way we can use it in middleware and pages asyncData/fetch
  app.i18n = new VueI18n({
    locale: store.state.locale,
    fallbackLocale: 'en',
    messages: {
      'en': require('~/locales/en.json'),
      'fr': require('~/locales/fr.json')
    }
  })
}

In middlewares/i18n.js

export default function ({ isHMR, app, store, route, params, error, redirect }) {
  // If middleware is called from hot module replacement, ignore it
  if (isHMR) return
  // Get locale from params
  const locale = params.lang || 'en'

  if (store.state.locales.indexOf(locale) === -1) {
    return error({ message: 'This page could not be found.', statusCode: 404 })
  }
  // Set locale
  store.commit('SET_LANG', locale)
  app.i18n.locale = store.state.locale

  // If route is /en/... -> redirect to /...
  if (locale === 'en' && route.fullPath.indexOf('/en') === 0) {
    return redirect(route.fullPath.replace(/^\/en/, '/'))
  }
}

I think this related with changes that comes with rc7. https://github.com/nuxt/nuxt.js/commit/8dca35821621aa28e977eda189049d594464662e
Client runs plugins earlier than replaced store from server.

In rc8, I blindly swapped

  <% plugins.filter(p => p.ssr).forEach(plugin => { %>
  if (typeof <%= plugin.name %> === 'function') await <%= plugin.name %>(ctx, inject)<% }) %>
  <% if (plugins.filter(p => !p.ssr).length) { %>
  if (process.browser) { <% plugins.filter(p => !p.ssr).forEach(plugin => { %>
    if (typeof <%= plugin.name %> === 'function') await <%= plugin.name %>(ctx, inject)<% }) %>
  }<% } %>

  <% if (store) { %>
  if (process.browser) {
    // Replace store state before calling plugins
    if (window.__NUXT__ && window.__NUXT__.state) {
      store.replaceState(window.__NUXT__.state)
    }
  }
  <% } %>

to

  <% if (store) { %>
  if (process.browser) {
    // Replace store state before calling plugins
    if (window.__NUXT__ && window.__NUXT__.state) {
      store.replaceState(window.__NUXT__.state)
    }
  }
  <% } %>

  <% plugins.filter(p => p.ssr).forEach(plugin => { %>
  if (typeof <%= plugin.name %> === 'function') await <%= plugin.name %>(ctx, inject)<% }) %>
  <% if (plugins.filter(p => !p.ssr).length) { %>
  if (process.browser) { <% plugins.filter(p => !p.ssr).forEach(plugin => { %>
    if (typeof <%= plugin.name %> === 'function') await <%= plugin.name %>(ctx, inject)<% }) %>
  }<% } %>

And it fixed the issue. @Vampirkod was right.

@Atinux, can you comment on what the downside would be? Why was 8dca358 implemented?

Hi,

It's because we want to add the possibility of plugnis to register dynamic module and replaceState before make it fail.

Actually with rc9, I am introducing serverStoreState which is equal to window.__NUXT__.state.

I am sorry, does middleware not run on the client during server load?
As a temporary fix I added

if (app.i18n.locale !== store.state.locale) {
  app.i18n.locale = store.state.locale
}

after

 if (store.state.locale !== locale) {
  store.commit('SET_LANG', locale)
  app.i18n.locale = store.state.locale
 }

But it only fixes after the first clientside navigation?

@qm3ster this would helps

export default (ctx) => {
  const { isClient, app, store } = ctx;
  // this would be by default in rc9, so in future you not need this 3 lines
  if (isClient) {
    ctx.serverStoreState = window.__NUXT__.state;
  }

  app.i18n = new VueI18n({
    locale: isClient ? ctx.serverStoreState.locale : store.state.locale,
    fallbackLocale: 'en',
    messages: messages: {
      'en': require('~/locales/en.json'),
      'fr': require('~/locales/fr.json')
    },
  });
};

I get what you mean, but the big problem is that when state is replaced, it seems to break reactivity in my case.
It would be far superior if I could just initialize VueI18n to point at the store.

// middleware/i18n.js
/*...*/
if (store.state.i18n.locale !== locale) {
  store.commit('i18n/SET_LANG', locale)
  app.i18n.locale = store.state.i18n.locale // so that this line is removed
}

(The state.i18n is because I moved the whole i18n configuration object into a store module so that I can later manage loading locales. My plugin is now just the following: )

// plugins/i18n.js
import Vue from 'vue'
import VueI18n from 'vue-i18n'

Vue.use(VueI18n)

export default({app, store}) => {
  app.i18n = new VueI18n(store.state.i18n)
}

Basically, it's undeniable that the appropriate, idiomatic solution would be for i18n to observe the store like any other part of our app, and manually also putting data elsewhere inside the mutation or especially at the mutation callsite, like in the example, is a dirty hack.

It should be fixed with rc9, I moved back store hydration before plugins execution

This thread has been automatically locked since there has not been any recent activity after it was closed. Please open a new issue for related bugs.

Was this page helpful?
0 / 5 - 0 ratings

Related issues

mattdharmon picture mattdharmon  路  3Comments

nassimbenkirane picture nassimbenkirane  路  3Comments

danieloprado picture danieloprado  路  3Comments

jaredreich picture jaredreich  路  3Comments

VincentLoy picture VincentLoy  路  3Comments