Can't use internationalization plugin i18n with Pinia Store

Viewed 1340

import { useToast } from "vue-toastification";
import { useI18n } from "vue-i18n";

export default function useToastMsg(message, type) {

  const { t } = useI18n();
  const toast = useToast()

  if (type == "success") {
    return toast.success(t(`${message}`))
  } else if (type == "error") {
    return toast.error(t(`${message}`))
  }

}

I am trying to use this function in the store after action has taken place. store.js import useToast from "@/composables/toast"

in action function:

useToast("submit", "success")

error message: Uncaught (in promise) SyntaxError: Must be called at the top of a setup function

How can I work around this?

3 Answers

This gave me some headache. But eventually I did came up with a supper simple solution.

Step one: Initialise i18n like this:

/* /utils/i18n.ts */

import { createI18n } from 'vue-i18n';
import messages from 'src/i18n';

const instance = createI18n({
    locale: 'en-US',
    messages,
});

export default instance;

export const i18n = instance.global;

Step 2: Add above i18n instance to your Vue app like this:

import { createApp } from 'vue';
import i18nInstance from '/utils/i18n.ts'

const app = createApp({
  // something vue options here ...
})

app.use(i18nInstance)
app.mount('#app')

So this is your usual i18n setup so far. Now, when you want to use it in pinia for example, you can do it like this:

import { defineStore } from 'pinia';
import { i18n } from '/utils/i18n';

const useStore = defineStore({
  id: 'StoreApp',
  state: () => {},
  getters: {
    getSomeTranslatedString() => i18n.t('String to be translated')
  },
  actions: {}
});

export default useStore;

useI18n() can only be used at the top of a setup() function because it requires using the Composition API's inject().

A workaround is to pass in $t() to useToastMsg():

// composables/toast.js
export default function useToastMsg(t, message, type) {
  //...
  return toast.success(t(`${message}`))
}
<template>
  <button @click="sayHello($t)">{{ $t('action.hello') }}</button>
</template>

<script>
import { defineComponent } from 'vue'
import { useStore } from '@/store'
import { mapActions } from 'pinia'

export default defineComponent({
  methods: {
    ...mapActions(useStore, ['sayHello']),
  },
})
</script>

demo

in store define a variable that contains i18n instance and define a getter to access it:

...
import { defineStore } from 'pinia';
import { Composer } from 'vue-i18n'; // WHEN USING TYPESCRIPT
...
const useStore = defineStore('store', {
  state: () =>
    ({
      $i18n: {}, // <== HERE
      ...
    }),
  getters: {
    // JS VERSION
    i18n() {
      return this.$i18n.global;
    },
    // TS VERSION
    i18n(): Composer {
      return this.$i18n.global as Composer;
    },
  },
  actions: ....
})

somewhere, better during app initialization, after pinia creation, create i18n and then assign its instance:

import { createI18n } from 'vue-i18n';
import { createPinia } from 'pinia';
...
// init Pinia
const pinia = createPinia();
app.use(pinia);
const store = useStore();

// init i18n
const i18n = createI18n({...})

// assign instance
store.$i18n = i18n;

then everywhere in the store will be available the i18n instance:

...
const useStore = defineStore('store', {
  state: ...
  getters: ...
  actions: {
    saySomething(): {
      toast.success(this.i18n.t('it works!')) // USE this.i18n
    }
  }
})
Related