Vue-i18n: Vue.js Avoriaz unit test produces translation warnings when vue-i18n is used

Created on 20 Jul 2017  ·  24Comments  ·  Source: kazupon/vue-i18n

vue & vue-i18n version

vue: 2.2.2
vue-i18n: 7.0.5

Reproduction Link

https://github.com/ndabAP/Vue.js-with-Sails.js-backend-example-project/blob/master/frontend/test/unit/specs/Login.spec.js

Question at stackoverflow: https://stackoverflow.com/questions/45184859/vue-js-avoriaz-unit-test-produces-translation-warnings-when-vue-i18n-is-used

Steps to reproduce

Unit test a working translated Vue.js component with Mocha, PhantomJS and Karma.

What is Expected?

Don't get any warnings.

What is actually happening?

Multiple warnings that keys are not translated.

WARN LOG: '[vue-i18n] Cannot translate the value of keypath 'description.first'. Use the value of keypath as default.'

Most helpful comment

@ndabAP @kazupon I'm not sure if this is avoriaz specific or not?

I got this minimal example passing without warnings:

import Vue from 'vue'
import {
    mount
} from 'avoriaz'
import VueI18n from 'vue-i18n'
import Register from '../src/components/Bar.vue'
import { expect } from 'chai'

Vue.use(VueI18n)

describe('Register', () => {
    it('should accept inputs', () => {
        const messages = {
            en: {
                'description': 'Enter a name',
            },
            de: {
                'description': 'Gebe einen Namen ein',
            }
        }
        const i18n = new VueI18n({
            locale: 'en', // set locale,
            messages
        })
        const wrapper = mount(Register, { i18n })

        expect(wrapper.find('div')[0].text()).to.contain(messages.en.description)
    })
})
<template>
    <div>
      {{$t('description')}}
    </div>
</template>

<script>
    export default {
        name: 'bar',
        messages: {
            en: {
                'description': 'Enter a name',
            },
            de: {
                'description': 'Gebe einen Namen ein',
            }
        }
    }
</script>

All 24 comments

Thank you for your reporting!

I confirmed your reproduction codes.
In avoriaz, I seem that you need to specify i18n option to the options (second args) of mount function with similar uses as store of vuex.
/cc @eddyerburgh

ref:
https://github.com/eddyerburgh/avoriaz/blob/master/docs/guides/using-with-vuex.md

const wrapper = mount(Login, { Login.i18n })

Hi, @kazupon and thank you for your answer.

Unfortunately, it doesn't work.

This modified version also doesn't work:

const wrapper = mount(Login, {
  i18n: Login.i18n,
  store
})

@ndabAP @kazupon I'm not sure if this is avoriaz specific or not?

I got this minimal example passing without warnings:

import Vue from 'vue'
import {
    mount
} from 'avoriaz'
import VueI18n from 'vue-i18n'
import Register from '../src/components/Bar.vue'
import { expect } from 'chai'

Vue.use(VueI18n)

describe('Register', () => {
    it('should accept inputs', () => {
        const messages = {
            en: {
                'description': 'Enter a name',
            },
            de: {
                'description': 'Gebe einen Namen ein',
            }
        }
        const i18n = new VueI18n({
            locale: 'en', // set locale,
            messages
        })
        const wrapper = mount(Register, { i18n })

        expect(wrapper.find('div')[0].text()).to.contain(messages.en.description)
    })
})
<template>
    <div>
      {{$t('description')}}
    </div>
</template>

<script>
    export default {
        name: 'bar',
        messages: {
            en: {
                'description': 'Enter a name',
            },
            de: {
                'description': 'Gebe einen Namen ein',
            }
        }
    }
</script>

Thank you so much for testing this, @eddyerburgh. I still don't get it to work. This is my updated (still not working) setup.

New setup

it('should accept inputs', async() => {
    const state = {
      user: {
        name: '',
        password: ''
      }
    }

    const mutations = {
      SET_USER_NAME(state, name) {
        state.user.name = name
      },

      SET_USER_PASSWORD(state, password) {
        state.user.password = password
      }
    }

    const store = new Vuex.Store({
      state,
      mutations
    })

    const messages = {
      en: {
        'description.first': 'Enter a name',
        'label.first': 'Name *',
        'description.second': 'Enter a password',
        'label.second': 'Password *',
        'button.first': 'Create'
      },
      de: {
        'description.first': 'Gebe einen Namen ein',
        'label.first': 'Name *',
        'description.second': 'Gebe ein Passwort ein',
        'label.second': 'Passwort *',
        'figcaption.first': 'Du kannst einen dieser Nutzer wählen, um dich einzuloggen.',
        'button.first': 'Erstellen'
      }
    }

    const i18n = new VueI18n({
      locale: 'en',
      messages
    })

    const wrapper = mount(Register, {
      store,
      i18n
    })

    let name = 'Hans'
    let password = '123'

    let nameInput = wrapper.find('input')[0]
    let passwordInput = wrapper.find('input')[1]

    nameInput.element.value = name
    passwordInput.element.value = password

    nameInput.trigger('input')
    passwordInput.trigger('input')

    expect(wrapper.vm.$store.state.user.name).to.equal(name)
    expect(wrapper.vm.$store.state.user.password).to.equal(password)
  })

