Recomendación: Begin with a centralized i18n setup using Vue 3's createI18n and a lightweight locale loader. Keep the active locale in a const, expose a composable for getting and switching locale, and store messages in JSON files named by locale (en.json, fr.json, es.json). This approach is made for readability and makes it easier for translators and developers to contribute, with the most straightforward workflow. The scope includes dates, numbers, and pluralization within the same notation, so components can rely on a single source of truth and callers can react to locale changes in real time. It also helps teams understand the translation flow from context to rendered text.
When selecting locales, especially right-to-left languages like Arabic or Hebrew, set the document direction to rtl and flip layouts with a small utility that toggles a data-dir attribute on the root. This keeps the UI localized and prevents slow layout shifts during switches. The UI shows localized text consistently and avoids broken typography during calling translations on user actions.
Loading strategy matters: lazy-load locale messages so the initial bundle stays lean. Use a dynamic import such as import('./locales/${locale}.json') and cache results in a const map. This reduces initial latency and keeps space usage controlled. On each event that changes language, refresh only the needed keys instead of recomputing the entire context.
Key design: adopt a clear notation for message keys, such as common.button.save or errors.user.notFound. This preserves scope and makes keys consistent across most locales. Document after how keys map to UI context and avoid embedding HTML in messages to keep rendering predictable.
Automation and QA: establish a pipeline that extracts strings, generates translation requests, and validates coverage per locale. Use space for namespacing and keep each locale file aligned to a single schema. The result is a smooth flow where selecting languages renders consistently and developers focus on features rather than translation logistics.
Practical steps to bootstrap a multilingual Vue 3 project
Begin with a minimal Vite + Vue 3 scaffold and install a dedicated i18n tool to enable locale handling from the start.
-
Architecture plan: designate a src/locales folder with per-locale files named en.json, fr.json, etc. Use formatsimple JSON to simplify collaboration and provide a clean list of translations for a large-scale project, something that teams can extend easily as part of the architecture.
-
Install core packages: npm i vue-i18n@9 @intlify/vite-plugin-vue-i18n --save; ensure the package.json already includes scripts for dev and build so you can bootstrap quickly, as told by the setup guide.
-
Initialize i18n: create a single src/i18n/index.ts, export a configured i18n instance, and set a sensible fallbackLocale like en; this gives you true control over how locale data flows through components.
-
Dynamic loading: leverage import.meta.glob to import all src/locales/*.json files and build a messages map that links each locale to its translations; this speeds up development and keeps files organized.
-
Format and sources: support formatsimple JSON locally and allow remote urls for production; consider a dejson workflow to convert endpoint payloads into the expected JSON structure when needed.
-
Locale detection: detect the initial locale from localStorage or a URL query, fallback to navigator.language, and persist the choice to localStorage so the setting survives reloads.
-
Fetching strategy: fetch translations after startup or on navigation to reduce the initial payload; preload the most common locales to avoid layout shifts and faster first paint.
-
Synchronization: enable synchronized locale state across open tabs using a storage event; when a user switches language, all tabs reflect the change with negligible delay.
-
Platform readiness: verify behavior across platforms–SPA, SSR, and PWA–by testing routing, hydration, and offline access; adjust caching for translation files to guarantee quick startup.
-
Quality checks: implement tests for missing keys, plural rules, and formatters; create a dedicated page listing sample strings in each locale to validate consistency and coverage.
-
Documentation and governance: publish an i18n guide in the repo, enumerate supported locales, and describe how to add new languages without breaking existing keys; this keeps the project maintainable as part of ongoing development.
Install and configure Vue 3 with Vite for i18n-ready apps
Use Vite to scaffold a Vue 3 project and install vue-i18n to enable i18n-ready apps. Run: npm create vite@latest my-app --template vue, then cd my-app and npm i vue-i18n@next. Ensure nodejs is available (Node.js 14+ recommended) to support development in nodejs environments. This setup is approachable for projects of any size, including a real-world aplicación that needs clear localization strategy.
Create a compact i18n module and register it in main.js. The approach keeps messages distinct per locale and supports collaborative teams in large-scale projects. Begin with a locales directory and a simple JSON structure like:
// src/locales/en.json
{
"message": {
"hello": "Hello world"
}
}
// src/locales/es.json
{
"message": {
"hello": "Hola mundo"
}
}
Wire i18n into Vue by mounting it in main.js. See a minimal integration pattern that works with both composition and Options APIs:
// src/main.js
import { createApp } from 'vue';
import App from './App.vue';
import i18n from './i18n';
const app = createApp(App);
app.use(i18n);
app.mount('#app');
Provide a dedicated i18n module to export a single instance. This keeps the configuration easy to manage and ready for localization across components:
// src/i18n.js
import { createI18n } from 'vue-i18n';
import en from './locales/en.json';
import es from './locales/es.json';
export default createI18n({
locale: 'en',
fallbackLocale: 'en',
messages: { en, es }
});
Test and switch locales from the UI. A simple locale toggle updates the locale and lookups without reloading the page. Inline example shows a straightforward approach:
// Switch locale (example)
import i18n from './i18n';
function setLocale(lang) {
i18n.global.locale = lang;
// optionally load messages if using lazy-loading
}
For performance, optimize bundle size by lazy-loading locales. The following pattern demonstrates a dynamic import-based flow to load a locale when needed, then register it with the i18n instance:
// Lazy-load a locale and register its messages (illustrative)
async function loadLocale(lang) {
const msgs = (await import(`./locales/${lang}.json`)).default;
i18n.global.setLocaleMessage(lang, msgs);
i18n.global.locale = lang;
}
| Step | Action / File | Notes |
|---|---|---|
| 1 | npm create vite@latest my-app --template vue cd my-app npm i vue-i18n@next | Prerequisites: Node.js (nodejs). Use npm or pnpm as your package manager. |
| 2 | src/i18n.js | Export a single i18n instance with locale messages for each language. |
| 3 | src/main.js | Attach i18n before mounting the app with app.use(i18n). |
| 4 | src/locales/en.json, src/locales/es.json | Define translation keys like message.hello for each locale. |
| 5 | Locale switch code | Expose a function to set i18n.global.locale and optionally load messages lazily. |
| 6 | Build | Run npm run build and verify that locale changes reflect in production bundles. |
Choose and integrate the i18n library (vue-i18n) and set initial locale
Install vue-i18n (v9) for Vue 3 and create a centralized i18n instance. This custom tool keeps locale, translation objects, and fallbacks in one place, enabling you to add new languages without touching components and ensuring it stays supported across your app.
Put the instance in a dedicated i18n.js file and wire it into the app: import createI18n from vue-i18n, define your messages, set the initial locale and a fallbackLocale, then export the instance and call app.use(i18n) before mounting. This upfront setup ensures all components access translations consistently and avoids flicker during startup.
Initial locale should be chosen upfront from user preference or the browser, with a sensible fallback such as 'en'. Read the saved locale from storage (if present), otherwise derive it from navigator.language and finally fall back to 'en'. Pass this locale to createI18n and keep fallbackLocale aligned with your most complete language keys.
Structure translations as nested objects to group related keys and simplify maintenance. For example, store en: { greeting: { morning: 'Good morning', evening: 'Good evening' } } and fr: { greeting: { morning: 'Bonjour', evening: 'Bonsoir' } }. Use dot-notation keys like greeting.morning and placeholders for dynamic data, e.g., 'Welcome, {name}'. A glossary helps keep terminology consistent across languages, and adding new terms remains straightforward.
If you need a turnkey translation workflow, consider usetolgee as a custom backend integration. This approach lets you fetch translations and keep a glossary up to date, while you map terms to placeholders and organize them within nested objects for clarity. Tasks like adding new strings and updating existing ones stay centralized and predictable.
Expose a small locale switcher with a clear, concise UI. Attach it to a compact control and apply a consistent style with classmt-2 for spacing. The switcher uses a handletogglecomplete callback to persist the choice, update the i18n locale, and store the preference locally so users stay on their selected language.
Within the app, validate that each locale has complete coverage for the keys you use, and look for placeholders or missing translations during tests. If a key is missing, add it to the corresponding nested object and re-run the test workflow. allen from the localization team often highlights gaps in the glossary to keep translations aligned with the glossary terms and style guidelines.
Define translation files: structure, namespaces, and fallback language
Create a dedicated i18n/locales folder at the project root, for example i18n/locales/en.json, i18n/locales/fr.json, i18n/locales/ar.json. Use formats: JSON for simplicity, YAML for readability, or JSON5 if your pipeline supports comments. This layout streamlines delivery and serve of translations to the UI and makes automation easy.
Define namespaces per feature: common, home, errors, dates, and validation. In each file, place an object per namespace, for example common and home as separate blocks to keep keys organized and avoid collisions. Maintain a glosario to document recurring terms so translators align on meanings; reference the glossary in your collaboration workflow and update the oneskys glossary as the project expands.
Adopt a consistent key naming convention using var_name style for dynamic placeholders, e.g., var_name stands in for runtime values like userName or itemCount. Keep keys short but descriptive and map them to values in all languages across the resources sets.
In the configuration, set a default locale and a fallbackLocale so missing keys revert to English or your primary language. Use createi18n to initialize the instance and ensure the fallback covers UI labels, dates, and formats.
For languages that require dirrtl or right-to-left layout, toggle direction at the root element and adjust margin with CSS so UI elements don’t collide. Consider a simple flag in the language data to signal RTL mode and apply it during app bootstrap via appmountapp.
Plan for resources and supporting files, keep delivery of translations tight, and run select validations before publishing. Check something like missing keys and language drift with a lightweight test suite. Use easy checks, and maintain a process that ensures translations reflect UI text across all formats and dates.
Finally, test with sample data, verify date formats, and check RTL rendering. Confirm that keys render correctly, keys map to the right namespace, and fallbacks load when a translation is missing. This approach keeps app initialization robust under appmountapp, supports collaboration, and yields predictable margin across locales.
Set up dynamic locale switching and language menu
Bind a dynamic locale switch to a selectlang control and persist the chosen locale in localStorage; then dynamically load and replace the active messages with vue3_vite_i18n to reflect the new language.
Store translations in a directory like src/locales and externalize them as dejson files to speed loading in applications.
Model the language data in a hierarchical structure: a list of languages and their region variants, so the UI can show a clear name for each locale and reflect it in the margin of the menu. In the scaffold, place the LocaleSwitcher component next to the header for a compact, accessible flow.
Render the menu with a button that toggles visibility, display the name of each locale, and allow selection from a list for múltiple locales. Use the previo selection to keep the current locale highlighted when the menu opens.
Step-by-step plan: 1) load translation files from the src/locales directory; 2) expose a selectlang dropdown bound to i18n.global.locale; 3) persist the choice in localStorage and apply it on startup; 4) on change, call i18n.global.locale = newLocale and replace the route or UI state to reflect the change; 5) test across applications to ensure all components react.
Organize codebase for scalable translations: components, routes, and shared phrases
Set up a centralized i18n module in src/i18n and initialize it from mainjs to guarantee a single source of truth for keys across the app. Place locale resources under resources, for example resources/en.json, resources/arabic.json, and ensure they load configured language data when the app starts. Use a deterministic file naming convention and export a stable key set to keep translators aligned.
Adopt a clear key hierarchy: module.section.key. This approach lets you generate typetext keys that map directly to UI elements, while keeping names human readable. Include a shared namespace (shared) for phrases used across routes and components, and keep core keys and display names in a separate core namespace to simplify maintenance and audits.
In components, use the composition API to access translation functions (for example, the t function) and bind strings through keys rather than hard-coded text. Keep component-specific phrases in resources per locale, but reference common phrases from shared. When a component displays Arabic text, ensure RTL layout is synchronized with the active locale to avoid layout shifts and slow reflows.
For routes, store titles, breadcrumbs, and route labels in per-language resources and load them lazily. Map route meta titles to translation keys and resolve them on navigation enter, so displayed labels reflect the current language without reloading the page. Maintain an explicit order for route keys to simplify review and updates during localizations.
Create a shared phrases file (resources/shared.json) for terms that recur across pages, such as action labels, status messages, and error prompts. Export this file alongside component and route resources to minimize duplication and guarantee consistency. Include a small glossary in resources/name to help translators understand domain terms and maintain a steady vocabulary across languages.
Define pipelines to manage growth: extract keys from code, generate resource files, review with translators, and export to platforms like onesky. Use a lightweight generator to create initial JSON files from typetext definitions, then adjust keys as the UI evolves. Synchronize resources across locales so displayed content remains aligned across components, routes, and shared phrases.
Configure pluginsi18n and related tooling to automate checks, lint translations, and verify key presence across locales. Integrate a translator workflow with resources, enter translated values, and export updates via onesky. Use a name-based export to publish language packs without breaking existing keys, and guarantee that each export preserves the original key structure.
Set initial locale handling in mainjs and provide a native fallback path. If a translation for a key is missing in a locale, display the key or a clear placeholder, then log the gap for translators. This guarantees a predictable UX while the team fills gaps in the Arabic or other languages.
In the build configuration (viteconfigjs), enable aliases for the i18n resources and ensure the locale loader chunks are separate from core app code to prevent a slow initial render. Keep mainjs lightweight by loading the i18n plugin early but lazy-loading language packs to minimize startup time. This approach aligns with a fast, responsive experience when a user enters the app and chooses a language.
When expanding to new languages, add a language name entry in resources and create a mirror set of keys for that locale. The process supports consistency and reduces risk as teams add Arabic, Spanish, or others. As translators work, you know the pipelines will generate updated resources and synchronize them with the app automatically.




