{"id":6638,"date":"2025-08-07T06:00:00","date_gmt":"2025-08-07T06:00:00","guid":{"rendered":"https:\/\/poeditor.com\/blog\/?p=6638"},"modified":"2026-04-20T12:51:45","modified_gmt":"2026-04-20T12:51:45","slug":"vue-i18n","status":"publish","type":"post","link":"https:\/\/poeditor.com\/blog\/vue-i18n\/","title":{"rendered":"A complete guide to internationalizing Vue apps with vue-i18n"},"content":{"rendered":"\n<figure class=\"wp-block-image size-large\"><img loading=\"lazy\" decoding=\"async\" width=\"1024\" height=\"981\" src=\"https:\/\/poeditor.com\/blog\/wp-content\/uploads\/2025\/08\/vue-i18n-1024x981.png\" alt=\"vue i18n\" class=\"wp-image-6641\" srcset=\"https:\/\/poeditor.com\/blog\/wp-content\/uploads\/2025\/08\/vue-i18n-1024x981.png 1024w, https:\/\/poeditor.com\/blog\/wp-content\/uploads\/2025\/08\/vue-i18n-300x287.png 300w, https:\/\/poeditor.com\/blog\/wp-content\/uploads\/2025\/08\/vue-i18n-768x736.png 768w, https:\/\/poeditor.com\/blog\/wp-content\/uploads\/2025\/08\/vue-i18n-1536x1471.png 1536w, https:\/\/poeditor.com\/blog\/wp-content\/uploads\/2025\/08\/vue-i18n.png 1726w\" sizes=\"auto, (max-width: 1024px) 100vw, 1024px\" \/><\/figure>\n\n\n\n<p>If you\u2019re building Vue applications for real-world users, you\u2019ll eventually need to go beyond a single language. That means dealing with translations, formatting numbers and dates correctly, and supporting different locales.<\/p>\n\n\n\n<p>This is referred to as internationalization (i18n) and localization (l10n). The best way to implement it in Vue is with the <a href=\"https:\/\/www.npmjs.com\/package\/vue-i18n\" rel=\"nofollow\">vue-i18n package<\/a>.<\/p>\n\n\n\n<p>This guide walks through internationalizing your Vue app with <code>vue-i18n<\/code>. You\u2019ll learn how to structure translations, wire up locale switching, and manage translations cleanly as your app grows.<\/p>\n\n\n\n<h2 class=\"wp-block-heading\">Getting started<\/h2>\n\n\n\n<p>Before we dive into translations and language switching, let\u2019s make sure your project is set up. If you already have a <a href=\"https:\/\/vuejs.org\/\" rel=\"nofollow\">Vue 3<\/a> app running, just install the localization package:<\/p>\n\n\n\n<pre class=\"wp-block-code\"><code>npm install vue-i18n<\/code><\/pre>\n\n\n\n<p>If you don\u2019t have a project yet, here\u2019s how to <a href=\"https:\/\/v3.vitejs.dev\/guide\/\" rel=\"nofollow\">spin one up with Vite<\/a>:<\/p>\n\n\n\n<pre class=\"wp-block-code\"><code># create a new Vue 3 project with Vite\nnpm create vite@latest vue-i18n-demo -- --template vue\n\n# move into the project folder and install dependencies\ncd vue-i18n-demo\nnpm install\n\n# start the dev server\nnpm run dev\n<\/code><\/pre>\n\n\n\n<p>Once it\u2019s running, open <code>src\/App.vue<\/code> and replace the default markup with something a bit more realistic:<\/p>\n\n\n\n<pre class=\"wp-block-code\"><code>&lt;template&gt;\n  &lt;div class=\"container\"&gt;\n    &lt;h1&gt;Welcome to MyApp&lt;\/h1&gt;\n    &lt;p&gt;This is a simple localized Vue app.&lt;\/p&gt;\n\n    &lt;h2&gt;Ongoing Tasks&lt;\/h2&gt;\n    &lt;p&gt;You have {{ tasksCount }} tasks.&lt;\/p&gt;\n  &lt;\/div&gt;\n&lt;\/template&gt;\n\n&lt;script setup&gt;\nconst tasksCount = 3\n&lt;\/script&gt;<\/code><\/pre>\n\n\n\n<p>This gives us a basic UI to work with. Next, we\u2019ll set up <code>vue-i18n<\/code> and start replacing these hardcoded strings with translatable text.<\/p>\n\n\n\n<h2 class=\"wp-block-heading\">Basic text translation<\/h2>\n\n\n\n<p>Now that localization is set up, let\u2019s add some real translations and understand how it all fits together.<\/p>\n\n\n\n<p>The core idea behind <code>vue-i18n<\/code> is that instead of hard-coding text directly into your templates, you define it once using translation keys and then pull in the appropriate message depending on the current locale. This makes your app easier to scale across different languages and regions and keeps your UI clean and consistent.<\/p>\n\n\n\n<p>Let\u2019s start by adding a few sample messages. For now, we\u2019ll define them inline directly in main.js so it\u2019s easy to see what\u2019s happening. Later on, we\u2019ll move them to external files, which is how things usually work in production.<\/p>\n\n\n\n<p>Open <code>main.js<\/code> and update it like this:<\/p>\n\n\n\n<pre class=\"wp-block-code\"><code>import { createApp } from 'vue'\nimport { createI18n } from 'vue-i18n'\nimport App from '.\/App.vue'\n\nconst i18n = createI18n({<\/code><\/pre>\n\n\n\n<p>In the code above, we\u2019re using <code>createI18n()<\/code> to create an i18n instance and pass in our messages \u2014 an object containing translations for each locale. Inside each locale, the translations are grouped under a namespace (<code>app<\/code> in this case) to keep things structured and easier to manage as the app grows.<\/p>\n\n\n\n<p>Once that\u2019s in place, we can start using these translations in the app\u2019s template using the <code>$t()<\/code> function, which stands for \u201ctranslate.\u201d For example:<\/p>\n\n\n\n<pre class=\"wp-block-code\"><code>&lt;template&gt;\n  &lt;div class=\"container\"&gt;\n    &lt;h1&gt;{{ $t('app.welcome') }}&lt;\/h1&gt;\n    &lt;p&gt;{{ $t('app.description') }}&lt;\/p&gt;\n    &lt;p&gt;{{ $t('app.tasks', { count: tasksCount }) }}&lt;\/p&gt;\n  &lt;\/div&gt;\n&lt;\/template&gt;\n\n&lt;script setup&gt;\nconst tasksCount = 3\n&lt;\/script&gt;<\/code><\/pre>\n\n\n\n<p>Here, <code>$t('app.welcome')<\/code> fetches the welcome message based on the active locale, while <code>$t('app.tasks', { count: tasksCount })<\/code> shows how to pass dynamic values. This would be explained better later in this guide.<\/p>\n\n\n\n<h3 class=\"wp-block-heading\">Add a basic language switcher<\/h3>\n\n\n\n<p>vue-i18n exposes the current locale on <code>$i18n.locale<\/code>, so you can bind that to a dropdown and let users switch on the fly. Like this:<\/p>\n\n\n\n<pre class=\"wp-block-code\"><code>&lt;select v-model=\"$i18n.locale\"&gt;\n  &lt;option value=\"en\"&gt;English&lt;\/option&gt;\n  &lt;option value=\"fr\"&gt;Fran\u00e7ais&lt;\/option&gt;\n&lt;\/select&gt;<\/code><\/pre>\n\n\n\n<p>When a user picks a different language, vue-i18n automatically updates the UI. You don\u2019t need to reload the page or re-render components manually.<\/p>\n\n\n\n<figure class=\"wp-block-image\"><img decoding=\"async\" src=\"https:\/\/lh7-rt.googleusercontent.com\/docsz\/AD_4nXcRL7BkCg-HTyYZMUposh5ANHvTthNFktya9dOvtylNdSqG9uxmn-634rW12EAx96478O7x4J4MeS5k3srCvUruHhGvkVPig4DPYCduZadvDU4X6A_Tap41dq3evpK8FvRFAFAp?key=nT2JcH9FdAElcAxIbjOGRQ\" alt=\"\"\/><\/figure>\n\n\n\n<h2 class=\"wp-block-heading\">Extracting translations into JSON files<\/h2>\n\n\n\n<p>The inline <code>messages<\/code> setup is fine when you&#8217;re just getting started. But once your app grows past a couple of screens, or you start supporting more than one or two languages, it&#8217;s better to split translations into dedicated JSON files.<\/p>\n\n\n\n<p>This keeps things clean and also makes it easier to hand off strings to translators or sync with a translation management platform later on.<\/p>\n\n\n\n<p>Here\u2019s a common structure:<\/p>\n\n\n\n<pre class=\"wp-block-code\"><code>src\/\n\u2514\u2500 locales\/\n   \u251c\u2500 en.json\n   \u2514\u2500 fr.json<\/code><\/pre>\n\n\n\n<p>Each file holds a nested object of translation keys and values:<\/p>\n\n\n\n<pre class=\"wp-block-code\"><code>\/\/ en.json\n{\n  \"app\": {\n    \"welcome\": \"Welcome to MyApp\",\n    \"description\": \"This is a simple localized Vue app.\",\n    \"tasks\": \"You have {count} tasks.\"\n  }\n}<\/code><\/pre>\n\n\n\n<p><br>The structure stays the same across languages:<\/p>\n\n\n\n<pre class=\"wp-block-code\"><code>\/\/ fr.json\n{\n  \"app\": {\n    \"welcome\": \"Bienvenue sur MyApp\",\n    \"description\": \"Ceci est une application Vue localis\u00e9e.\",\n    \"tasks\": \"Vous avez {count} t\u00e2ches.\"\n  }\n}\n<\/code><\/pre>\n\n\n\n<p>Then update your <code>main.js<\/code> to import them:<\/p>\n\n\n\n<pre class=\"wp-block-code\"><code>import en from '.\/locales\/en.json'\nimport fr from '.\/locales\/fr.json'\n\nconst i18n = createI18n({\n  locale: 'en',\n  fallbackLocale: 'en',\n  messages: { en, fr }\n})\n\n<\/code><\/pre>\n\n\n\n<p>That\u2019s it. Now you\u2019re not hard-coding translations directly into your config, and your app is set up to scale, both in terms of code and workflow.<\/p>\n\n\n\n<h2 class=\"wp-block-heading\">Managing translations with POEditor<\/h2>\n\n\n\n<p>Once you start supporting multiple languages or if you&#8217;re working with other people, managing translation files manually can get tedious fast. That\u2019s where translation management platforms like <a href=\"\/product_tour\/\">POEditor<\/a> come in handy.<\/p>\n\n\n\n<p>Instead of editing JSON files by hand, you upload them to POEditor, where you (or your team) can manage all the translations through a web interface.<\/p>\n\n\n\n<p>To get started, export your existing translations into JSON (if you haven\u2019t already), then upload them to your POEditor project.<\/p>\n\n\n<div class=\"wp-block-image\">\n<figure class=\"aligncenter size-large\"><img loading=\"lazy\" decoding=\"async\" width=\"1024\" height=\"569\" src=\"https:\/\/poeditor.com\/blog\/wp-content\/uploads\/2025\/08\/angular-project-1024x569.jpg\" alt=\"\" class=\"wp-image-6849\" srcset=\"https:\/\/poeditor.com\/blog\/wp-content\/uploads\/2025\/08\/angular-project-1024x569.jpg 1024w, https:\/\/poeditor.com\/blog\/wp-content\/uploads\/2025\/08\/angular-project-300x167.jpg 300w, https:\/\/poeditor.com\/blog\/wp-content\/uploads\/2025\/08\/angular-project-768x427.jpg 768w, https:\/\/poeditor.com\/blog\/wp-content\/uploads\/2025\/08\/angular-project-1536x854.jpg 1536w, https:\/\/poeditor.com\/blog\/wp-content\/uploads\/2025\/08\/angular-project.jpg 1571w\" sizes=\"auto, (max-width: 1024px) 100vw, 1024px\" \/><\/figure>\n<\/div>\n\n\n<p>You can do this manually through the dashboard, or automate it with the <a href=\"\/docs\/api\">POEditor API<\/a>. Here\u2019s what an upload request looks like using <code>curl<\/code>:<\/p>\n\n\n\n<pre class=\"wp-block-code\"><code>curl -X POST https:\/\/api.poeditor.com\/v2\/projects\/upload \\\n  -F api_token=\"your_api_token\" \\\n  -F id=\"your_project_id\" \\\n  -F updating=\"terms_translations\" \\\n  -F file=@\"src\/locales\/en.json\"\n<\/code><\/pre>\n\n\n\n<p>This imports your translation keys and values into POEditor so you can start translating immediately. See the <a href=\"\/docs\/api#projects_upload\">upload docs<\/a> for everything you can tweak.Once your translations are done, you can <a href=\"\/docs\/api#projects_export\">export updated files<\/a> using their API too. For example, to download the French version:<\/p>\n\n\n\n<pre class=\"wp-block-code\"><code>curl -X POST https:\/\/api.poeditor.com\/v2\/projects\/export \\\n  -d api_token=\"your_api_token\" \\\n  -d id=\"your_project_id\" \\\n  -d language=\"fr\" \\\n  -d type=\"json\"<\/code><\/pre>\n\n\n\n<p>This returns a URL where you can download the file and drop it back into <code>src\/locales\/fr.json<\/code>. If you want to automate this step (say, as part of a deploy or CI workflow), you can build that in with a simple script.<\/p>\n\n\n\n<p>Now that translations are in place and your workflow is cleaned up, let\u2019s tackle the real-world stuff, like pluralization, formatting, and making sure your app speaks like a local.<\/p>\n\n\n\n<h2 class=\"wp-block-heading\">Handling dynamic values and interpolations<\/h2>\n\n\n\n<p>Most real-world apps need to show personalized or data-driven content. Things like \u201cWelcome back, Sarah\u201d or \u201cYou have 3 notifications.\u201d That\u2019s where interpolation comes in.<\/p>\n\n\n\n<p>With <code>vue-i18n<\/code>, you can define placeholders in your translation messages using double curly braces <code>({{ }})<\/code>, then pass in the dynamic values when calling <code>$t()<\/code>.<\/p>\n\n\n\n<p>Let\u2019s say your translation file looks like this:<\/p>\n\n\n\n<pre class=\"wp-block-code\"><code>\/\/ en.json\n{\n  \"user\": {\n    \"greeting\": \"Welcome back, {{name}}!\",\n    \"notifications\": \"You have {{count}} new notifications.\"\n  }\n}<\/code><\/pre>\n\n\n\n<p>You can render these in your component like this:<br><\/p>\n\n\n\n<pre class=\"wp-block-code\"><code>&lt;template&gt;\n  &lt;div&gt;\n    &lt;p&gt;{{ $t('user.greeting', { name: userName }) }}&lt;\/p&gt;\n    &lt;p&gt;{{ $t('user.notifications', { count: notificationCount }) }}&lt;\/p&gt;\n  &lt;\/div&gt;\n&lt;\/template&gt;\n\n&lt;script setup&gt;\nconst userName = 'Sarah'\nconst notificationCount = 3\n&lt;\/script&gt;<\/code><\/pre>\n\n\n\n<p>The <code>{{name}}<\/code> and <code>{{count}}<\/code> in the code above are placeholders in the translation string. When you pass <code>{ name: userName }<\/code> to <code>$t()<\/code>, vue-i18n replaces<code> {{name}}<\/code> with the value of userName.<\/p>\n\n\n\n<p>You can also use this pattern for more complex data, like dates or amounts, but we\u2019ll handle that separately when we talk about formatting.<\/p>\n\n\n\n<h2 class=\"wp-block-heading\">Pluralization and choice formatting<\/h2>\n\n\n\n<p>Plural rules vary wildly across languages. In English, it\u2019s straightforward, for example, \u201c1 item\u201d vs. \u201c2 items.\u201d But some languages use different forms for 0, 1, 2\u20134, or even for numbers that end in specific digits.&nbsp;<\/p>\n\n\n\n<p>Trying to handle that manually with if statements or ternaries quickly becomes a mess. Luckily, <code>vue-i18n<\/code> handles all of this for you using CLDR plural rules, the same standard behind most modern localization systems.&nbsp;<\/p>\n\n\n\n<p>It knows English only needs two forms (<code>one<\/code> and <code>other<\/code>), while Arabic or Russian might require four or five.&nbsp;<\/p>\n\n\n\n<p>Here\u2019s a simple example:<\/p>\n\n\n\n<pre class=\"wp-block-code\"><code>\/\/ en.json\n{\n  \"cart\": {\n    \"items\": \"No items | One item | {count} items\"\n  }\n}<\/code><\/pre>\n\n\n\n<p>This is a pipe-separated plural rule:<\/p>\n\n\n\n<ul class=\"wp-block-list\">\n<li>The first segment (<code>No items<\/code>) is used when the count is 0.<\/li>\n\n\n\n<li>The second (<code>One item<\/code>) is used when the count is 1.<\/li>\n\n\n\n<li>The third (<code>{count} items<\/code>) is used for everything else.<\/li>\n<\/ul>\n\n\n\n<p>To use this in a component:<\/p>\n\n\n\n<pre class=\"wp-block-code\"><code>&lt;p&gt;{{ $t('cart.items', count) }}&lt;\/p&gt;\n<\/code><\/pre>\n\n\n\n<p>If <code>count<\/code> is 0, you get: <code>No items<\/code>, If <code>count<\/code> is 1, you get: <code>One item<\/code>, If <code>count<\/code> is 3, you get: <code>3 items<\/code>.<\/p>\n\n\n\n<p>Under the hood, <code>vue-i18n<\/code> figures out which version of the message to use based on the active locale. That means if your user switches to French or Arabic, it\u2019ll apply the correct grammar rules for that language.<\/p>\n\n\n\n<p>And if you want to customize the behavior, you can use named interpolation along with plural rules:<\/p>\n\n\n\n<pre class=\"wp-block-code\"><code>\"likes\": \"No likes | One like | {count} likes\"<\/code><\/pre>\n\n\n\n<p>Then in your template, you\u2019d pass the count like this:<\/p>\n\n\n\n<pre class=\"wp-block-code\"><code>&lt;p&gt;{{ $t('likes', { count: likesCount }) }}&lt;\/p&gt;<\/code><\/pre>\n\n\n\n<p>Again, <code>vue-i18n<\/code> takes care of figuring out which form to use. You just pass the number.<\/p>\n\n\n\n<p>Next, let\u2019s explain how to handle translations that include HTML or styled elements, like bolded names, links, or icons, inside your messages.<\/p>\n\n\n\n<h2 class=\"wp-block-heading\">HTML and rich text in translations<\/h2>\n\n\n\n<p>Sometimes you need more than plain text. Maybe you want to bold a user\u2019s name, insert a link, or wrap part of a message in a span. But you can\u2019t just shove raw HTML into your JSON if you care about security or readability.<\/p>\n\n\n\n<p>Vue gives you two ways to handle this:<\/p>\n\n\n\n<h4 class=\"wp-block-heading\">v-html (for simple, trusted content)<\/h4>\n\n\n\n<p>If you know the HTML is safe and controlled (for example, you\u2019re bolding a word or adding emphasis). You can define your translation like this:<\/p>\n\n\n\n<pre class=\"wp-block-code\"><code>\/\/ en.json\n{\n  \"banner\": {\n    \"promo\": \"Enjoy our &lt;strong&gt;limited-time&lt;\/strong&gt; offer!\"\n  }\n}\n<\/code><\/pre>\n\n\n\n<p>Then in your template:<\/p>\n\n\n\n<pre class=\"wp-block-code\"><code>&lt;p v-html=\"$t('banner.promo')\"&gt;&lt;\/p&gt;<\/code><\/pre>\n\n\n\n<p>This works, but be careful as v-html renders raw HTML. If you ever pass untrusted content, you\u2019re opening yourself up to XSS. Never use this with user-generated strings.<\/p>\n\n\n\n<h4 class=\"wp-block-heading\">&lt;i18n-t&gt; (for dynamic or interactive content)<\/h4>\n\n\n\n<p>For anything more complex, especially if you need to mix in components, links, or spans, use the built-in &lt;i18n-t&gt; component. It lets you safely render rich content without giving up reactivity or translation structure.<\/p>\n\n\n\n<p>Here\u2019s a translation string with a placeholder:<\/p>\n\n\n\n<pre class=\"wp-block-code\"><code>\"notice\": \"Check our &lt;link&gt;terms and conditions&lt;\/link&gt;.\"<\/code><\/pre>\n\n\n\n<p>And here\u2019s how you use it in a Vue component:<\/p>\n\n\n\n<pre class=\"wp-block-code\"><code>&lt;i18n-t keypath=\"notice\"&gt;\n  &lt;template #link&gt;\n    &lt;a href=\"\/terms\" target=\"_blank\"&gt;terms and conditions&lt;\/a&gt;\n  &lt;\/template&gt;\n&lt;\/i18n-t&gt;<\/code><\/pre>\n\n\n\n<p>In the code above, the translation defines a custom tag: <code>&lt;link&gt;\u2026&lt;\/link&gt;<\/code>, and the <code>&lt;i18n-t&gt;<\/code> component maps that to whatever you put in the <code>#link<\/code> slot. This keeps things safe, scoped, and still fully localized.<\/p>\n\n\n\n<p>It\u2019s also smart about fallback languages and reactivity, just like <code>$t()<\/code>.<\/p>\n\n\n\n<h2 class=\"wp-block-heading\">Formatting dates, numbers, and currencies<\/h2>\n\n\n\n<p>Localization isn\u2019t just about translating words. It\u2019s also about formatting data in ways that feel natural to the user. That means showing dates, times, and currencies in a way that matches their region.<\/p>\n\n\n\n<p>vue-i18n makes this easy with built-in helpers like <code>d()<\/code> for dates and <code>n()<\/code> for numbers. These are available from <code>useI18n()<\/code> when you\u2019re using the Composition API.&nbsp;<\/p>\n\n\n\n<p><strong>Note:<\/strong> If you&#8217;re still using the Options API, you\u2019d use <code>$d()<\/code> and <code>$n()<\/code> instead.<\/p>\n\n\n\n<p>Let\u2019s start with a basic example:<\/p>\n\n\n\n<pre class=\"wp-block-code\"><code>&lt;script setup&gt;\nimport { useI18n } from 'vue-i18n'\n\nconst { d } = useI18n()\n&lt;\/script&gt;\n\n&lt;template&gt;\n  &lt;p&gt;{{ d(new Date()) }}&lt;\/p&gt;\n&lt;\/template&gt;\n<\/code><\/pre>\n\n\n\n<p>If your locale is set to <code>en-US<\/code>, this will render <code>7\/22\/2025<\/code>. If you switch to <code>fr-FR<\/code>, and it becomes <code>22\/07\/2025<\/code>.<\/p>\n\n\n\n<p>Same date, different format, automatically handled based on the active locale. You can also pass in formatting options:<\/p>\n\n\n\n<pre class=\"wp-block-code\"><code>&lt;template&gt;\n  &lt;p&gt;{{ d(new Date(), { dateStyle: 'full', timeStyle: 'short' }) }}&lt;\/p&gt;\n&lt;\/template&gt;<\/code><\/pre>\n\n\n\n<p>This gives you something like:<\/p>\n\n\n\n<pre class=\"wp-block-code\"><code>Tuesday, July 22, 2025 at 8:30 AM<\/code><\/pre>\n\n\n\n<p>Instead of repeating options inline, you can define reusable format presets when setting up your i18n instance:<br><\/p>\n\n\n\n<pre class=\"wp-block-code\"><code>const datetimeFormats = {\n  'en-US': {\n    short: { year: 'numeric', month: 'short', day: 'numeric' },\n    long: {\n      year: 'numeric',\n      month: 'short',\n      day: 'numeric',\n      weekday: 'short',\n      hour: 'numeric',\n      minute: 'numeric',\n    },\n  },\n  'ja-JP': {\n    short: { year: 'numeric', month: 'short', day: 'numeric' },\n    long: {\n      year: 'numeric',\n      month: 'short',\n      day: 'numeric',\n      weekday: 'short',\n      hour: 'numeric',\n      minute: 'numeric',\n      hour12: true,\n    },\n  },\n}\n\nconst i18n = createI18n({\n  locale: 'en-US',\n  fallbackLocale: 'en-US',\n  datetimeFormats,\n})<\/code><\/pre>\n\n\n\n<p>Then you can call:<\/p>\n\n\n\n<pre class=\"wp-block-code\"><code>&lt;template&gt;\n  &lt;p&gt;{{ d(new Date(), 'long') }}&lt;\/p&gt;\n&lt;\/template&gt;<\/code><\/pre>\n\n\n\n<p>And vue-i18n will apply the right format based on the active locale.<\/p>\n\n\n\n<h2 class=\"wp-block-heading\">Number formatting<\/h2>\n\n\n\n<p>The same idea applies to numbers.<\/p>\n\n\n\n<pre class=\"wp-block-code\"><code>&lt;script setup&gt;\nimport { useI18n } from 'vue-i18n'\n\nconst { n } = useI18n()\n&lt;\/script&gt;\n\n&lt;template&gt;\n  &lt;p&gt;{{ n(1234567.89) }}&lt;\/p&gt;\n&lt;\/template&gt;<\/code><\/pre>\n\n\n\n<p>With <code>en-US<\/code>, it shows <code>1,234,567.89<\/code>. With <code>de-DE<\/code>, it switches to <code>1.234.567,89<\/code>. You can learn more in the <a href=\"https:\/\/vue-i18n.intlify.dev\/guide\/essentials\/datetime.html\" rel=\"nofollow\">vue-i18n official documentation<\/a>.<\/p>\n\n\n\n<h2 class=\"wp-block-heading\">Formatting currencies<\/h2>\n\n\n\n<p>To display currency, pass in a style config:<\/p>\n\n\n\n<pre class=\"wp-block-code\"><code>&lt;template&gt;\n  &lt;p&gt;{{ n(29.99, { style: 'currency', currency: 'USD' }) }}&lt;\/p&gt;\n&lt;\/template&gt;<\/code><\/pre>\n\n\n\n<p>This renders as <code>$29.99<\/code>. When you change the locale to <code>ja-JP<\/code>, and you\u2019ll get <code>\uffe53,000<\/code>.&nbsp;<\/p>\n\n\n\n<p>It uses proper symbols, spacing, and separators based on the user\u2019s region. No manual formatting or regex tricks needed. Learn more <a href=\"https:\/\/vue-i18n.intlify.dev\/guide\/essentials\/number.html\" rel=\"nofollow\">here<\/a>.<\/p>\n\n\n\n<h2 class=\"wp-block-heading\">Localized routing (optional but powerful)<\/h2>\n\n\n\n<p>If you&#8217;re building a multi-language app, showing the selected locale in the URL can be a nice touch both for users and for SEO. It tells users what language they\u2019re in and lets them share links that stay consistent.<\/p>\n\n\n\n<p>To do this, you&#8217;ll need to combine <code>vue-i18n<\/code> with <code>vue-router<\/code>. There are a few ways to approach it, but the simplest pattern is to include the locale as a dynamic route param.<\/p>\n\n\n\n<p>Here\u2019s a basic example of how you might set up routes:<\/p>\n\n\n\n<pre class=\"wp-block-code\"><code>\/\/ router\/index.js\nconst routes = &#091;\n  {\n    path: '\/:locale',\n    component: AppLayout,\n    children: &#091;\n      {\n        path: 'about',\n        name: 'about',\n        component: AboutPage\n      },\n      {\n        path: '',\n        name: 'home',\n        component: HomePage\n      }\n    ]\n  }\n]<\/code><\/pre>\n\n\n\n<p>With that structure, <code>\/en\/about<\/code> and <code>\/fr\/about<\/code> both map to the same <code>AboutPage<\/code>, but now you can read the locale param and apply it to <code>vue-i18n<\/code>.<\/p>\n\n\n\n<p>To make that work, add this to your router file:<\/p>\n\n\n\n<pre class=\"wp-block-code\"><code>\/\/ router setup\nrouter.beforeEach((to, from, next) =&gt; {\n  const locale = to.params.locale\n\n  if (!&#091;'en', 'fr'].includes(locale)) {\n    return next('en') \/\/ fallback\n  }\n\n  i18n.global.locale = locale\n  next()\n})<\/code><\/pre>\n\n\n\n<p>This sets the current language based on the URL before each route is resolved.<\/p>\n\n\n\n<h2 class=\"wp-block-heading\">Switching routes programmatically<\/h2>\n\n\n\n<p>If your app has a language dropdown, you can make it update the route when the user selects a new language:<\/p>\n\n\n\n<pre class=\"wp-block-code\"><code>&lt;select @change=\"changeLocale($event.target.value)\"&gt;\n  &lt;option value=\"en\"&gt;English&lt;\/option&gt;\n  &lt;option value=\"fr\"&gt;Fran\u00e7ais&lt;\/option&gt;\n&lt;\/select&gt;<\/code><\/pre>\n\n\n\n<p>Then, in your script block or <code>setup()<\/code> function, define the logic:<\/p>\n\n\n\n<pre class=\"wp-block-code\"><code>function changeLocale(locale) {\n  router.push({ name: route.name, params: { ...route.params, locale } })\n}<\/code><\/pre>\n\n\n\n<p>That way, your route changes to something like <code>\/fr\/about<\/code> and updates the locale at the same time \u2014 no need to reload the page.<\/p>\n\n\n<div class=\"wp-block-image\">\n<figure class=\"aligncenter\"><img decoding=\"async\" src=\"https:\/\/lh7-rt.googleusercontent.com\/docsz\/AD_4nXd8EaT1aJ4962RxM9R3ick8Kc4bPhI0mpThyix5w7MS44W8OssbjTe3UbYYwlmatDZ04rcTbg7TtKwD3snjBfMoK6dhLyf_HAquTP9gMWos9bOu4zt7257WQ2kiZkYhQXV-5K8-zA?key=nT2JcH9FdAElcAxIbjOGRQ\" alt=\"\"\/><\/figure>\n<\/div>\n\n\n<p>You can take this further with route guards, default redirects, or storing the user\u2019s preferred language in localStorage. But for most apps, just having the locale in the URL and wiring it to <code>vue-i18n<\/code> gives you a clean, shareable experience that scales.<\/p>\n\n\n\n<h2 class=\"wp-block-heading\">Lazy loading translations<\/h2>\n\n\n\n<p>When your app supports just one or two languages, it might be fine to bundle all translations upfront. But as your project grows, loading all that at once becomes unnecessary.&nbsp;<\/p>\n\n\n\n<p>You don\u2019t want to make users download five translation files if they only ever use one. That\u2019s where lazy loading comes in.&nbsp;<\/p>\n\n\n\n<p>Let\u2019s say your project looks like this:<\/p>\n\n\n\n<pre class=\"wp-block-code\"><code>src\/\n\u251c\u2500 locales\/\n\u2502  \u251c\u2500 en.json\n\u2502  \u2514\u2500 fr.json\n\u251c\u2500 i18n.js\n\u251c\u2500 main.js\n\u251c\u2500 router.js<\/code><\/pre>\n\n\n\n<p>In i18n.js, create and export the base i18n instance, coupled with some helper functions:<\/p>\n\n\n\n<pre class=\"wp-block-code\"><code>\/\/ src\/i18n.js\nimport { createI18n } from 'vue-i18n'\n\nexport const SUPPORT_LOCALES = &#091;'en', 'fr']\n\nexport function setupI18n(options = { locale: 'en' }) {\n  const i18n = createI18n(options)\n  setI18nLanguage(i18n, options.locale)\n  return i18n\n}\n\nexport function setI18nLanguage(i18n, locale) {\n  if (i18n.mode === 'legacy') {\n    i18n.global.locale = locale\n  } else {\n    i18n.global.locale.value = locale\n  }\n\n  document.querySelector('html').setAttribute('lang', locale)\n}\n<\/code><\/pre>\n\n\n\n<p>This gives you a reusable <code>setupI18n()<\/code> function to plug into <code>main.js<\/code> and a <code>setI18nLanguage()<\/code> helper that sets the active locale, and updates <code>&lt;html lang=\"\"&gt;<\/code>.<\/p>\n\n\n\n<p>Still inside <code>i18n.js<\/code>, add this function to load a translation file only when it\u2019s needed:<\/p>\n\n\n\n<pre class=\"wp-block-code\"><code>\/\/ src\/i18n.js (continued)\nimport { nextTick } from 'vue'\n\nexport async function loadLocaleMessages(i18n, locale) {\n  const messages = await import(\n    \/* webpackChunkName: \"locale-&#091;request]\" *\/ `.\/locales\/${locale}.json`\n  )\n\n  i18n.global.setLocaleMessage(locale, messages.default)\n\n  return nextTick()\n}<\/code><\/pre>\n\n\n\n<p>This uses dynamic <code>import()<\/code> to split each translation file into its own chunk. When the user switches languages, only that specific file is loaded.<\/p>\n\n\n\n<p>Now open <code>router.js<\/code> and make sure you\u2019re handling route-based language switching.<\/p>\n\n\n\n<p>If your routes look like <code>\/en\/about<\/code> or <code>\/fr\/contact<\/code>, then <code>to.params.locale<\/code> will contain the language code. You can hook into this with a <code>beforeEach<\/code> navigation guard:<\/p>\n\n\n\n<pre class=\"wp-block-code\"><code>\/\/ src\/router.js\nimport { loadLocaleMessages, setI18nLanguage, SUPPORT_LOCALES } from '.\/i18n'\n\nrouter.beforeEach(async (to, from, next) =&gt; {\n  const locale = to.params.locale\n\n  if (!SUPPORT_LOCALES.includes(locale)) {\n    return next('\/en') \/\/ fallback if unsupported\n  }\n\n  if (!i18n.global.availableLocales.includes(locale)) {\n    await loadLocaleMessages(i18n, locale)\n  }\n\n  setI18nLanguage(i18n, locale)\n  next()\n})<\/code><\/pre>\n\n\n\n<p>Finally, bring everything together in your app entry point:<\/p>\n\n\n\n<pre class=\"wp-block-code\"><code>\/\/ src\/main.js\nimport { createApp } from 'vue'\nimport App from '.\/App.vue'\nimport router from '.\/router'\nimport { setupI18n } from '.\/i18n'\n\nconst i18n = setupI18n({ locale: 'en', fallbackLocale: 'en', messages: {} })\n\ncreateApp(App).use(router).use(i18n).mount('#app')<\/code><\/pre>\n\n\n\n<p>At this point, you\u2019ve got lazy loading working via <code>import()<\/code> and language switching tied into your router.<\/p>\n\n\n\n<h2 class=\"wp-block-heading\">Wrapping up<\/h2>\n\n\n\n<p>The truth is, localization always starts small, but it never stays that way. More users equal more languages, which leads to more complexity.&nbsp;<\/p>\n\n\n\n<p>By structuring your i18n logic early, you\u2019ll save your team a ton of headaches down the line. If you\u2019re working with a team or targeting more than one market, plug in a translation management tool like POEditor to stay organized and collaborate easily.<\/p>\n\n\n\n<p>That\u2019s it. You now have everything you need to build a fully localized Vue app that doesn\u2019t feel bolted together. Keep it clean, keep it scalable, and your users, no matter what language they speak, will thank you for it.<\/p>\n\n\n<div class=\"call-action my-4 d-flex justify-content-between align-items-md-center gap-4 flex-column flex-lg-row\"><div><h3 class=\"fs-4\">Improve your localization process<\/h3><span class=\"fs-6\">Discover an easy to use and affordable localization app.<\/span><\/div><a class=\"btn btn-b-primary d-flex align-items-center justify-content-center px-4 py-3 flex-shrink-0\" \n\t\t\t\t\thref=\"https:\/\/poeditor.com\/register\/?utm_source=blog&#038;utm_medium=btn&#038;utm_campaign=cta_register\">Get started<\/a><\/div>\n\n\n\n<p><\/p>\n","protected":false},"excerpt":{"rendered":"<p>If you\u2019re building Vue applications for real-world users, you\u2019ll eventually need to go beyond a single language. That means dealing with translations, formatting numbers and dates correctly, and supporting different locales. This is referred to as internationalization (i18n) and localization (l10n). The best way to implement it in Vue is with the vue-i18n package. This [&hellip;]<\/p>\n","protected":false},"author":9,"featured_media":0,"comment_status":"closed","ping_status":"open","sticky":false,"template":"","format":"standard","meta":{"footnotes":""},"categories":[3],"tags":[],"class_list":["post-6638","post","type-post","status-publish","format-standard","hentry","category-tutorials"],"yoast_head":"<!-- This site is optimized with the Yoast SEO plugin v27.4 - https:\/\/yoast.com\/product\/yoast-seo-wordpress\/ -->\n<title>A complete guide to internationalizing Vue apps with vue-i18n - POEditor Blog<\/title>\n<meta name=\"description\" content=\"Learn how to implement internationalization in your Vue.js applications using Vue i18n. Discover setup tips and examples.\" \/>\n<meta name=\"robots\" content=\"index, follow, max-snippet:-1, max-image-preview:large, max-video-preview:-1\" \/>\n<link rel=\"canonical\" href=\"https:\/\/poeditor.com\/blog\/vue-i18n\/\" \/>\n<meta property=\"og:locale\" content=\"en_US\" \/>\n<meta property=\"og:type\" content=\"article\" \/>\n<meta property=\"og:title\" content=\"A complete guide to internationalizing Vue apps with vue-i18n - POEditor Blog\" \/>\n<meta property=\"og:description\" content=\"Learn how to implement internationalization in your Vue.js applications using Vue i18n. Discover setup tips and examples.\" \/>\n<meta property=\"og:url\" content=\"https:\/\/poeditor.com\/blog\/vue-i18n\/\" \/>\n<meta property=\"og:site_name\" content=\"POEditor Blog\" \/>\n<meta property=\"article:publisher\" content=\"https:\/\/www.facebook.com\/POEditor\" \/>\n<meta property=\"article:published_time\" content=\"2025-08-07T06:00:00+00:00\" \/>\n<meta property=\"article:modified_time\" content=\"2026-04-20T12:51:45+00:00\" \/>\n<meta property=\"og:image\" content=\"https:\/\/poeditor.com\/blog\/wp-content\/uploads\/2025\/08\/vue-i18n.png\" \/>\n\t<meta property=\"og:image:width\" content=\"1726\" \/>\n\t<meta property=\"og:image:height\" content=\"1653\" \/>\n\t<meta property=\"og:image:type\" content=\"image\/png\" \/>\n<meta name=\"author\" content=\"Joel Olawanle\" \/>\n<meta name=\"twitter:card\" content=\"summary_large_image\" \/>\n<meta name=\"twitter:creator\" content=\"@poeditor\" \/>\n<meta name=\"twitter:site\" content=\"@poeditor\" \/>\n<meta name=\"twitter:label1\" content=\"Written by\" \/>\n\t<meta name=\"twitter:data1\" content=\"Joel Olawanle\" \/>\n\t<meta name=\"twitter:label2\" content=\"Est. reading time\" \/>\n\t<meta name=\"twitter:data2\" content=\"10 minutes\" \/>\n<script type=\"application\/ld+json\" class=\"yoast-schema-graph\">{\"@context\":\"https:\\\/\\\/schema.org\",\"@graph\":[{\"@type\":\"Article\",\"@id\":\"https:\\\/\\\/poeditor.com\\\/blog\\\/vue-i18n\\\/#article\",\"isPartOf\":{\"@id\":\"https:\\\/\\\/poeditor.com\\\/blog\\\/vue-i18n\\\/\"},\"author\":{\"name\":\"Joel Olawanle\",\"@id\":\"https:\\\/\\\/poeditor.com\\\/blog\\\/#\\\/schema\\\/person\\\/7c32041b74b3e09061cabde6e194862b\"},\"headline\":\"A complete guide to internationalizing Vue apps with vue-i18n\",\"datePublished\":\"2025-08-07T06:00:00+00:00\",\"dateModified\":\"2026-04-20T12:51:45+00:00\",\"mainEntityOfPage\":{\"@id\":\"https:\\\/\\\/poeditor.com\\\/blog\\\/vue-i18n\\\/\"},\"wordCount\":2189,\"publisher\":{\"@id\":\"https:\\\/\\\/poeditor.com\\\/blog\\\/#organization\"},\"image\":{\"@id\":\"https:\\\/\\\/poeditor.com\\\/blog\\\/vue-i18n\\\/#primaryimage\"},\"thumbnailUrl\":\"https:\\\/\\\/poeditor.com\\\/blog\\\/wp-content\\\/uploads\\\/2025\\\/08\\\/vue-i18n-1024x981.png\",\"articleSection\":[\"Tutorials\"],\"inLanguage\":\"en-US\"},{\"@type\":\"WebPage\",\"@id\":\"https:\\\/\\\/poeditor.com\\\/blog\\\/vue-i18n\\\/\",\"url\":\"https:\\\/\\\/poeditor.com\\\/blog\\\/vue-i18n\\\/\",\"name\":\"A complete guide to internationalizing Vue apps with vue-i18n - POEditor Blog\",\"isPartOf\":{\"@id\":\"https:\\\/\\\/poeditor.com\\\/blog\\\/#website\"},\"primaryImageOfPage\":{\"@id\":\"https:\\\/\\\/poeditor.com\\\/blog\\\/vue-i18n\\\/#primaryimage\"},\"image\":{\"@id\":\"https:\\\/\\\/poeditor.com\\\/blog\\\/vue-i18n\\\/#primaryimage\"},\"thumbnailUrl\":\"https:\\\/\\\/poeditor.com\\\/blog\\\/wp-content\\\/uploads\\\/2025\\\/08\\\/vue-i18n-1024x981.png\",\"datePublished\":\"2025-08-07T06:00:00+00:00\",\"dateModified\":\"2026-04-20T12:51:45+00:00\",\"description\":\"Learn how to implement internationalization in your Vue.js applications using Vue i18n. Discover setup tips and examples.\",\"breadcrumb\":{\"@id\":\"https:\\\/\\\/poeditor.com\\\/blog\\\/vue-i18n\\\/#breadcrumb\"},\"inLanguage\":\"en-US\",\"potentialAction\":[{\"@type\":\"ReadAction\",\"target\":[\"https:\\\/\\\/poeditor.com\\\/blog\\\/vue-i18n\\\/\"]}]},{\"@type\":\"ImageObject\",\"inLanguage\":\"en-US\",\"@id\":\"https:\\\/\\\/poeditor.com\\\/blog\\\/vue-i18n\\\/#primaryimage\",\"url\":\"https:\\\/\\\/poeditor.com\\\/blog\\\/wp-content\\\/uploads\\\/2025\\\/08\\\/vue-i18n.png\",\"contentUrl\":\"https:\\\/\\\/poeditor.com\\\/blog\\\/wp-content\\\/uploads\\\/2025\\\/08\\\/vue-i18n.png\",\"width\":1726,\"height\":1653,\"caption\":\"vue i18n\"},{\"@type\":\"BreadcrumbList\",\"@id\":\"https:\\\/\\\/poeditor.com\\\/blog\\\/vue-i18n\\\/#breadcrumb\",\"itemListElement\":[{\"@type\":\"ListItem\",\"position\":1,\"name\":\"Home\",\"item\":\"https:\\\/\\\/poeditor.com\\\/blog\\\/\"},{\"@type\":\"ListItem\",\"position\":2,\"name\":\"A complete guide to internationalizing Vue apps with vue-i18n\"}]},{\"@type\":\"WebSite\",\"@id\":\"https:\\\/\\\/poeditor.com\\\/blog\\\/#website\",\"url\":\"https:\\\/\\\/poeditor.com\\\/blog\\\/\",\"name\":\"POEditor Blog\",\"description\":\"All about translation and localization management\",\"publisher\":{\"@id\":\"https:\\\/\\\/poeditor.com\\\/blog\\\/#organization\"},\"potentialAction\":[{\"@type\":\"SearchAction\",\"target\":{\"@type\":\"EntryPoint\",\"urlTemplate\":\"https:\\\/\\\/poeditor.com\\\/blog\\\/?s={search_term_string}\"},\"query-input\":{\"@type\":\"PropertyValueSpecification\",\"valueRequired\":true,\"valueName\":\"search_term_string\"}}],\"inLanguage\":\"en-US\"},{\"@type\":\"Organization\",\"@id\":\"https:\\\/\\\/poeditor.com\\\/blog\\\/#organization\",\"name\":\"POEditor\",\"url\":\"https:\\\/\\\/poeditor.com\\\/blog\\\/\",\"logo\":{\"@type\":\"ImageObject\",\"inLanguage\":\"en-US\",\"@id\":\"https:\\\/\\\/poeditor.com\\\/blog\\\/#\\\/schema\\\/logo\\\/image\\\/\",\"url\":\"https:\\\/\\\/poeditor.com\\\/blog\\\/wp-content\\\/uploads\\\/2019\\\/11\\\/logo_head_512_transparent.png\",\"contentUrl\":\"https:\\\/\\\/poeditor.com\\\/blog\\\/wp-content\\\/uploads\\\/2019\\\/11\\\/logo_head_512_transparent.png\",\"width\":512,\"height\":512,\"caption\":\"POEditor\"},\"image\":{\"@id\":\"https:\\\/\\\/poeditor.com\\\/blog\\\/#\\\/schema\\\/logo\\\/image\\\/\"},\"sameAs\":[\"https:\\\/\\\/www.facebook.com\\\/POEditor\",\"https:\\\/\\\/x.com\\\/poeditor\",\"https:\\\/\\\/www.linkedin.com\\\/company\\\/poeditor\\\/\",\"https:\\\/\\\/www.youtube.com\\\/channel\\\/UCXAk1u8N49VRMAqNneENCFA\"]},{\"@type\":\"Person\",\"@id\":\"https:\\\/\\\/poeditor.com\\\/blog\\\/#\\\/schema\\\/person\\\/7c32041b74b3e09061cabde6e194862b\",\"name\":\"Joel Olawanle\",\"image\":{\"@type\":\"ImageObject\",\"inLanguage\":\"en-US\",\"@id\":\"https:\\\/\\\/secure.gravatar.com\\\/avatar\\\/30f391dce10587a611158f3f46b79cabfc72e00d891bf7ae84dd80cec0a6d71b?s=96&d=mm&r=g\",\"url\":\"https:\\\/\\\/secure.gravatar.com\\\/avatar\\\/30f391dce10587a611158f3f46b79cabfc72e00d891bf7ae84dd80cec0a6d71b?s=96&d=mm&r=g\",\"contentUrl\":\"https:\\\/\\\/secure.gravatar.com\\\/avatar\\\/30f391dce10587a611158f3f46b79cabfc72e00d891bf7ae84dd80cec0a6d71b?s=96&d=mm&r=g\",\"caption\":\"Joel Olawanle\"},\"url\":\"https:\\\/\\\/poeditor.com\\\/blog\\\/author\\\/joel-olawanle\\\/\"}]}<\/script>\n<!-- \/ Yoast SEO plugin. -->","yoast_head_json":{"title":"A complete guide to internationalizing Vue apps with vue-i18n - POEditor Blog","description":"Learn how to implement internationalization in your Vue.js applications using Vue i18n. Discover setup tips and examples.","robots":{"index":"index","follow":"follow","max-snippet":"max-snippet:-1","max-image-preview":"max-image-preview:large","max-video-preview":"max-video-preview:-1"},"canonical":"https:\/\/poeditor.com\/blog\/vue-i18n\/","og_locale":"en_US","og_type":"article","og_title":"A complete guide to internationalizing Vue apps with vue-i18n - POEditor Blog","og_description":"Learn how to implement internationalization in your Vue.js applications using Vue i18n. Discover setup tips and examples.","og_url":"https:\/\/poeditor.com\/blog\/vue-i18n\/","og_site_name":"POEditor Blog","article_publisher":"https:\/\/www.facebook.com\/POEditor","article_published_time":"2025-08-07T06:00:00+00:00","article_modified_time":"2026-04-20T12:51:45+00:00","og_image":[{"width":1726,"height":1653,"url":"https:\/\/poeditor.com\/blog\/wp-content\/uploads\/2025\/08\/vue-i18n.png","type":"image\/png"}],"author":"Joel Olawanle","twitter_card":"summary_large_image","twitter_creator":"@poeditor","twitter_site":"@poeditor","twitter_misc":{"Written by":"Joel Olawanle","Est. reading time":"10 minutes"},"schema":{"@context":"https:\/\/schema.org","@graph":[{"@type":"Article","@id":"https:\/\/poeditor.com\/blog\/vue-i18n\/#article","isPartOf":{"@id":"https:\/\/poeditor.com\/blog\/vue-i18n\/"},"author":{"name":"Joel Olawanle","@id":"https:\/\/poeditor.com\/blog\/#\/schema\/person\/7c32041b74b3e09061cabde6e194862b"},"headline":"A complete guide to internationalizing Vue apps with vue-i18n","datePublished":"2025-08-07T06:00:00+00:00","dateModified":"2026-04-20T12:51:45+00:00","mainEntityOfPage":{"@id":"https:\/\/poeditor.com\/blog\/vue-i18n\/"},"wordCount":2189,"publisher":{"@id":"https:\/\/poeditor.com\/blog\/#organization"},"image":{"@id":"https:\/\/poeditor.com\/blog\/vue-i18n\/#primaryimage"},"thumbnailUrl":"https:\/\/poeditor.com\/blog\/wp-content\/uploads\/2025\/08\/vue-i18n-1024x981.png","articleSection":["Tutorials"],"inLanguage":"en-US"},{"@type":"WebPage","@id":"https:\/\/poeditor.com\/blog\/vue-i18n\/","url":"https:\/\/poeditor.com\/blog\/vue-i18n\/","name":"A complete guide to internationalizing Vue apps with vue-i18n - POEditor Blog","isPartOf":{"@id":"https:\/\/poeditor.com\/blog\/#website"},"primaryImageOfPage":{"@id":"https:\/\/poeditor.com\/blog\/vue-i18n\/#primaryimage"},"image":{"@id":"https:\/\/poeditor.com\/blog\/vue-i18n\/#primaryimage"},"thumbnailUrl":"https:\/\/poeditor.com\/blog\/wp-content\/uploads\/2025\/08\/vue-i18n-1024x981.png","datePublished":"2025-08-07T06:00:00+00:00","dateModified":"2026-04-20T12:51:45+00:00","description":"Learn how to implement internationalization in your Vue.js applications using Vue i18n. Discover setup tips and examples.","breadcrumb":{"@id":"https:\/\/poeditor.com\/blog\/vue-i18n\/#breadcrumb"},"inLanguage":"en-US","potentialAction":[{"@type":"ReadAction","target":["https:\/\/poeditor.com\/blog\/vue-i18n\/"]}]},{"@type":"ImageObject","inLanguage":"en-US","@id":"https:\/\/poeditor.com\/blog\/vue-i18n\/#primaryimage","url":"https:\/\/poeditor.com\/blog\/wp-content\/uploads\/2025\/08\/vue-i18n.png","contentUrl":"https:\/\/poeditor.com\/blog\/wp-content\/uploads\/2025\/08\/vue-i18n.png","width":1726,"height":1653,"caption":"vue i18n"},{"@type":"BreadcrumbList","@id":"https:\/\/poeditor.com\/blog\/vue-i18n\/#breadcrumb","itemListElement":[{"@type":"ListItem","position":1,"name":"Home","item":"https:\/\/poeditor.com\/blog\/"},{"@type":"ListItem","position":2,"name":"A complete guide to internationalizing Vue apps with vue-i18n"}]},{"@type":"WebSite","@id":"https:\/\/poeditor.com\/blog\/#website","url":"https:\/\/poeditor.com\/blog\/","name":"POEditor Blog","description":"All about translation and localization management","publisher":{"@id":"https:\/\/poeditor.com\/blog\/#organization"},"potentialAction":[{"@type":"SearchAction","target":{"@type":"EntryPoint","urlTemplate":"https:\/\/poeditor.com\/blog\/?s={search_term_string}"},"query-input":{"@type":"PropertyValueSpecification","valueRequired":true,"valueName":"search_term_string"}}],"inLanguage":"en-US"},{"@type":"Organization","@id":"https:\/\/poeditor.com\/blog\/#organization","name":"POEditor","url":"https:\/\/poeditor.com\/blog\/","logo":{"@type":"ImageObject","inLanguage":"en-US","@id":"https:\/\/poeditor.com\/blog\/#\/schema\/logo\/image\/","url":"https:\/\/poeditor.com\/blog\/wp-content\/uploads\/2019\/11\/logo_head_512_transparent.png","contentUrl":"https:\/\/poeditor.com\/blog\/wp-content\/uploads\/2019\/11\/logo_head_512_transparent.png","width":512,"height":512,"caption":"POEditor"},"image":{"@id":"https:\/\/poeditor.com\/blog\/#\/schema\/logo\/image\/"},"sameAs":["https:\/\/www.facebook.com\/POEditor","https:\/\/x.com\/poeditor","https:\/\/www.linkedin.com\/company\/poeditor\/","https:\/\/www.youtube.com\/channel\/UCXAk1u8N49VRMAqNneENCFA"]},{"@type":"Person","@id":"https:\/\/poeditor.com\/blog\/#\/schema\/person\/7c32041b74b3e09061cabde6e194862b","name":"Joel Olawanle","image":{"@type":"ImageObject","inLanguage":"en-US","@id":"https:\/\/secure.gravatar.com\/avatar\/30f391dce10587a611158f3f46b79cabfc72e00d891bf7ae84dd80cec0a6d71b?s=96&d=mm&r=g","url":"https:\/\/secure.gravatar.com\/avatar\/30f391dce10587a611158f3f46b79cabfc72e00d891bf7ae84dd80cec0a6d71b?s=96&d=mm&r=g","contentUrl":"https:\/\/secure.gravatar.com\/avatar\/30f391dce10587a611158f3f46b79cabfc72e00d891bf7ae84dd80cec0a6d71b?s=96&d=mm&r=g","caption":"Joel Olawanle"},"url":"https:\/\/poeditor.com\/blog\/author\/joel-olawanle\/"}]}},"_links":{"self":[{"href":"https:\/\/poeditor.com\/blog\/wp-json\/wp\/v2\/posts\/6638","targetHints":{"allow":["GET"]}}],"collection":[{"href":"https:\/\/poeditor.com\/blog\/wp-json\/wp\/v2\/posts"}],"about":[{"href":"https:\/\/poeditor.com\/blog\/wp-json\/wp\/v2\/types\/post"}],"author":[{"embeddable":true,"href":"https:\/\/poeditor.com\/blog\/wp-json\/wp\/v2\/users\/9"}],"replies":[{"embeddable":true,"href":"https:\/\/poeditor.com\/blog\/wp-json\/wp\/v2\/comments?post=6638"}],"version-history":[{"count":4,"href":"https:\/\/poeditor.com\/blog\/wp-json\/wp\/v2\/posts\/6638\/revisions"}],"predecessor-version":[{"id":6850,"href":"https:\/\/poeditor.com\/blog\/wp-json\/wp\/v2\/posts\/6638\/revisions\/6850"}],"wp:attachment":[{"href":"https:\/\/poeditor.com\/blog\/wp-json\/wp\/v2\/media?parent=6638"}],"wp:term":[{"taxonomy":"category","embeddable":true,"href":"https:\/\/poeditor.com\/blog\/wp-json\/wp\/v2\/categories?post=6638"},{"taxonomy":"post_tag","embeddable":true,"href":"https:\/\/poeditor.com\/blog\/wp-json\/wp\/v2\/tags?post=6638"}],"curies":[{"name":"wp","href":"https:\/\/api.w.org\/{rel}","templated":true}]}}