N
Nuxt2d ago
Mirage

Dynamically load i18n messages based on context

I'm using @nuxtjs/i18n and was wondering if it's possible to dynamically load/switch messages based on context (in my case, context is organization). In my case, organizations are of different types/templates, and for some organization templates, I would like to switch the messages to use different terminology. I tried to achieve this using lazy loading:
// nuxt.config.ts
export default defineNuxtConfig({
i18n: {
// Module Options
strategy: 'no_prefix',
locales: [
{
code: 'en',
file: 'i18n/locales/en.ts'
}
],
lazy: true,
defaultLocale: 'eb',
experimental: {
localeDetector: './localeDetector.ts',
},
},
})
// nuxt.config.ts
export default defineNuxtConfig({
i18n: {
// Module Options
strategy: 'no_prefix',
locales: [
{
code: 'en',
file: 'i18n/locales/en.ts'
}
],
lazy: true,
defaultLocale: 'eb',
experimental: {
localeDetector: './localeDetector.ts',
},
},
})
// i18n/locales/en.ts
export default defineI18nLocale(async (locale) => {
const route = useRoute()
const organization = route.params.organization as string
return await $fetch(`/api/${organization}/i18n/${locale}`)
})
// i18n/locales/en.ts
export default defineI18nLocale(async (locale) => {
const route = useRoute()
const organization = route.params.organization as string
return await $fetch(`/api/${organization}/i18n/${locale}`)
})
// app.vue
<script setup lang="ts">
const { locale, loadLocaleMessages } = useI18n()
const organization = useOrganization()

watch(organization, async () => {
await loadLocaleMessages(locale)
}, {
immediate: true,
})
</script>
<template>
<div>
<NuxtRouteAnnouncer />
<!-- <VitePwaManifest /> -->
<NuxtLayout>
<NuxtPage />
</NuxtLayout>
</div>
</template>
// app.vue
<script setup lang="ts">
const { locale, loadLocaleMessages } = useI18n()
const organization = useOrganization()

