Polyconseil/vue-gettext

Cant set a language per request with server side rendering (SSR).

trainiac opened this issue · 15 comments

Hello again! And thank you for your work. I've began porting our application to SSR and am finding that vue-gettext uses a global setting for all vue instances for the current language (i.e. Vue.config.language). This is a problem for server side rendering because a different language per request may be required. One request could set the global language value which would clobber the language context for other request.

There is a nuxt.js example of how they recommend to handle i18n https://nuxtjs.org/guide/plugins/#inject-in-root-amp-context. Notice how the plugin is being intergrated. They first call a Vue.use to wire up the properties and methods that a component will need and then per request setup the language on the app root component. Then the language can be accessed in components via this.$root.language.

This I think would actually simplify the codebase quite a bit as well as you wont have to instantiate a languageMixinVm. You just explain in the "How to Use" that in the the root component they need to define a "language" property (by default "language" would be the property but should be customizable). It could be computed or just plain data.

@kemar I'd love to hear your thoughts on this.

kemar commented

Hello @trainiac :)

To put things in context, vue-gettext was developped before SSR support in Vue.js.

Vue.config.language is heavily used in unit tests and can also be used in custom filters where you can't access any vm instance. That may be a little bit of a challenge to refactor.

As much as your suggestion sounds great, I do not have the time to work on it now, nor in the coming months.

But feel free to submit a PR!

Totally understand. I'll fork for now and show you what I ended up doing.

Do you really need to support a v-translate directive :). Is it purely for stylistic purposes, i.e. can the translate component do everything the directive can? If so, there could be some simplicity in only offering translate component (smaller lib size, simpler docs, supports SSR :P). However, if you really did need to support v-translate maybe you could use a function in the plugin setup?

// directive.js

export default lang => ({
  bind () {
    // access to lang
  },
  update () {
    // access to lang
  }
})

Anyways, I'll keep you posted!

kemar commented

Do you really need to support a v-translate directive

Yes! Just read the doc, the directive adds support for HTML content in translations.

If you plan to submit a PR, you have to support all available features and make sure npm test passes :)

@kemar After having done some research I think I could submit a PR that allows vue-gettext to support server side rendering with the exception of the v-translate directive. If you are using vue-gettext only for client side rendering v-translate will still work fine.

The reason v-translate can't be supported server side is due to a shortcoming of the Vue API. Directives can either be made global or available to a specific component. However, if you have a directive that depends on a per request value (i.e. SSR) and you want to make it available to all of the components within the current request render tree (i.e. not global for all components across all requests) there is no option.

You'd need something like this.

const getTranslateDirective = lang => ( {
  bind () {
    // access to lang
  },
  update () {
    // access to lang
  }
})

const translate = getTranslateDirective(() => store.getters.lang)

new Vue({
  directives: {
    translate: {
      module: translate,
      provide: true
    }
  }
})

vuejs/vue#6732 Let's see what they say.

Well it looks like there will be no budging on that so I don't see a way to inject reactive data into a directive without passing it into the directive (v-translate-language='language') but that seems verbose to have to do that every place you want a translation. If the only difference between the translate directive and translate component is the ability to inject html, I think I might start investing some time into why it can't be done with a component. The end all goal here is to support a translate directive and component that works for client side only apps and SSR apps.

@trainiac Is your fork is already working and usable ?

My fork works for my purposes. I only support the translate component. And there is a little more instruction on how to use it. If you're interested let me know and I could walk you through it via gitter.

Hi @trainiac, can you share your fork with us?

@trainiac personally I use cookies for translations with SSR. Pull value from cookies -> set the global $language param on the Node side

It looks like Vue is gonna allow directives per instance now so when this lands in 3.0 I'll submit a PR. vuejs/rfcs#29

I understand vue 3.0 will take a few months yet, and the repo fork from @trainiac is quite old and can't be merged automatically. @trainiac, do you think it's worth updating your fork for the current version, even thought v-translate won't work yet? I'll need translation for a SSR soon and I could work on that update.

@brunobg i'm desperate, please help me!
We got this issue :(

@WdgtSam never got a reply from @trainiac, so I don't know if his repo fork would still work if ported to the current version.

To those looking for this, here's how I did it. This is not a pure server side solution, but it works well enough for my purposes. This solution is used in https://github.com/Corollarium/lajevr/.

  1. Install vue-gettext as usual.

  2. Create a plugins/translation.js file to load vue-gettext. The code at my repo tries to guess the best language as well, but here's the basics:

import Vue from 'vue';
import GetTextPlugin from 'vue-gettext';
import translations from '../components/translations.json';

export default ({ app }) => {
  Vue.use(
    GetTextPlugin,
    {
      availableLanguages: {
        en_US: 'English',
        pt_BR: 'Português'
      },
      defaultLanguage: 'en_US',
      translations,
      silent: true
    }
  );
};
  1. Load the plugin in your nuxt.config.js:
  plugins: [
    { src: '~/plugins/translations.js', mode: 'client' }
  ],
  1. The language picker won't have access to $language when compiled, but it can use it at mount time:
<template>
  <div>
    <select
      v-model="current"
      @change="changed()"
      name="language"
    >
      <option v-for="(language, key) in available" :value="key">
        {{ language }}
      </option>
    </select>
  </div>
</template>

<script>
export default {
  data () {
    return {
      current: 'en_US',
      available: {}
    };
  },

  mounted () {
    this.current = this.$language.current;
    this.available = this.$language.available;
  },

  methods: {
    changed () {
      this.$language.current = this.current;
    }
  }
};
</script>

Everything else works as documented. The only drawbacks are that all translations are compiled in the JS and the strings are replaced at the client side. To the end user there's no difference, and on the other hand, if you change the language no reload from server is required.