DEV Community

AriesAlex
AriesAlex

Posted on • Updated on

Nuxt 3 i18n on server&client

There's a lot of tutorials on how to setup multi-lingual support on client-side. But I didn't find any tutorial for doing it on nuxt server-side so I did it manually. So there it is!

Client-side setup

First of all let's look at how I did it on client-side

  1. Installing i18n module dependency

I used edge version

Manually adding to package.json:

   {
     "devDependencies": {
       "@nuxtjs/i18n": "npm:@nuxtjs/i18n-edge"
     }
   }
Enter fullscreen mode Exit fullscreen mode

then npm i

  1. Configuring in nuxt.config.ts
   import locales from './locales'

   export default defineNuxtConfig({
     ...
     modules: ['@nuxtjs/i18n'],
     i18n: {
       lazy: true,
       langDir: 'locales',
       locales,
       defaultLocale: 'en',
       detectBrowserLanguage: false,
       strategy: 'no_prefix',
     },
     ...
   })
Enter fullscreen mode Exit fullscreen mode

I have chosen locales folder for my .json translations and also have dedicated locales.ts file to list them:

   export default [
     {
       code: 'en',
       name: 'English',
     },
     {
       code: 'ru',
       name: 'Русский',
     },
   ].map(lang => ({ file: lang.code + '.json', ...lang }))
Enter fullscreen mode Exit fullscreen mode

Example json translation files:

locales/en.json

   {
     "pageTitle": "Example page",
     "unknownError": "Unknown error happend!"
   }
Enter fullscreen mode Exit fullscreen mode

locales/ru.json

   {
     "pageTitle": "Страница для примера",
     "unknownError": "Произошла неизвестная ошибка!"
   }
Enter fullscreen mode Exit fullscreen mode

I extracted list of locales as locales.ts to dedicated file because we will also use them in server-side

As you can see I have disabled detectBrowserLanguage and strategy to disable auto language detect and prefixes that allows to, for example, redirect from /en to / with the language set as English

The reason why is because right now it's buggy in nuxt and can't even detect my browser language so I implemented it on my own what you will see later

  1. In app.vue at the very end of script setup:
   const { t } = useI18n()
   await useConfigureI18n()

   useHead({
     title: computed(() => t('pageTitle')),
   })
Enter fullscreen mode Exit fullscreen mode

I called my custom useConfigureI18n composable to detect browser's language and load/save it from/to cookie

Then I already used t to set page title

Btw I strongly recommend using I18n Ally extension that can show translations in your code, automatically translate them for you using google translation for free and also find and extract raw strings into translations

  1. composables/useConfigureI18n.ts:
   export default async () => {
     const { getBrowserLocale, setLocale, locale } = useI18n()

     const savedLang = useCookie('lang')

     watch(locale, newLocale => {
       if (savedLang.value != newLocale) savedLang.value = newLocale
     })

     await setLocale(savedLang.value || getBrowserLocale() || 'en')
   }
Enter fullscreen mode Exit fullscreen mode

Here I automatically detect browser language and syncronize it with cookie lang

  1. Language selector

We already can use t to use translated messages but we also need to let users to select their language

   <template>
       <select v-model="locale">
       <option
           v-for="locale in locales"
           :key="locale.code"
           :label="locale.name"
           :value="locale.code"
       />
       </select>
   </template>

   <script setup lang="ts">
   import locales from '@/locales'
   const { locale } = useI18n()
   </script>
Enter fullscreen mode Exit fullscreen mode

Everything is ready on the client!

Server-side setup

For using I18n I decided to create my own middleware that will add $t in event context for event handlers in our endpoints

server/middleware/i18n.ts:

import { createI18n } from 'vue-i18n'
import locales from '../../locales'
import fs from 'fs-extra'

const i18n = createI18n({
  fallbackLocale: 'en',
}).global

for (const locale of locales) {
  i18n.setLocaleMessage(locale.code, fs.readJSONSync('locales/' + locale.file))
}

export default defineEventHandler(e => {
  e.context.$t = (key: string) =>
    i18n.t(key, getCookie(e, 'lang') || i18n.fallbackLocale.toString())
})

declare module 'h3' {
  interface H3EventContext {
    $t: typeof i18n.t
  }
}
Enter fullscreen mode Exit fullscreen mode

We already have user's language in cookie so we can use it to detect user's locale. So I created my wrapper of default t that already passes user's locale

I used fs-extra module. You can install it or use default fs and then JSON.parse(fs.readFileSync('locales/' + locale.file))

And also we extending default H3EventContext interface so TypeScript will know that we have $t

Then we can use it in our endpoints like so:

export default defineEventHandler(async e => {
  return e.context.$t('unknownError')
})
Enter fullscreen mode Exit fullscreen mode

You can check code in my cool pet project to compare

That's all! We finally have i18n in our app. Happy coding and using machine translation to make users tear their hair out!

Top comments (0)