Home > OS >  UPDATED: Calling i18n too early inside composable
UPDATED: Calling i18n too early inside composable

Time:02-04

Updated from my original question. The content of the question remains the same, i only updated the scope. I am looking forwdard to call my Auth store, which calls a composable, which calls i18n. The problem begins when i try to call my Auth Store from the router.beforeEach callback. I can freely use this composable, except for the usecase inside the router guard callback.

router.ts

import { createRouter, createWebHistory } from '@ionic/vue-router'
import routes from '~pages'
const router = createRouter({
  history: createWebHistory(import.meta.env.BASE_URL),
  routes,
})
router.beforeEach((to) => {
  const publicPages = ['/login']
  const authRequired = !publicPages.includes(to.path)
  const auth = useAuthStore()
  if (authRequired && !auth.auth) {
    auth.returnUrl = to.fullPath
    return '/login'
  }
})

export default router

main.ts

import { createApp } from 'vue'
import { createPinia } from 'pinia'
import { IonicVue } from '@ionic/vue'
import { defineCustomElements } from '@ionic/pwa-elements/loader'
import { createI18n } from 'vue-i18n'
import messages from '@intlify/unplugin-vue-i18n/messages'
import App from './App.vue'
import router from '~/modules/router'
// Omited several CSS imports
import 'uno.css'

defineCustomElements(window)
const pinia = createPinia()
const i18n = createI18n({
  legacy: false,
  locale: 'en',
  messages,
})
const app = createApp(App)
  .use(IonicVue)
  .use(i18n)
  .use(pinia)
  .use(router)
router.isReady().then(() => { app.mount('#app') })

Yet i am seeing this error: MSEdge_Console_Erros

My Pinia store is a as simple as an authentication store would look like:

import { acceptHMRUpdate, defineStore } from 'pinia'
import type { Auth } from '~/types'
export const useAuthStore = defineStore('auth', () => {
  const { success, error, warn } = useToastManager()
    const auth = ref<Auth | null>(JSON.parse(localStorage.getItem('auth')!) ?? null)
  const returnUrl = ref('')
  const { httpClient } = useHttpClient()
  const isLogin = computed(() => {
    return auth.value !== null
  })
  function login(username: string, password: string): Promise<boolean> {
    isLoading.value = true
    return new Promise((resolve, reject) => {
      httpClient.post<Auth>('/api/Account/Login',
        {
          username,
          password,
        }).then(async (response) => {
        auth.value = response.data
        localStorage.setItem('auth', JSON.stringify(response.data))
        success('Login', 'Silent')
        resolve(true)
      }).catch(async (e) => {
        error('Login', e, e.code)
      })
    })
  }
  async function logoff() {
    auth.value = null
  }
  return { login, logoff, isLogin, auth, returnUrl }
})

if (import.meta.hot)
  import.meta.hot.accept(acceptHMRUpdate(useAuthStore, import.meta.hot))

Here is my composable for managing toasts.

import { toastController } from '@ionic/vue'
import { alertCircleOutline, checkmarkCircleOutline, helpCircleOutline } from 'ionicons/icons'
import { useI18n } from 'vue-i18n'
const errorTimeout = 3000
const successTimeout = 1000
export function useToastManager() {
  const { t } = useI18n()
  const success = async (action: string, message: string) => {
    isLoading.value = false
    const toast = await toastController.create({
      message: t('success'),
      duration: successTimeout,
      position: 'bottom',
      color: 'success',
      icon: checkmarkCircleOutline,
    })
    if (message === 'Silent')
      return
    if (action === 'Fetch')
      return
    await toast.present()
  }
  const error = async (action: string, message: string, code?: string) => {
    isLoading.value = false
    let showMessage = ''
    switch (code) {
      case 'ERR_NETWORK':
        showMessage = t('network_error')
        break
      case 'ERR_BAD_RESPONSE':
        showMessage = t('invalid_operation')
        break
      case 'ERR_BAD_REQUEST':
        showMessage = t('invalid_operation')
        if (action === 'Login')
          showMessage = t('err')
        break
      case 'ECONNABORTED':
        showMessage = t('timeout_failed_operation')
        break
      default:
        showMessage = 'No err code found'
    }
    const toast = await toastController.create({
      message: showMessage,
      duration: errorTimeout,
      position: 'top',
      color: 'danger',
      icon: alertCircleOutline,
      // onclick: detailedAlert(action, message)
    })
    await toast.present()
  }
  const warn = async (action: string, message: string) => {
    isLoading.value = false
    const toast = await toastController.create({
      message: `${message}`,
      duration: errorTimeout,
      position: 'bottom',
      color: 'warning',
      icon: helpCircleOutline,
    })
    await toast.present()
  }
  return { success, error, warn }
}

CodePudding user response:

It is probably caused by calling const { t } = useI18n() in the composable. i18n is complaining that you call the use-function not from a setup function: 'Uncaught SyntaxError: Must be called at the top of a setup function'.

In a large project where there we use options and composition api, we use the global t function as a work-around.

Make a i18n module i18n.ts:

import { createI18n } from 'vue-i18n'
import messages from '@intlify/unplugin-vue-i18n/messages'

export const i18n = createI18n({
  legacy: false,
  locale: 'en',
  messages,
})

Import this in your main.ts and call it like you in your example (.use(i18n)).

Then in your composable, import the i18n module and get the global t function from it:

import { i18n } from '..../i18n'; // import your i18n.ts file
const { t } = i18n.global;
  • Related