nuxt / test-utils

🧪 Test utilities for Nuxt
http://nuxt.com/docs/getting-started/testing
MIT License
323 stars 84 forks source link

mountSuspended with i18n #585

Closed ronaldkonjer closed 11 months ago

ronaldkonjer commented 1 year ago

I'm setting up the nuxt-vitest module in my project. Though I'm running into some issues getting a simple test, I use mountSuspended(Logo) to test the mounting of a Logo component. This component uses i18n. Whereas when I'm using mount from Vitest the test passes.

The error I'm getting is as follows:

Vitest caught 1 unhandled error during the test run.
This might cause false positive tests. Resolve unhandled errors to make sure your tests are not affected.

⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯ Unhandled Rejection ⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯
SyntaxError: Need to install with `app.use` function
 ❯ Module.createCompileError ../node_modules/.pnpm/@intlify+message-compiler@9.3.0-beta.17/node_modules/@intlify/message-compiler/dist/message-compiler.mjs:54:19
 ❯ createI18nError ../node_modules/.pnpm/vue-i18n@9.3.0-beta.17_vue@3.3.4/node_modules/vue-i18n/dist/vue-i18n.runtime.mjs:97:34
     96|     [I18nErrorCodes.INVALID_ARGUMENT]: 'Invalid argument',
     97|     [I18nErrorCodes.MUST_BE_CALL_SETUP_TOP]: 'Must be called at the top of a `setup` function',
     98|     [I18nErrorCodes.NOT_INSLALLED]: 'Need to install with `app.use` function',
       |                                  ^
     99|     [I18nErrorCodes.UNEXPECTED_ERROR]: 'Unexpected error',
    100|     [I18nErrorCodes.NOT_AVAILABLE_IN_LEGACY_MODE]: 'Not available in legacy mode',
 ❯ Module.useI18n ../node_modules/.pnpm/vue-i18n@9.3.0-beta.17_vue@3.3.4/node_modules/vue-i18n/dist/vue-i18n.runtime.mjs:2253:15
 ❯ setup app.vue:33:40

And while we are at it. Is there a way to test with the translated i18n messages iso the keys?

Here are some snippets from my configuration. vitest.config.js

...
export default defineVitestConfig({
  resolve: {
    alias
  },
  test: {
    dir: tests,
    setupFiles: ['tests/unit.setup.ts'],
    environment: 'jsdom',
    globals: true,
    root: rootDir,
    environmentOptions: {
      nuxt: {
        rootDir
      }
    }
  }
})

./test/nuxt/components/Logo.nuxt.spec.ts (with mountSuspended)

import { describe, it, expect } from 'vitest'

import { mountSuspended } from 'vitest-environment-nuxt/utils'
import Logo from '@/components/header/Logo.vue'

describe('HeaderLogo', () => {
  it('can mount some component', async () => {
    const component = await mountSuspended(Logo)
    expect(component.vm).toBeTruthy()
    expect(component.text()).toMatchInlineSnapshot(
      '"global.logo-brand-name.global.logo-brand-localehomepage.logo-tagline"'
    )
  })
})

./tests/unit.setup.ts

import { config } from '@vue/test-utils'
import { createI18n } from 'vue-i18n'

import nlNL from '~/locales/nl_NL.json'

const i18n = createI18n({
  legacy: false,
  locale: 'nl_NL',
  missing: (_, key) => key,
  messages: {
    nlNL
  }
})

config.global.mocks = {
  t: msg => msg
}
config.global.plugins.push(i18n)

./tests/nuxt/components/Logo.nuxt.spec.ts (without mountSuspended)

import { mount } from '@vue/test-utils'
import { describe, it, expect } from 'vitest'

import Logo from '@/components/header/Logo.vue'

describe('HeaderLogo', () => {
  it('can mount some component', () => {
    const component = mount(Logo)
    expect(component.vm).toBeTruthy()
    expect(component.text()).toMatchInlineSnapshot(
      '"global.logo-brand-name.global.logo-brand-localehomepage.logo-tagline"'
    )
  })
})
danimm commented 1 year ago

