Vue-i18n: Access to vue-i18n assets is not available within a Vue component method when called via a Promise

Created on 26 Jun 2017  路  39Comments  路  Source: kazupon/vue-i18n

vue & vue-i18n version

vue v2.3.4
vue-i18n v7.0.3
Using with Laravel 5.4, and bundling assets with Webpack via Laravel Mix.

Reproduction Link

(Pending, will update post upon completion.)

Steps to reproduce

  • Prepare a Vue component, with Vue-i18n included as an asset on the top-level Vue instance
  • Prepare a Vue component, which includes a Vue method that accesses this.$t or this.$tc within the method. The method should not be defined using ES6 arrow notation so that the 'this' context is the Vue instance.
  • Call the method as the response or error method from a JS Promise

What is Expected?

Access to this.$t is provided via the 'this' binding solution provided by Vue

What is actually happening?

vue-i18n is not available (VueComponent property '_i18n' is null).
Other Vue instance properties are available in this context, such as this.$store when using vuex, as well as all component data, props and computed properties via 'this'.
An attempt to access this.$t generates an error:
TypeError: Cannot read property '_t' of null
Error is generated here, though the source cause is external of this: https://github.com/kazupon/vue-i18n/blob/9ef7b798e3c1d58dfae4f25e5e6b8c488b86d819/dist/vue-i18n.js#L111

Need More Info

Most helpful comment

I having the same issue,
use this.$root.$t() in promise works for me

All 39 comments

Erm... I'm gonna close this for now as I need to narrow down more what's going on.

@SirLamer having the same issue, downgrading to ^5.x.x works for me

@SirLamer Have you investigated this problem? I just faced it today with vue 2.5.9 and vue-i18n 7.3.2

I think I avoided using vue-i18n in a promise context and haven't really confronted this issue again, sorry.

I know when I approached this before (above), after review I came to suspect I might have been doing something wrong.

@SirLamer but why did you close this issue? It's still reproducible.
It's really great to use this.$t for notifications (vue-notification) inside .then() or .catch().

image
I face this issue again with version 7.1.1. I use this.$t('xxxx') in a promise's then handler ( btw, it's a web app load in a Android webview, I think it may because user tap back button when request is loading )

Confirmed for me too with v7.6.0.
Happened in Promise#then(), but not all.
I have many promises using this plugin, the problem seems to be with long promises response time.

Can we reopen this ? I experienced the same error when using $t('xxx') inside a watcher callback.
version: [email protected]

As a workaround I stored the value outside the callback....

Uncaught TypeError: Cannot read property '_t' of null
    at VueComponent.eval (vue-i18n.esm.js?7467:179)
    at VueComponent.testPop (hello.vue?bfe5:219)
    at eval (hello.vue?bfe5:223)

happened some times when using this.$t('langname') (not every time)

the same

Can this please be reopened -- this is still an issue in the latest release (8.3.1). You cannot access this.$t(...) inside of a Promise callback even though the rest of the Vue component works as expected on the this object.

@dmcknight26
Could you kindly prepare a reproducible code?

Lots of reopen requests so it shall be so.

I can confirm this is also an issue for me.

Sorry for the confusion but this does appear to be working correctly (at least it is for me now). After trying to create a test case I found the root of the problem which had to do with my application code. In short, the component in question, this, was in the process of being destroyed while also being used inside of some promises.

Honestly I'm not sure if this is really a bug or not, if I try to access other objects on this they still work correctly, e.g. this.$store, and even this.$t resolves to the current function -- it just doesn't work when used. I could probably throw together a test case if you think it's worth investigating...

OK, here's a test case: http://jsfiddle.net/jL7edyau/

Clicking the Remove button should cause the item to disappear and a success message to be displayed, however if you try you will get a console error: Cannot read property '_t' of null.

I suspect this is because clicking the button triggers a store action & commit which removes the item from the store (and thus starts destroying the component?). Then, back in the component in the .then you can no longer use this.$t.

I having the same issue,
use this.$root.$t() in promise works for me

I having the same issue,
use this.$root.$t() in promise works for me

works, thanks. But it's just a workaround no real solution for this issue, right?

the funny think is on my webserver the app does not respond anymore when the error occurs. locally built for prod the error occurs but the app still works/react.

btw: i use vue-cli

Thank you volks

I had same problem, so I found a examples of nuxt.js official website:
https://nuxtjs.org/examples/i18n
following this examples configuration your project it will work.
don't forget about check that i18n.js of middleware and vuex folder.

{{$t('item')}} throws [Vue warn]: Error in render: "TypeError: Cannot read property '_t' of undefined" on 8.10.0 as well.

I had the same problem, this problem occurs only when I destroy the component, then looking at the source file I found beforeDestroy function:

image