Output

20 07 2017 15:04:11.456:INFO [karma]: Karma v1.7.0 server started at http://0.0.0.0:9876/
20 07 2017 15:04:11.458:INFO [launcher]: Launching browser PhantomJS with unlimited concurrency
20 07 2017 15:04:11.485:INFO [launcher]: Starting browser PhantomJS
20 07 2017 15:04:11.769:INFO [PhantomJS 2.1.1 (Linux 0.0.0)]: Connected on socket LfhbcWV7VkmTecU3AAAA with id 62250953
WARN LOG: '[vue-i18n] Cannot translate the value of keypath 'description.first'. Use the value of keypath as default.'
WARN LOG: '[vue-i18n] Cannot translate the value of keypath 'label.first'. Use the value of keypath as default.'
WARN LOG: '[vue-i18n] Cannot translate the value of keypath 'description.second'. Use the value of keypath as default.'
WARN LOG: '[vue-i18n] Cannot translate the value of keypath 'label.second'. Use the value of keypath as default.'
WARN LOG: '[vue-i18n] Cannot translate the value of keypath 'button.first'. Use the value of keypath as default.'
WARN LOG: '[vue-i18n] Cannot translate the value of keypath 'description.first'. Use the value of keypath as default.'
WARN LOG: '[vue-i18n] Cannot translate the value of keypath 'label.first'. Use the value of keypath as default.'
WARN LOG: '[vue-i18n] Cannot translate the value of keypath 'description.second'. Use the value of keypath as default.'
WARN LOG: '[vue-i18n] Cannot translate the value of keypath 'label.second'. Use the value of keypath as default.'
WARN LOG: '[vue-i18n] Cannot translate the value of keypath 'button.first'. Use the value of keypath as default.'
WARN LOG: '[vue-i18n] Cannot translate the value of keypath 'description.first'. Use the value of keypath as default.'
WARN LOG: '[vue-i18n] Cannot translate the value of keypath 'label.first'. Use the value of keypath as default.'
WARN LOG: '[vue-i18n] Cannot translate the value of keypath 'description.second'. Use the value of keypath as default.'
WARN LOG: '[vue-i18n] Cannot translate the value of keypath 'label.second'. Use the value of keypath as default.'
WARN LOG: '[vue-i18n] Cannot translate the value of keypath 'button.first'. Use the value of keypath as default.'
WARN LOG: '[vue-i18n] Cannot translate the value of keypath 'description.first'. Use the value of keypath as default.'
WARN LOG: '[vue-i18n] Cannot translate the value of keypath 'label.first'. Use the value of keypath as default.'
WARN LOG: '[vue-i18n] Cannot translate the value of keypath 'description.second'. Use the value of keypath as default.'
WARN LOG: '[vue-i18n] Cannot translate the value of keypath 'label.second'. Use the value of keypath as default.'
WARN LOG: '[vue-i18n] Cannot translate the value of keypath 'button.first'. Use the value of keypath as default.'
WARN LOG: '[vue-i18n] Cannot translate the value of keypath 'description.first'. Use the value of keypath as default.'
WARN LOG: '[vue-i18n] Cannot translate the value of keypath 'label.first'. Use the value of keypath as default.'
WARN LOG: '[vue-i18n] Cannot translate the value of keypath 'description.second'. Use the value of keypath as default.'
WARN LOG: '[vue-i18n] Cannot translate the value of keypath 'label.second'. Use the value of keypath as default.'
WARN LOG: '[vue-i18n] Cannot translate the value of keypath 'button.first'. Use the value of keypath as default.'
WARN LOG: '[vue-i18n] Cannot translate the value of keypath 'description.first'. Use the value of keypath as default.'
WARN LOG: '[vue-i18n] Cannot translate the value of keypath 'label.first'. Use the value of keypath as default.'
WARN LOG: '[vue-i18n] Cannot translate the value of keypath 'description.second'. Use the value of keypath as default.'
WARN LOG: '[vue-i18n] Cannot translate the value of keypath 'label.second'. Use the value of keypath as default.'
WARN LOG: '[vue-i18n] Cannot translate the value of keypath 'button.first'. Use the value of keypath as default.'

  Register
    ✓ should accept inputs