same problem here. I can't seem to set up an environment where I can have the vitest and i18n modules running at the same time 😅

rinux55 commented 1 year ago

I've had a similar issue with a slightly different setup where a different @vue/test-utils was being called during a mount function, so it did not have the global mocks.

I've solved it by setting an alias in the resolve section of my config file:

//vitest.config.ts
resolve: {
      alias: [
          {
              find: '@vue/test-utils',
              replacement: '/node_modules/@vue/test-utils/dist/vue-test-utils.cjs.js'
          }
      ]
  }

Does that work for you?

ronaldkonjer commented 1 year ago

So I added your suggested resolve alias and changed the package of the mountSuspended import. And now mountSuspended has not thrown the SyntaxError: Need to install with app.use function error.

Although my tests pass now I still have some unhandled errors thrown by Vitest which seem to originate in the Nuxt layout component.

TypeError: default[props.name] is not a function
 ❯ setup node_modules/.pnpm/nuxt@3.6.5_@types+node@20.4.5_eslint@8.46.0_typescript@5.1.6/node_modules/nuxt/dist/app/components/layout.js:25:76
     26|       type: [String, Boolean, Object],
     27|       default: null
     28|     }
       |     ^
     29|   },
     30|   setup(props, context) {

     ❯ callWithErrorHandling node_modules/.pnpm/@vue+runtime-core@3.3.4/node_modules/@vue/runtime-core/dist/runtime-core.cjs.js:156:18
 ❯ setupStatefulComponent node_modules/.pnpm/@vue+runtime-core@3.3.4/node_modules/@vue/runtime-core/dist/runtime-core.cjs.js:7190:25
 ❯ setupComponent node_modules/.pnpm/@vue+runtime-core@3.3.4/node_modules/@vue/runtime-core/dist/runtime-core.cjs.js:7151:36
 ❯ mountComponent node_modules/.pnpm/@vue+runtime-core@3.3.4/node_modules/@vue/runtime-core/dist/runtime-core.cjs.js:5555:7
 ❯ processComponent node_modules/.pnpm/@vue+runtime-core@3.3.4/node_modules/@vue/runtime-core/dist/runtime-core.cjs.js:5521:9
 ❯ patch node_modules/.pnpm/@vue+runtime-core@3.3.4/node_modules/@vue/runtime-core/dist/runtime-core.cjs.js:5007:11
 ❯ ReactiveEffect.componentUpdateFn [as fn] node_modules/.pnpm/@vue+runtime-core@3.3.4/node_modules/@vue/runtime-core/dist/runtime-core.cjs.js:5664:11
 ❯ ReactiveEffect.run node_modules/.pnpm/@vue+reactivity@3.3.4/node_modules/@vue/reactivity/dist/reactivity.cjs.js:182:19
 ❯ instance.update node_modules/.pnpm/@vue+runtime-core@3.3.4/node_modules/@vue/runtime-core/dist/runtime-core.cjs.js:5770:51

I remove the alias again to determine if it has anything to do with this error being thrown. But it points out that in my config changing the import package did the trick, not adding the alias you suggested.

import { mountSuspended } from 'vitest-environment-nuxt/utils'
=>
import { mountSuspended } from 'nuxt-vitest/utils'

If anyone has an idea what is causing this error is thrown while running any vitest test. Please enlighten me.

igbominadeveloper commented 1 year ago

I am also using @nuxtjs/i18n package for translation and when I run my tests, I get this error:

SyntaxError: Need to install with `app.use` function
 ❯ Module.createCompileError ../node_modules/.pnpm/@intlify+message-compiler@9.3.0-beta.27/node_modules/@intlify/message-compiler/dist/message-compiler.node.mjs:78:19
 ❯ createI18nError ../node_modules/.pnpm/vue-i18n@9.3.0-beta.27/node_modules/vue-i18n/dist/vue-i18n.mjs:107:34
    105|     [I18nErrorCodes.UNEXPECTED_RETURN_TYPE]: 'Unexpected return type in composer',
    106|     [I18nErrorCodes.INVALID_ARGUMENT]: 'Invalid argument',
    107|     [I18nErrorCodes.MUST_BE_CALL_SETUP_TOP]: 'Must be called at the top of a `setup` function',
       |                                  ^
    108|     [I18nErrorCodes.NOT_INSTALLED]: 'Need to install with `app.use` function',
    109|     [I18nErrorCodes.UNEXPECTED_ERROR]: 'Unexpected error',
 ❯ Module.useI18n ../node_modules/.pnpm/vue-i18n@9.3.0-beta.27/node_modules/vue-i18n/dist/vue-i18n.mjs:2291:15
 ❯ setup components/global/ToolbarAccountMenu.vue:8:7
 ❯ callWithErrorHandling ../node_modules/.pnpm/@vue+runtime-core@3.3.4/node_modules/@vue/runtime-core/dist/runtime-core.cjs.js:156:18
 ❯ setupStatefulComponent ../node_modules/.pnpm/@vue+runtime-core@3.3.4/node_modules/@vue/runtime-core/dist/runtime-core.cjs.js:7190:25
 ❯ setupComponent ../node_modules/.pnpm/@vue+runtime-core@3.3.4/node_modules/@vue/runtime-core/dist/runtime-core.cjs.js:7151:36
 ❯ mountComponent ../node_modules/.pnpm/@vue+runtime-core@3.3.4/node_modules/@vue/runtime-core/dist/runtime-core.cjs.js:5555:7
 ❯ processComponent ../node_modules/.pnpm/@vue+runtime-core@3.3.4/node_modules/@vue/runtime-core/dist/runtime-core.cjs.js:5521:9
 ❯ patch ../node_modules/.pnpm/@vue+runtime-core@3.3.4/node_modules/@vue/runtime-core/dist/runtime-core.cjs.js:5007:11

⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯
Serialized Error: {
  "code": 24,
  "domain": undefined,
}
ai-sakib commented 1 year ago

I am getting this error. Still not fixed !

test

asiz15 commented 1 year ago

Same problem here. I have tried many alternatives to be able to test components but none of them work correctly. I believe that component testing is essential for development of any type, not having a way to perform this type of testing is a huge blocker. @danielroe Do you have information about official support for this feature or what you recommend to solve this?

srcdev commented 1 year ago

Just stumbled across. I've managed to get some successful i18n tests.

I stumbled for a while on only being able to access the i18n key when I had a requirement to test the actual value.

The repo is public if it helps anyone. There's still lots I'm trying to figure out myself.

Here's my setup.

// vitest.config.ts
import { fileURLToPath } from "node:url";
import { defineVitestConfig } from "nuxt-vitest/config";

export default defineVitestConfig({
  test: {
    globals: true,
    include: "**/*.nuxt.spec.ts",
    environment: "nuxt",
    environmentOptions: {
      nuxt: {
        rootDir: fileURLToPath(new URL("./", import.meta.url)),
      },
    },
    setupFiles: ["./tests/setupFiles/i18n.ts"],
  },
});
// tests/setupFiles/i18n.ts
import { config } from "@vue/test-utils";
import { createI18n } from "vue-i18n";
import messages from "@/locales/";
import { useRootStore } from "@/stores/store.root";

const rootStore = useRootStore();
const i18n = createI18n({
  legacy: false,
  globalInjection: true,
  locale: rootStore.locale,
  locales: rootStore.locales,
  fallbackLocale: rootStore.fallbackLocale,
  messages: messages,
});

config.global.plugins.push(i18n);
// Header.nuxt.spec.t
import { mountSuspended } from "nuxt-vitest/utils";
import { describe, it, expect } from "vitest";
import Header from "./Header.vue";
import { useAccountStore } from "@/stores/store.account";
import { useRootStore } from "@/stores/store.root";

let initialPropsData = {
  someProp: "value1",
};

let wrapper;
const wrapperFactory = (propsData = {}) => {
  const mockPropsData = Object.keys(propsData).length > 0 ? propsData : initialPropsData;

  return mountSuspended(Header, {
    props: mockPropsData,
  });
};

describe("Header", () => {
  const accountStore = useAccountStore();
  const rootStore = useRootStore();

  it("Mounts", async () => {
    expect(useAppConfig()).toBeTruthy();
  });

  it("Component i18n html", async () => {
    wrapper = await wrapperFactory();
    const textCheck = wrapper.find("h1");
    expect(textCheck.html()).toMatchInlineSnapshot('"<h1 data-v-b96573a8=\\"\\" class=\\"text-header-large text-color-orange\\">[en] Header text from i18n dynamic imports</h1>"');
  });

  it("Shared i18n text", async () => {
    wrapper = await wrapperFactory();
    const textCheck = wrapper.find("[data-test-id='shared-text-test']");
    expect(textCheck.text()).toEqual("[en] Sample shared title entry");
  });

  it("Store value entry", async () => {
    wrapper = await wrapperFactory();
    const textCheck = wrapper.find("[data-test-id='store-test']");
    expect(textCheck.text()).toEqual("someString value");
  });

  it("Store value updated", async () => {
    wrapper = await wrapperFactory();
    rootStore.someString = "Some new value";
    expect(rootStore.someString).toBe("Some new value");
    await nextTick();
    const textCheck = wrapper.find("[data-test-id='store-test']");
    expect(textCheck.text()).toEqual("Some new value");
  });

  it("Store value patched", async () => {
    wrapper = await wrapperFactory();
    rootStore.$patch({ someString: "Another new value" });
    expect(rootStore.someString).toBe("Another new value");
    await nextTick();
    const textCheck = wrapper.find("[data-test-id='store-test']");
    expect(textCheck.text()).toEqual("Another new value");
  });

  it("should render default props within nuxt suspense", async () => {
    const props = {
      someProp: "value1",
    };
    wrapper = await wrapperFactory(props);
    const textCheck = wrapper.find("[data-test-id='props-test']");
    expect(textCheck.text()).toEqual("value1");
  });

  it("should render store signed out within nuxt suspense", async () => {
    wrapper = await wrapperFactory();
    accountStore.signedIn = false;
    expect((accountStore.signedIn = false)).toBeFalsy();
    await nextTick();
    const storeCheck = wrapper.find("[data-test-id='account-state-test']");
    expect(storeCheck.text()).toEqual("[en] Signed out");
  });

  it("should render store signed in within nuxt suspense", async () => {
    wrapper = await wrapperFactory();
    accountStore.signedIn = true;
    expect((accountStore.signedIn = true)).toBeTruthy();
    await nextTick();
    const storeCheck = wrapper.find("[data-test-id='account-state-test']");
    expect(storeCheck.text()).toEqual("[en] Signed in");
  });
});
danielroe commented 12 months ago

If someone is still experiencing this, would you provide a reproduction?

I've opened a sample PR here with an runtime test with i18n which seems to be working fine: https://github.com/nuxt/test-utils/pull/633

github-actions[bot] commented 12 months ago

Would you be able to provide a reproduction? 🙏

More info ### Why do I need to provide a reproduction? Reproductions make it possible for us to triage and fix issues quickly with a relatively small team. It helps us discover the source of the problem, and also can reveal assumptions you or we might be making. ### What will happen? If you've provided a reproduction, we'll remove the label and try to reproduce the issue. If we can, we'll mark it as a bug and prioritise it based on its severity and how many people we think it might affect. If `needs reproduction` labeled issues don't receive any substantial activity (e.g., new comments featuring a reproduction link), we'll close them. That's not because we don't care! At any point, feel free to comment with a reproduction and we'll reopen it. ### How can I create a reproduction? We have a couple of templates for starting with a minimal reproduction: 👉 https://stackblitz.com/github/nuxt/test-utils/tree/main/examples/app-vitest 👉 https://stackblitz.com/github/nuxt/test-utils/tree/main/examples/app-jest 👉 https://stackblitz.com/github/nuxt/test-utils/tree/main/examples/module A public GitHub repository is also perfect. 👌 Please ensure that the reproduction is as **minimal** as possible. See more details [in our guide](https://nuxt.com/docs/community/reporting-bugs/#create-a-minimal-reproduction). You might also find these other articles interesting and/or helpful: - [The Importance of Reproductions](https://antfu.me/posts/why-reproductions-are-required) - [How to Generate a Minimal, Complete, and Verifiable Example](https://stackoverflow.com/help/mcve)
github-actions[bot] commented 11 months ago

This issue was closed because it was open for 7 days without a reproduction.

sarayourfriend commented 11 months ago

@danielroe I have a reproduction in a public repository, WordPress/openverse, where we're attemping to migrate to Nuxt 3 and are running into this very problem.

Unfortunately things are a bit messy there still, because we're also still working through other issues: https://github.com/WordPress/openverse/pull/3571

You can reproduce the issue by running pnpm run test:unit v-image-cell inside the frontend directory.

What I'm seeing when I run vitest in the frontend directory is that globalThis.useNuxtApp().$i18n has a working vuei18n instance. I can pass it keys and I get back the right strings. However, if I retrieve vueApp from globalThis.__unctx__.get("nuxt-app").vueApp and then access vueApp._context.provides[vueApp.__VUE_I18N_SYMBOL__], that vuei18n object has zero configured instances, it doesn't look installed. Though, I'm not sure I'm looking at this right. I'm not familiar with the guts of vue-i18n and have just been digging through the test-utils code to figure out how this module works to set up the render function's context with the configured Nuxt environment.

If you'd like me to open a separate issue for this, please let me know. The exact same presentation for us as in this issue:

vue:setup
  [nuxt-app] vue:setup: 259.213ms []
vue:error
  [nuxt-app] vue:error: 0.816ms [
    SyntaxError: Need to install with `app.use` function
        at Module.createCompileError (/var/home/sara/projects/openverse/node_modules/.pnpm/@intlify+message-compiler@9.8.0/node_modules/@intlify/message-compiler/dist/message-compiler.mjs:78:19)
        at createI18nError (/var/home/sara/projects/openverse/node_modules/.pnpm/vue-i18n@9.8.0_vue@3.3.12/node_modules/vue-i18n/dist/vue-i18n.mjs:105:34)
        at Module.useI18n (/var/home/sara/projects/openverse/node_modules/.pnpm/vue-i18n@9.8.0_vue@3.3.12/node_modules/vue-i18n/dist/vue-i18n.mjs:2313:15)
        at setup (/var/home/sara/projects/openverse/frontend/src/components/VImageCell/VImageCell.vue:138:11)
        at clonedComponent.setup (/var/home/sara/projects/openverse/node_modules/.pnpm/@nuxt+test-utils@3.9.0_@testing-library+vue@8.0.1_@vitest+ui@1.1.0_@vue+test-utils@2.4.3_h3@1_prr4umkikytza6o2cai2dcbbye/node_modules/@nuxt/test-utils/dist/runtime-utils/index.mjs:215:37)
        at callWithErrorHandling (/var/home/sara/projects/openverse/node_modules/.pnpm/@vue+runtime-core@3.3.12/node_modules/@vue/runtime-core/dist/runtime-core.cjs.js:156:18)
        at setupStatefulComponent (/var/home/sara/projects/openverse/node_modules/.pnpm/@vue+runtime-core@3.3.12/node_modules/@vue/runtime-core/dist/runtime-core.cjs.js:7285:25)
        at setupComponent (/var/home/sara/projects/openverse/node_modules/.pnpm/@vue+runtime-core@3.3.12/node_modules/@vue/runtime-core/dist/runtime-core.cjs.js:7246:36)
        at mountComponent (/var/home/sara/projects/openverse/node_modules/.pnpm/@vue+runtime-core@3.3.12/node_modules/@vue/runtime-core/dist/runtime-core.cjs.js:5643:7)
        at processComponent (/var/home/sara/projects/openverse/node_modules/.pnpm/@vue+runtime-core@3.3.12/node_modules/@vue/runtime-core/dist/runtime-core.cjs.js:5609:9) {
      code: 27,
      domain: undefined
    },
    {
      image: [Getter/Setter],
      searchTerm: [Getter/Setter],
      aspectRatio: [Getter/Setter],
      kind: [Getter/Setter],
      relatedTo: [Getter/Setter]
    },
    'setup function'
  ]
page:finish
  [nuxt-app] page:finish: 261.044ms []

cc @obulat who is the Openverse maintainer actually leading our Nuxt 3 migration effort. I'm just lending a hand today.

sarayourfriend commented 11 months ago

Actually, I was able to reproduce this inside the application: https://github.com/nuxt/test-utils/pull/673

The issue is with the useI18n composable. Using the template (which the example didn't test either) via $t OR using useNuxtApp().$i18n does work. So it seems like there's some kind of vue-i18n wiring that is getting lost in the Nuxt environment construction.

renky commented 10 months ago

Can confirm @sarayourfriend It's just happening when using

const { t } = useI18n()

after changing all my t()-function-calls to

useNuxtApp().$i18n.t()

and removing the above useI18n-composable call, the error is gone.

peterleilei86 commented 10 months ago

@renky It is indeed gone but useNuxtApp().$i18n().t() cannot be used if you want component based translation. It can only be done using useI18n like

const { t } = useI18n({useScope: 'local', messages: { en: { test: 'test' } } } })

i18n component option does not work either for local scoped translations!

martio commented 9 months ago

Spent two days on this. It's worth fixing this issue. I solved the problem by mocking the "vue-i18n" module and assigning it to global plugins

tests/setup.ts

import { beforeAll, afterAll, vi } from 'vitest'
import { ref } from 'vue'
import { config } from '@vue/test-utils'
import { createI18n } from 'vue-i18n'

beforeAll(() => {
  vi.mock('vue-i18n', async (importOriginal) => {
    const module = await importOriginal<typeof import('vue-i18n')>()

    return {
      ...module,
      useI18n: () => ({
        locale: ref('en'),
        locales: ref([
          {
            name: 'English',
            code: 'en',
            iso: 'en-GB',
            file: 'en.ts',
          },
        ]),
      }),
    }
  })
})

afterAll(() => {
  vi.restoreAllMocks()
})

// setup i18n
const i18n = createI18n({
  legacy: false,
  locale: 'en',
  locales: [
    {
      name: 'English',
      code: 'en',
      iso: 'en-GB',
      file: 'en.ts',
    },
  ],
  messages: {},
  missing: (locale: string, key: string) => key,
})

config.global.plugins.push(i18n)
maciekiwaniuk commented 9 months ago

@martio solution work for me, thank you

sniperadmin commented 7 months ago

@martio Solution is perfect :)

rigtigeEmil commented 1 week ago

Spent two days on this. It's worth fixing this issue. I solved the problem by mocking the "vue-i18n" module and assigning it to global plugins

tests/setup.ts

import { beforeAll, afterAll, vi } from 'vitest' import { ref } from 'vue' import { config } from '@vue/test-utils' import { createI18n } from 'vue-i18n'

beforeAll(() => { vi.mock('vue-i18n', async (importOriginal) => { const module = await importOriginal<typeof import('vue-i18n')>()

return {
  ...module,
  useI18n: () => ({
    locale: ref('en'),
    locales: ref([
      {
        name: 'English',
        code: 'en',
        iso: 'en-GB',
        file: 'en.ts',
      },
    ]),
  }),
}

}) })

afterAll(() => { vi.restoreAllMocks() })

// setup i18n const i18n = createI18n({ legacy: false, locale: 'en', locales: [ { name: 'English', code: 'en', iso: 'en-GB', file: 'en.ts', }, ], messages: {}, missing: (locale: string, key: string) => key, })

config.global.plugins.push(i18n)

This does not work for me, is there something missing before this is doable (or am I just missing an obvious step)? [Vue warn]: Directive "t" has already been registered in target app.