This error happens because before the component is destroyed this function sets _i18n to null. To solve this, you can use the solution @hunterliu1003 or store the values in some variable out of the promise

In extend.js: Can't we simply put in a null check and print a warning if it is null everytime this.$i18n is accessed?
It is very cumbersome to have checks everywhere that test if the component was already destroyed.

I'm experiencing the same issue with [email protected] when using the new <MountingPortal> component.

Replacing $t() with this.$root.$t() doesn't seem to work.

I'm experiencing the same issue with [email protected] when using the new <MountingPortal> component.

Replacing $t() with this.$root.$t() doesn't seem to work.

I think this is because portal-vue docs (https://portal-vue.linusb.org/api/mounting-portal.html#props)

All of these props are not dynamic. 
That means changing their values after initial render will have no effect.

this example shows that $t worked in <MountingPortal> component.
But after changing locale, $t('mesage.hello') didn't update the value.
https://codepen.io/hunterliu1003/pen/axVzJg

In master, beforeDestroy now looks as follows:

beforeDestroy (): void {
    if (!this._i18n) { return }

    const self = this
    this.$nextTick(() => {
      if (self._subscribing) {
        self._i18n.unsubscribeDataChanging(self)
        delete self._subscribing
      }

      if (self._i18nWatcher) {
        self._i18nWatcher()
        self._i18n.destroyVM()
        delete self._i18nWatcher
      }

      if (self._localeWatcher) {
        self._localeWatcher()
        delete self._localeWatcher
      }

      self._i18n = null
    })
  }

The this.$nextTick construct was added in version 8.8 to address #499.

Wouldn't both #499 and this ticket be solved if we just drop the self._i18n = null line? The component is destroyed anyway moment later. I'd even say that the this.$nextTick construct isn't needed anymore then, but I might miss something there.

Happy to make the (very simple) pull.

@everhardt
Thanks for having eyes on this issue !!
PRs are welcome !!

Hi,
@everhardt I hope you don't mind I just made a PR as we just ran in the same issue. I did not touch the $nextTick stuff

I don't mind at all, the sooner this is fixed the better!

Still having this issue. Assigning variables outside the promise still causes the errors for me. using 8.14

That would make sense: the pull (#690) has not yet been merged (and there is no release yet that includes it).

I made another sample to reproduce the issue: https://jsfiddle.net/dskwp1ru/6/

This is still an issue, at least for us...
It seems that the PR (#690) was closed...
It would be nice to have a good solution for this... It's quite common to have async functions using this.$t in components. There must be a solution!

Maybe wait for all running promises to resolve before undefining this._i18n ?
Or maybe a "safe" $t function (maybe named $st) that would not throw an error if this._i18n is undefined and that could be used in promises?

I agree. I don't understand why not setting self._i18n = null might introduce a memory leak. If there are no references to it any more, won't it be removed by the garbage collector automatically?

If that's the case, the solution doesn't require any new function, only dropping the self._i18n = null line as the pull request did.

I agree. It is strange :) I used idea https://github.com/kazupon/vue-i18n/issues/184#issuecomment-446591824 and implemented wrapper

const I18nPlugin = {
  install (Vue, options) {
    const _$t = Vue.prototype.$t
    Vue.prototype._$t = _$t

    Vue.prototype.$t = function () {
      if (this.$i18n) {
        return _$t.apply(this, arguments)
      } else {
        return _$t.apply(this.$root, arguments)
      }
    }
  }
}

// ....
Vue.use(VueI18n)
Vue.use(I18nPlugin)

I agree. I don't understand why not setting self._i18n = null might introduce a memory leak. If there are no references to it any more, won't it be removed by the garbage collector automatically?

If that's the case, the solution doesn't require any new function, only dropping the self._i18n = null line as the pull request did.

@redevening, could you please provide your view on this? You rejected https://github.com/kazupon/vue-i18n/pull/690, so you probably had reasons we have missed here.

@kazupon I think @redevening is not going to respond any time soon. What more information would you need to get this fixed? In my opinion #690 is a fine solution and good to go (see https://github.com/kazupon/vue-i18n/pull/690#discussion_r327501732).

Works fine locally and in staging, but on production with more data it fails during async

Example for Composition API users:

setup(props, vm) {

  const state = reactive({
    name: vm.root.$i18n.t("titles.overview"),
    ...
  })

I created a sample application showing a working example
https://github.com/code1x1/vue-translation-test
it writes to requireMessage in a Promise.resolve the value returned by this.$t('general.form.input.required') which is working fine.
src/components/base/validation/form-validation.tsx

In Vue I18n 8.x, you can avoid this comment.
https://github.com/kazupon/vue-i18n/issues/184#issuecomment-446591824

Close the issue once, so we need to organize.

Was this page helpful?
0 / 5 - 0 ratings