PhantomJS 2.1.1 (Linux 0.0.0): Executed 1 of 1 SUCCESS (0.055 secs / 0.033 secs)
TOTAL: 1 SUCCESS

Any other ideas?

1. update

I made a little progress when I stub the $t function like this:

wrapper.vm.$t = sinon.stub()

There are still some warnings. @kazupon, is it possible to completely disable/stub the translation inside the VueI18n instance? I think about something like this:

const i18n = new VueI18n({
  locale: 'en',
  messages
})

i18n.$t = sinon.stub()

2. update

I tried to ignore the warnings inside my main.js:

const i18n = new VueI18n({
  locale: 'en',
  silentTranslationWarn: true
})

I also tried the "missing" handler. Both didn't work. Only setting the testing enviroment to production helped, because of:

https://github.com/kazupon/vue-i18n/blob/dev/src/index.js#L155

However, this is not a satisfiable solution. I'm still looking for a better one.

You need to setup the locale messages not 'description.first': 'Enter a name', but description: { first: 'Enter a name' }

@kazupon, still no success. I changed it in both, the Register.vue component and the test file Register.spec.js.

describe('Register', () => {
  it('should accept inputs', async () => {
    const messages = {
      en: {
        'description': {
          'first': 'Enter a name',
          'second': 'Enter a password'
        },
        'label': {
          'first': 'Name *',
          'second': 'Password *'
        },
        'button': {
          'first': 'Create'
        }
      },
      de: {
        'description': {
          'first': 'Enter a name',
          'second': 'Enter a password'
        },
        'label': {
          'first': 'Name *',
          'second': 'Password *'
        },
        'button': {
          'first': 'Create'
        }
      }
    }

    const i18n = new VueI18n({
      locale: 'en',
      messages
    })

    const state = {
      user: {
        name: '',
        password: ''
      }
    }

    const mutations = {
      SET_USER_NAME (state, name) {
        state.user.name = name
      },

      SET_USER_PASSWORD (state, password) {
        state.user.password = password
      }
    }

    const store = new Vuex.Store({
      state,
      mutations
    })

    const wrapper = mount(Register, {
      store,
      i18n
    })

    wrapper.vm.$t = sinon.stub()

    let name = 'Hans'
    let password = '123'

    let nameInput = wrapper.find('input')[0]
    let passwordInput = wrapper.find('input')[1]

    nameInput.element.value = name
    passwordInput.element.value = password

    nameInput.trigger('input')
    passwordInput.trigger('input')

    expect(wrapper.vm.$store.state.user.name).to.equal(name)
    expect(wrapper.vm.$store.state.user.password).to.equal(password)
  })
})

FYI works for me with the example from @eddyerburgh :+1: - seems not be a problem of vue-i18n or avoriaz

@ndabAP can you provide a minimal example, without the $t mock?

@defel,

Clone this repository and change this line to:

'process.env': require('../config/test.env')

Then do:

$ cd frontend/ && npm i && npm run test

@ndabAP sorry, but this is not a minimal example. See the example provided by @eddyerburgh - which works for me.

The question is, what are you doing different which triggers the error you have? There are too many moving parts at the moment, making it hard to spot what the cause of this problem is.

I meet the same issue when running unit test.
silentTranslationWarn is not work for test environment?

@defel it's not a minimal example but an example that demonstrates two things:

First: How I consume the translation API is correct, because it works fine out of any test context. If not, I use the API in a way that should be blocked.

Second: It doesn't work when running tests while using the same component that does work under a real environment.

I updated vue-i18n to the newest version, switched to vue-test-utils and still got the same issue.

Hi, regarding testing components with translations I think we could make testing easier if the component is wrapped in a HOC which passes only the part of the translations needed by the component as props. That way testing would be very easy as we do not need to mock i18n. The problem is I have only seen that solution for React:

https://www.youtube.com/watch?v=mwYHDXS6uSc

and I do not know how It could be implemented in Vue.

Hi @ndabAP,
I've managed to fix this problem by setting up the locale on the i18n Login component property and reusing it on the mount instead of creating a new VueI18n instance. The problem was that I already had i18n defined in the Login component options and (I assume) it was conflicting with the one that was being set up by the mount function.

    Login.i18n.locale = 'en';
    wrapper = mount(Login, {
      i18n: Login.i18n,
      store,
    });

@voxivoid your feedback is great! I already switched to another translation library (which is, unfortunately, far away from this one) but I'll revert some commits to check that out.

