Polyconseil / vue-gettext

Translate your Vue.js applications with gettext.
MIT License
278 stars 55 forks source link

Text in translation tags doesnt work with hot module reloading. #18

Open trainiac opened 7 years ago

trainiac commented 7 years ago

When using hot module reloading with webpack, the text doesn't update when making changes. This is because the msgid property in the translate component is decided when the component created event.

I tried moving the logic in the created function into the translation computed property but that doesn't update because $options._renderChildren (which we should consider changing to $slots.default in order to not access a private property) is not a reactive property so nothing would trigger the computed property to update. Even $slots is not a reactive property, so computed property is not the solution.

It seems like the only way this works is if moving the logic into another part of the component life cycle. This probably has some performance implications so I wanted to propose something and I'll submit a PR if it seems appropriate.

 data () {
    // Make msgid a reactive property
    return {
      msgid: ''
    }
  },

  created: function () {
    this.msgid = this.getMsgId()
    this.isPlural = this.translateN !== undefined && this.translatePlural !== undefined;
    if (!this.isPlural && (this.translateN || this.translatePlural)) {
      throw new Error(("`translate-n` and `translate-plural` attributes must be used together: " + (this.msgid) + "."))
    }
  },

  // This is what would make the hot update work.
  beforeUpdate: function () {
    this.msgid = this.getMsgId()
  },

  methods : {
   // move this logic into a function so that in can be reused. 
    getMsgId: function () {
      // Store the raw uninterpolated string to translate.
      // This is currently done by looking inside a private attribute `_renderChildren` of the current
      // Vue instance's instantiation options.
      // However spaces introduced by newlines are not exactly the same between the HTML and the
      // content of `_renderChildren`, e.g. 6 spaces becomes 4 etc. See issue #15 for problems which
      // can arise with this.
      // I haven't (yet) found a better way to access the raw content of the component.

      // use $slots.default instead of $options._renderChildren
      if (this.$slots.default) {
        if (this.$slots.default[0].hasOwnProperty('text')) {
          return this.$slots.default[0].text.trim();
        }
        return this.$slots.default[0].trim();
      }

      return ''
    }
  },

So the changes are:

  1. Make msgid reactive
  2. Include a beforeUpdate function that updates the msgid before rendering.
  3. Break out the logic for computing the msgid into a reusable function.
  4. Use $slots.default instead of $options._renderChildren (maybe make this a separate PR?)
  5. Add a new plugin config option develop that if set to true would extend the translate component configuration with the beforeUpdate. This way the extra function call won't happen on each rerender.
kemar commented 7 years ago

Yup that's true. Hot module reloading won't update the translation tags content.

However, for me in any case, it's never been an issue since such a change most likely implies a change in the translations files so you have to compile translations again, relaunch the dev server etc.

Also I'm curious about how you can use this.$slots.default since no slot is ever used in the component template? I'm not able to access this.$slots in the current state of the component without trigerring an error.

trainiac commented 7 years ago

Hmm maybe it's because I'm using a webpack vue-loader build process. I assumed by default every custom component that gets passed content implicitly has $slot.default, but perhaps it's due to my build system.

trainiac commented 7 years ago

With the changes I made above, I have access to this.$slots.default.

There are a few examples of rendering components using $slots where it's not defined in templates. https://vuejs.org/v2/guide/render-function.html

trainiac commented 7 years ago

Have you tried only accessing $slots in the render function? Maybe in created $slots has not been created yet. But it is strange that it worked for me.