watch(organization, async () => {
await loadLocaleMessages(locale)
}, {
immediate: true,
})
</script>
<template>
<div>
<NuxtRouteAnnouncer />
<!-- <VitePwaManifest /> -->
<NuxtLayout>
<NuxtPage />
</NuxtLayout>
</div>
</template>
However, because I'm using useRoute() in that handler, it results in the following error:
ssr:error Failed locale loading: [nuxt] A composable that requires access to the Nuxt instance was called outside of a plugin, Nuxt hook, Nuxt middleware, or Vue setup function. This is probably not a Nuxt bug. Find out more at `https://nuxt.com/docs/guide/concepts/auto-imports#vue-and-nuxt-composables`.
ssr:error Failed locale loading: [nuxt] A composable that requires access to the Nuxt instance was called outside of a plugin, Nuxt hook, Nuxt middleware, or Vue setup function. This is probably not a Nuxt bug. Find out more at `https://nuxt.com/docs/guide/concepts/auto-imports#vue-and-nuxt-composables`.
How can I best achieve dynamic loading of messages based on a context other than locale? (e.g. based on a certain route param)
5 Replies
kapa.ai
kapa.ai2d ago
Hi @Mirage. I'm kapa.ai, your friendly support bot. I will try to answer your question by searching through the knowledge base. :wave: ---------------------- To dynamically load i18n messages based on context (such as organization) in Nuxt 3 with @nuxtjs/i18n, you'll need to adjust your approach slightly. The error you're encountering is because useRoute() is being called outside of the Nuxt context. Here's a way to achieve what you're looking for: 1. First, let's modify your nuxt.config.ts:
export default defineNuxtConfig({
i18n: {
strategy: 'no_prefix',
locales: [
{
code: 'en',
file: 'i18n/locales/en.ts'
}
],
lazy: true,
defaultLocale: 'en',
customRoutes: 'config',
},
})
export default defineNuxtConfig({
i18n: {
strategy: 'no_prefix',
locales: [
{
code: 'en',
file: 'i18n/locales/en.ts'
}
],
lazy: true,
defaultLocale: 'en',
customRoutes: 'config',
},
})
2. Now, create a plugin to handle the dynamic loading of messages:
// plugins/i18n.ts
export default defineNuxtPlugin(async (nuxtApp) => {
const { $i18n } = nuxtApp
const route = useRoute()

const loadMessages = async () => {
const organization = route.params.organization as string
const locale = $i18n.locale.value
const messages = await $fetch(`/api/${organization}/i18n/${locale}`)
$i18n.setLocaleMessage(locale, messages)
}

nuxtApp.hook('app:mounted', loadMessages)
nuxtApp.hook('page:start', loadMessages)
})
// plugins/i18n.ts
export default defineNuxtPlugin(async (nuxtApp) => {
const { $i18n } = nuxtApp
const route = useRoute()

const loadMessages = async () => {
const organization = route.params.organization as string
const locale = $i18n.locale.value
const messages = await $fetch(`/api/${organization}/i18n/${locale}`)
$i18n.setLocaleMessage(locale, messages)
}

nuxtApp.hook('app:mounted', loadMessages)
nuxtApp.hook('page:start', loadMessages)
})
3. In your app.vue, you can remove the watch logic:
<template>
<div>
<NuxtLayout>
<NuxtPage />
</NuxtLayout>
</div>
</template>
<template>
<div>
<NuxtLayout>
<NuxtPage />
</NuxtLayout>
</div>
</template>
This approach uses a Nuxt plugin to handle the dynamic loading of messages. The plugin uses the useRoute() composable safely within the Nuxt context. It loads the messages when the app is mounted and on each page navigation. The loadMessages function fetches the messages based on the current organization (from route params) and locale, then sets them using $i18n.setLocaleMessage(). This solution avoids the error you were encountering and should allow you to dynamically load messages based on the organization context. Remember to adjust the API endpoint (/api/${organization}/i18n/${locale}) to match your actual backend structure. For more information on Nuxt plugins and hooks, you can refer to the Nuxt documentation on plugins (https://nuxt.com/docs/guide/directory-structure/plugins). Auto-imports documentation (https://nuxt.com/docs/guide/concepts/auto-imports#vue-and-nuxt-composables) provides more context on why certain composables need to be used within the correct Nuxt lifecycle. ---------------------- Note: you can ask me a follow up question by @ mentioning me again :speech_balloon: ----------------------
kapa.ai
kapa.ai2d ago
<@1188032231105630269> Kapa.ai is still learning and improving, please let me know how I did by reacting below.
Mirage
MirageOP2d ago
@kapa.ai Does the page:start hook awaits the callback to finish before loading the page? E.g. to make sure the new translations are loaded before displaying the page to the user.
kapa.ai
kapa.ai2d ago
Let me check the knowledge base for you again. One moment! :books: ---------------------- To answer your question about the page:start hook in Nuxt 3, let's look at the relevant information from the knowledge sources: According to the Nuxt documentation on hooks (https://nuxt.com/docs/api/advanced/hooks#app-hooks-runtime), the page:start hook is called on the Suspense (https://vuejs.org/guide/built-ins/suspense.html#suspense) pending event. However, it's important to note that this hook itself does not await the callback to finish before loading the page. If you want to ensure that new translations are loaded before displaying the page to the user, you might want to consider using the page:finish hook instead. The Nuxt documentation (https://nuxt.com/docs/api/advanced/hooks#app-hooks-runtime) describes this hook as being "Called on Suspense (https://vuejs.org/guide/built-ins/suspense.html#suspense) resolved event." To make sure the translations are loaded before the page is displayed, you could modify your plugin to use both hooks:
export default defineNuxtPlugin(async (nuxtApp) => {
const { $i18n } = nuxtApp
const route = useRoute()

const loadMessages = async () => {
const organization = route.params.organization as string
const locale = $i18n.locale.value
const messages = await $fetch(`/api/${organization}/i18n/${locale}`)
$i18n.setLocaleMessage(locale, messages)
}

nuxtApp.hook('page:start', () => {
// Set a loading state if needed
})

nuxtApp.hook('page:finish', async () => {
await loadMessages()
// Clear loading state if set
})
})
export default defineNuxtPlugin(async (nuxtApp) => {
const { $i18n } = nuxtApp
const route = useRoute()

const loadMessages = async () => {
const organization = route.params.organization as string
const locale = $i18n.locale.value
const messages = await $fetch(`/api/${organization}/i18n/${locale}`)
$i18n.setLocaleMessage(locale, messages)
}

nuxtApp.hook('page:start', () => {
// Set a loading state if needed
})

nuxtApp.hook('page:finish', async () => {
await loadMessages()
// Clear loading state if set
})
})
This approach ensures that the messages are loaded after the page component is resolved but before it's displayed to the user. For more precise control over the loading process, you might also want to look into the useLoadingIndicator (https://nuxt.com/docs/api/composables/use-loading-indicator#isloading) composable, which allows you to manually control the loading state of the Nuxt loading indicator. Remember, the exact behavior might depend on your specific Nuxt configuration and other factors in your application. Always test thoroughly to ensure the desired user experience. Nuxt documentation on hooks (https://nuxt.com/docs/api/advanced/hooks#app-hooks-runtime) Nuxt documentation on useLoadingIndicator (https://nuxt.com/docs/api/composables/use-loading-indicator#isloading) ---------------------- Note: you can ask me a follow up question by @ mentioning me again :speech_balloon: ----------------------
kapa.ai
kapa.ai2d ago
<@1188032231105630269> Kapa.ai is still learning and improving, please let me know how I did by reacting below.
Want results from more Discord servers?
Add your server