@ndabAP
Could you resolve this issue?
If so, I hope that close this issue. 🙏

Sorry for the delay @kazupon

I will test it!

Well, maybe I'm doing wrong but I had to do two things to get rid of the warnings:

  1. Disable strict mode because of Babel plugin
    Uncomment in _node_modules/babel-plugin-transform-es2015-modules-commonjs/lib/index.js_ line 166 where it says inherits: _babelPluginTransformStrictMode2.default,

  2. Set locale manually

Login.i18n = {locale: 'en'}
// ...
const vm = new Ctor({
  i18n: Login.i18n,
  store
}).$mount()

But with this, it works. I close the issue. Thanks for your attention!

Too many different version of "a fix". I also have the same issue, I am not sure what the solution is.

I will update the solution from @eddyerburgh to a currently working example:

import { shallowMount, createLocalVue } from '@vue/test-utils'
import VueI18n from 'vue-i18n'
import Register from '../src/components/Bar.vue'

describe('Register', () => {
    it('should accept inputs', () => {
        let localVue = createLocalVue()
        localVue.use(VueI18n)
        const messages = {
            en: {
                description: 'Enter a name',
            },
            es: {
                description: 'Ingresa tu nombre',
            }
        }
        const i18n = new VueI18n({
            locale: 'es', // set locale,
            fallbackLocale: 'es',
            messages
        })
        const wrapper = shallowMount(Register, { i18n, localVue })

        expect(wrapper.find('div')[0].text()).to.contain(messages.en.description)
    })
})

Bye!

This isn't working for me. Am I doing something wrong here?

import { shallowMount, createLocalVue } from '@vue/test-utils'
import BaseDialog from '@/components/BaseDialog/BaseDialog'
import VueI18n from 'vue-i18n'

describe('BaseDialog', () => {
  it('is called', () => {
    let localVue = createLocalVue()
    localVue.use(VueI18n)
    const messages = {
      gb: {
        'ui.universal.label.ok': 'OK',
        'ui.universal.label.cancel': 'Cancel'
      }
    }

    const i18n = new VueI18n({
      locale: 'gb',
      fallbackLocale: 'gb',
      messages
    })

    const wrapper = shallowMount(BaseDialog, {
      i18n,
      localVue
    })
    expect(wrapper.name()).toBe('BaseDialog')
    expect(wrapper.isVueInstance()).toBeTruthy()
  })
})

@mmelvin0581 The code I was working on is similar to yours. In my case the vue i18n was not getting the locale so it was complaining and falling back to default locale. Try to console the current locale and check whether you are getting any locale.. that could probably the reason

Hi, I know this is from long time ago, but I want to share my solution. And to explain it better, I want to match the texts inside my component after wrap.

I wasn't able to do this because the translations wasn't comming. This was my html part that I want to test before the solution: ... <span></span> ... . An empty span when my code was waiting for the translation: <span>{{ $t('menu.title') }}</span>

My solution:

import Vue from 'vue'
import VueI18n from 'vue-i18n'
import BootstrapVue from 'bootstrap-vue';

import { shallowMount, createLocalVue } from '@vue/test-utils'
import HeaderComponent from '@/components/HeaderComponent'
import ToggleMenu from '../../src/mixins/ToggleMenu'

import pt from '../../src/i18n/languages/pt-br/pt'

Vue.use(VueI18n)

describe.only('HeaderComponent', () => {
  let wrapper
  const localVue = createLocalVue()

  localVue.use(BootstrapVue)
  localVue.mixin(ToggleMenu)

  beforeEach(() => {
    const messages = {
      pt: pt
    }

    const i18n = new VueI18n({
      locale: 'pt',
      messages
    })

    wrapper = shallowMount(HeaderComponent, {
      localVue,
      i18n,
      stubs: ['router-link'],
      mixins: [ToggleMenu],
    })
  })

  it('ensure that HeaderComponent is a Vue instance', () => {
    expect(wrapper.isVueInstance()).toBeTruthy()
  })

  it('render the important elements', () => {
    expect(wrapper.contains('.menu-bar')).toBe(true)
    expect(wrapper.contains('#open_menu')).toBe(true)
    expect(wrapper.text()).toMatch('Menu');
    expect(wrapper.text()).toMatch('Sair');
  })
})

Now I have a fullfilled span: <span>Menu</span> and I am able to match all my text elements.

Was this page helpful?
0 / 5 - 0 ratings

Related issues

zhaohaodang picture zhaohaodang  ·  4Comments

abou7mied picture abou7mied  ·  3Comments

cslee picture cslee  ·  5Comments

koslo picture koslo  ·  5Comments

lucianobosco picture lucianobosco  ·  4Comments