Configuring vue-i18n with TypeScript ensures type safety for your Vue 3 localization.
Localizing a Vue application is not just about translating strings. On one project with 8 languages, we ended up with a bloated bundle of 2 megabytes solely due to statically loading all translations. The client complained about slow TTFB and low LCP. We had to rewrite to lazy loading — the bundle shrunk by 5 times, and Core Web Vitals metrics returned to normal. Such a story repeats often: developers underestimate the impact of translations on performance.
Proper configuration of vue-i18n not only solves performance issues but also common pitfalls: incorrect pluralization for Russian, conflicts with SSR during hydration, and lack of key typing. Our experience — 5+ years with Vue, 20+ localization projects, and 5 years on the market — guarantees a reliable solution that works from the first time. Basic configuration starts from $500, full integration from $1500.
Official vue-i18n documentation recommends using Composition API and lazy loading for large projects. We adhere to this approach and adapt it to specific tasks.
Why use vue-i18n?
Vue I18n is the official extension fully integrated with Vue 3. It supports Composition API, lazy loading, date/number formatting, and SSR via Nuxt. Compared to custom solutions, vue-i18n provides ready-made mechanisms for pluralization and interpolation, which cuts development time in half. Additionally, it is the industry standard for the Vue ecosystem.
How to solve the SSR hydration problem?
With SSR, Vue I18n can cause hydration errors due to locale mismatch between server and client. The solution is to synchronize the locale via a cookie or request header, and also use sync: false in configuration. In Nuxt, the @nuxtjs/i18n module does this automatically by reading the locale from accept-language and saving it in a cookie.
Example configuration with cookie
// nuxt.config.ts export default defineNuxtConfig({ i18n: { detectBrowserLanguage: { useCookie: true, cookieKey: 'i18n_redirected', alwaysRedirect: true } } }) When is lazy loading of translations needed?
For applications with dozens of locales, loading all translations at startup is an unsustainable luxury. The initial bundle can grow by megabytes, which is critical for LCP and TTI. Lazy loading only loads the current language, others on demand. Lazy loading is 10x more efficient than static loading — it reduces bundle size by 10 times and load time by 6.6 times.
| Approach | Bundle Size (10 languages) | Load Time | UX |
|---|---|---|---|
| Static loading | ~1 MB | 2 seconds | - |
| Lazy loading | ~100 KB | 0.3 seconds | + |
How we configure vue-i18n
Installation and basic configuration
npm install vue-i18n@9 // src/i18n/index.ts import { createI18n } from 'vue-i18n' import ru from './locales/ru.json' import en from './locales/en.json' export type MessageSchema = typeof ru const i18n = createI18n<[MessageSchema], 'ru' | 'en'>({ legacy: false, locale: 'ru', fallbackLocale: 'en', messages: { ru, en }, datetimeFormats: { ru: { short: { day: 'numeric', month: 'short', year: 'numeric' } }, en: { short: { month: 'short', day: 'numeric', year: 'numeric' } } }, numberFormats: { ru: { currency: { style: 'currency', currency: 'RUB' } }, en: { currency: { style: 'currency', currency: 'USD' } } } }) export default i18n Key features
- Pluralization: supports up to 4 forms for Russian.
- Date and number formatting: built-in formatters tied to locale.
- TypeScript: strict key typing via MessageSchema.
- Lazy loading: load translations only for the current language.
How to organize lazy loading of translations?
For large applications, it is critical not to load all translations at once. We implement it via dynamic imports:
// src/i18n/index.ts const i18n = createI18n({ legacy: false, locale: 'ru', messages: { ru } }) const loaded = new Set(['ru']) export async function loadLocale(locale: string) { if (loaded.has(locale)) return const msgs = await import(`./locales/${locale}.json`) i18n.global.setLocaleMessage(locale, msgs.default) loaded.add(locale) } Use it in the router:
router.beforeEach(async (to) => { const locale = to.params.locale || 'ru' await loadLocale(locale) i18n.global.locale.value = locale }) How does configuration differ for Nuxt?
For Nuxt, we use the official module that automatically integrates with vue-i18n, supports SSR and SEO. Configuration is minimal:
// nuxt.config.ts export default defineNuxtConfig({ modules: ['@nuxtjs/i18n'], i18n: { locales: [ { code: 'ru', language: 'ru-RU', file: 'ru.json', name: 'Русский' }, { code: 'en', language: 'en-US', file: 'en.json', name: 'English' } ], defaultLocale: 'ru', lazy: true, langDir: 'locales/', strategy: 'prefix_except_default' } }) What is included in our service for setting up i18n?
Our service delivers: documentation of all translation keys, code examples with TypeScript types, setup of lazy loading and localized routes, support for adding new languages, and 30-day post-launch support.
| Stage | Duration | Result |
|---|---|---|
| Requirements analysis | 0.5 days | List of languages, keys, format |
| Plugin configuration | 0.5 days | Working basic localization |
| Integration with router | 1 day | Localized routes |
| Lazy loading setup | 1 day | Minimal bundle on startup |
| Testing | 0.5 days | Check all languages and pluralization |
Timeframes and cost
Basic vue-i18n setup for 2 languages takes 1 day. Full integration with Nuxt, lazy loading, and localized routes — 2–3 days. Basic configuration from $500, full integration from $1500. The cost of the service is determined individually after analyzing your project's requirements.
We support the implementation: we help with nuances of Russian pluralization (four forms vs two in English), set up strict key typing via TypeScript, and document rules for adding translations for the entire team. This eliminates duplicate keys and hardcoded strings in components. As a result, adding a new language takes 2–4 hours instead of several days.
Get a consultation — contact us to discuss the details. We guarantee that after configuration, your Vue application will correctly display in all target languages without sacrificing performance.







