{"id":437,"date":"2024-08-01T15:00:00","date_gmt":"2024-08-01T15:00:00","guid":{"rendered":"https:\/\/fdswebdesign.com\/?p=437"},"modified":"2024-10-15T23:32:28","modified_gmt":"2024-10-15T23:32:28","slug":"how-to-build-a-multilingual-website-with-nuxt-js","status":"publish","type":"post","link":"https:\/\/fdswebdesign.com\/index.php\/2024\/08\/01\/how-to-build-a-multilingual-website-with-nuxt-js\/","title":{"rendered":"How To Build A Multilingual Website With Nuxt.js"},"content":{"rendered":"

How To Build A Multilingual Website With Nuxt.js<\/title><\/p>\n<article>\n<header>\n<h1>How To Build A Multilingual Website With Nuxt.js<\/h1>\n<address>Tim Benniks<\/address>\n<p> 2024-08-01T15:00:00+00:00<br \/>\n 2024-10-15T23:05:45+00:00<br \/>\n <\/header>\n<p>This article is sponsored by <b>Hygraph<\/b><\/p>\n<p>Internationalization, often abbreviated as i18n, is the process of designing and developing software applications in a way that they can be easily adapted to various spoken languages like English, German, French, and more without requiring substantial changes to the codebase. It involves <strong>moving away from hardcoded strings and techniques<\/strong> for translating text, formatting dates and numbers, and handling different character encodings, among other tasks.<\/p>\n<p>Internationalization can <strong>give users the choice<\/strong> to access a given website or application in their native language, which can have a positive impression on them, making it crucial for reaching a global audience.<\/p>\n<h2 id=\"what-we-re-making\">What We\u2019re Making<\/h2>\n<p>In this tutorial, we\u2019re making a website that puts these i18n pieces together using a combination of libraries and a UI framework. You\u2019ll want to have intermediate proficiency with JavaScript, Vue, and Nuxt to follow along. Throughout this article, we will <strong>learn by examples<\/strong> and incrementally <strong>build a multilingual Nuxt website<\/strong>. Together, we will learn how to provide i18n support for different languages, lazy-load locale messages, and switch locale on runtime.<\/p>\n<p>After that, we will explore features like interpolation, pluralization, and date\/time translations.<\/p>\n<p>And finally, we will fetch dynamic localized content from an API server using Hygraph as our API server to get localized content. If you do not have a Hygraph account <a href=\"https:\/\/hygraph.com\/\">please create one for free<\/a> before jumping in.<\/p>\n<p>As a final detail, we will use <a href=\"https:\/\/vuetifyjs.com\/en\/\">Vuetify<\/a> as our UI framework, but please feel free to use another framework if you want. The final code for what we\u2019re building is published in a <a href=\"https:\/\/github.com\/hygraph\/hygraph-examples\/tree\/master\/with-nuxtjs-i18n\">GitHub repository<\/a> for reference. And finally, you can also take a look at the final result <a href=\"https:\/\/nuxtjs-i18n.withheadlesscms.com\/\">in a live demo<\/a>.<\/p>\n<h2 id=\"the-nuxt-i18n-library\">The <code>nuxt-i18n<\/code> Library<\/h2>\n<p><a href=\"https:\/\/i18n.nuxtjs.org\/\"><code>nuxt-i18n<\/code><\/a> is a library for implementing internationalization in Nuxt.js applications, and it\u2019s what we will be using in this tutorial. The library is built on top of <a href=\"https:\/\/vue-i18n.intlify.dev\/\">Vue I18n<\/a>, which, again, is the de facto standard library for implementing i18n in Vue applications.<\/p>\n<p>What makes <code>nuxt-i18n<\/code> ideal for our work is that it provides the comprehensive set of features included in Vue I18n while adding more functionalities that are specific to Nuxt, like lazy loading locale messages, route generation and redirection for different locales, SEO metadata per locale, locale-specific domains, and more.<\/p>\n<h2 id=\"initial-setup\">Initial Setup<\/h2>\n<p>Start a new Nuxt.js project and set it up with a UI framework of your choice. Again, I will be using Vue to establish the interface for this tutorial.<\/p>\n<p>Let us add a basic layout for our website and set up some sample Vue templates.<\/p>\n<p>First, a \u201cBlog\u201d page:<\/p>\n<pre><code class=\"language-html\"><!-- pages\/blog.vue -->\n<template>\n <div>\n <v-card color=\"cardBackground\">\n <v-card-title class=\"text-overline\">\n Home\n <\/v-card-title>\n <v-card-text>\n This is the home page description\n <\/v-card-text>\n <\/v-card>\n <\/div>\n<\/template>\n<\/code><\/pre>\n<p>Next, an \u201cAbout\u201d page:<\/p>\n<pre><code class=\"language-html\"><!-- pages\/about.vue -->\n<template>\n <div>\n <v-card color=\"cardBackground\">\n <v-card-title class=\"text-overline\">\n About\n <\/v-card-title>\n <v-card-text>\n This is the about page description\n <\/v-card-text>\n <\/v-card>\n <\/div>\n<\/template>\n<\/code><\/pre>\n<p>This gives us a bit of a boilerplate that we can integrate our i18n work into.<\/p>\n<h2 id=\"translating-plain-text\">Translating Plain Text<\/h2>\n<p>The page templates look good, but notice how the text is hardcoded. As far as i18n goes, hardcoded content is difficult to translate into different locales. That is where the <code>nuxt-i18n<\/code> library comes in, providing the language-specific strings we need for the Vue components in the templates.<\/p>\n<p>We\u2019ll start by installing the library via the command line:<\/p>\n<pre><code class=\"language-bash\">npx nuxi@latest module add i18n\n<\/code><\/pre>\n<p>Inside the <code>nuxt.config.ts<\/code> file, we need to ensure that we have <code>@nuxtjs\/i18n<\/code> inside the <code>modules<\/code> array. We can use the <code>i18n<\/code> property to provide module-specific configurations.<\/p>\n<pre><code class=\"language-javascript\">\/\/ nuxt.config.ts\nexport default defineNuxtConfig({\n \/\/ ...\n modules: [\n ...\n \"@nuxtjs\/i18n\",\n \/\/ ...\n ],\n i18n: {\n \/\/ nuxt-i18n module configurations here\n }\n \/\/ ...\n});\n<\/code><\/pre>\n<p>Since the <code>nuxt-i18n<\/code> library is built on top of the Vue I18n library, we can utilize its features in our Nuxt application as well. Let us create a new file, <code>i18n.config.ts<\/code>, which we will use to provide all <code>vue-i18n<\/code> configurations.<\/p>\n<pre><code class=\"language-javascript\">\/\/ i18n.config.ts\nexport default defineI18nConfig(() => ({\n legacy: false,\n locale: \"en\",\n messages: {\n en: {\n homePage: {\n title: \"Home\",\n description: \"This is the home page description.\"\n },\n aboutPage: {\n title: \"About\",\n description: \"This is the about page description.\"\n },\n },\n },\n}));\n<\/code><\/pre>\n<p>Here, we have specified internationalization configurations, like using the <code>en<\/code> locale, and added messages for the <code>en<\/code> locale. These messages can be used inside the markup in the templates we made with the help of a <code>$t<\/code> function from Vue I18n.<\/p>\n<p>Next, we need to link the <code>i18n.config.ts<\/code> configurations in our Nuxt config file.<\/p>\n<pre><code class=\"language-javascript\">\/\/ nuxt.config.ts\nexport default defineNuxtConfig({\n ...\n i18n: {\n vueI18n: \".\/i18n.config.ts\"\n }\n ...\n});\n<\/code><\/pre>\n<p>Now, we can use the <code>$t<\/code> function in our components — as shown below — to parse strings from our internationalization configurations.<\/p>\n<p><strong>Note<\/strong>: <em>There\u2019s no need to import <code>$t<\/code> since we have Nuxt\u2019s default auto-import functionality.<\/em><\/p>\n<pre><code class=\"language-html\"><!-- i18n.config.ts -->\n<template>\n <div>\n <v-card color=\"cardBackground\">\n <v-card-title class=\"text-overline\">\n {{ $t(\"homePage.title\") }}\n <\/v-card-title>\n <v-card-text>\n {{ $t(\"homePage.description\") }}\n <\/v-card-text>\n <\/v-card>\n <\/div>\n<\/template>\n<\/code><\/pre>\n<figure class=\"\n \n break-out article__image\n \n \n \"><\/p>\n<p> <a href=\"https:\/\/files.smashing.media\/articles\/how-build-multilingual-website-nuxtjs\/1-home-page.png\"><\/p>\n<p> <img decoding=\"async\" loading=\"lazy\" width=\"800\" height=\"316\" src=\"data:image\/gif;base64,R0lGODlhAQABAAAAACH5BAEKAAEALAAAAAABAAEAAAICTAEAOw==\" alt=\"Page with a title and description\" class=\"lazyload\" data-src=\"https:\/\/res.cloudinary.com\/indysigner\/image\/fetch\/f_auto,q_80\/w_400\/https:\/\/files.smashing.media\/articles\/how-build-multilingual-website-nuxtjs\/1-home-page.png\"><\/p>\n<p> <\/a><figcaption class=\"op-vertical-bottom\">\n (<a href=\"https:\/\/files.smashing.media\/articles\/how-build-multilingual-website-nuxtjs\/1-home-page.png\">Large preview<\/a>)<br \/>\n <\/figcaption><\/figure>\n<h2 id=\"lazy-loading-translations\">Lazy Loading Translations<\/h2>\n<p>We have the title and description served from the configurations. Next, we can add more languages to the same config. For example, here\u2019s how we can establish translations for English (<code>en<\/code>), French (<code>fr<\/code>) and Spanish (<code>es<\/code>):<\/p>\n<pre><code class=\"language-javascript\">\/\/ i18n.config.ts\nexport default defineI18nConfig(() => ({\n legacy: false,\n locale: \"en\",\n messages: {\n en: {\n \/\/ English\n },\n fr: {\n \/\/ French\n },\n es: {\n \/\/ Spanish\n }\n },\n}));\n<\/code><\/pre>\n<p>For a production website with a lot of content that needs translating, it would be unwise to bundle all of the messages from different locales in the main bundle. Instead, we should use the <code>nuxt-i18<\/code> lazy loading feature asynchronously load only the required language rather than all of them at once. Also, having messages for all locales in a single configuration file can become difficult to manage over time, and breaking them up like this makes things easier to find.<\/p>\n<p>Let\u2019s set up the lazy loading feature in <code>nuxt.config.ts<\/code>:<\/p>\n<pre><code class=\"language-javascript\">\/\/ etc.\n i18n: {\n vueI18n: \".\/i18n.config.ts\",\n lazy: true,\n langDir: \"locales\",\n locales: [\n {\n code: \"en\",\n file: \"en.json\",\n name: \"English\",\n },\n {\n code: \"es\",\n file: \"es.json\",\n name: \"Spanish\",\n },\n {\n code: \"fr\",\n file: \"fr.json\",\n name: \"French\",\n },\n ],\n defaultLocale: \"en\",\n strategy: \"no_prefix\",\n },\n\n\/\/ etc.\n<\/code><\/pre>\n<p>This enables lazy loading and specifies the <code>locales<\/code> directory that will contain our locale files. The <code>locales<\/code> array configuration specifies from which files Nuxt.js should pick up messages for a specific language.<\/p>\n<p>Now, we can create individual files for each language. I\u2019ll drop all three of them right here:<\/p>\n<pre><code class=\"language-javascript\">\n\/\/ locales\/en.json\n{\n \"homePage\": {\n \"title\": \"Home\",\n \"description\": \"This is the home page description.\"\n },\n \"aboutPage\": {\n \"title\": \"About\",\n \"description\": \"This is the about page description.\"\n },\n \"selectLocale\": {\n \"label\": \"Select Locale\"\n },\n \"navbar\": {\n \"homeButton\": \"Home\",\n \"aboutButton\": \"About\"\n }\n}\n<\/code><\/pre>\n<div class=\"break-out\">\n<pre><code class=\"language-javascript\">\/\/ locales\/fr.json\n{\n \"homePage\": {\n \"title\": \"Bienvenue sur la page d'accueil\",\n \"description\": \"Ceci est la description de la page d'accueil.\"\n },\n \"aboutPage\": {\n \"title\": \"\u00c0 propos de nous\",\n \"description\": \"Ceci est la description de la page \u00e0 propos de nous.\"\n },\n \"selectLocale\": {\n \"label\": \"S\u00e9lectionner la langue\"\n },\n \"navbar\": {\n \"homeButton\": \"Accueil\",\n \"aboutButton\": \"\u00c0 propos\"\n }\n}\n<\/code><\/pre>\n<\/div>\n<div class=\"break-out\">\n<pre><code class=\"language-javascript\">\/\/ locales\/es.json\n{\n \"homePage\": {\n \"title\": \"Bienvenido a la p\u00e1gina de inicio\",\n \"description\": \"Esta es la descripci\u00f3n de la p\u00e1gina de inicio.\"\n },\n \"aboutPage\": {\n \"title\": \"Sobre nosotros\",\n \"description\": \"Esta es la descripci\u00f3n de la p\u00e1gina sobre nosotros.\"\n },\n \"selectLocale\": {\n \"label\": \"Seleccione el idioma\"\n },\n \"navbar\": {\n \"homeButton\": \"Inicio\",\n \"aboutButton\": \"Acerca de\"\n }\n}\n<\/code><\/pre>\n<\/div>\n<p>We have set up lazy loading, added multiple languages to our application, and moved our locale messages to separate files. The user gets the right locale for the right message, and the locale messages are kept in a maintainable manner inside the code base.<\/p>\n<h2 id=\"switching-between-languages\">Switching Between Languages<\/h2>\n<p>We have different locales, but to see them in action, we will build a component that can be used to switch between the available locales.<\/p>\n<pre><code class=\"language-html\"><!-- components\/select-locale.vue -->\n<script setup>\nconst { locale, locales, setLocale } = useI18n();\n\nconst language = computed({\n get: () => locale.value,\n set: (value) => setLocale(value),\n});\n<\/script>\n\n<template>\n <v-select\n :label=\"$t('selectLocale.label')\"\n variant=\"outlined\"\n color=\"primary\"\n density=\"compact\" \n :items=\"locales\"\n item-title=\"name\"\n item-value=\"code\"\n v-model=\"language\"\n ><\/v-select>\n<\/template>\n<\/code><\/pre>\n<p>This component uses the <code>useI18n<\/code> hook provided by the Vue I18n library and a computed property <code>language<\/code> to get and set the global locale from a <code><select><\/code> input. To make this even more like a real-world website, we\u2019ll include a small navigation bar that links up all of the website\u2019s pages.<\/p>\n<pre><code class=\"language-html\"><!-- components\/select-locale.vue -->\n<template>\n <v-app-bar app :elevation=\"2\" class=\"px-2\">\n <div>\n <v-btn color=\"button\" to=\"\/\">\n {{ $t(\"navbar.homeButton\") }}\n <\/v-btn>\n <v-btn color=\"button\" to=\"\/about\">\n {{ $t(\"navbar.aboutButton\") }}\n <\/v-btn>\n <\/div>\n <v-spacer \/>\n <div class=\"mr-4 mt-6\">\n <SelectLocale \/>\n <\/div>\n <\/v-app-bar>\n<\/template>\n<\/code><\/pre>\n<p>That\u2019s it! Now, we can switch between languages on the fly.<\/p>\n<figure class=\"\n \n break-out article__image\n \n \n \"><\/p>\n<p> <a href=\"https:\/\/files.smashing.media\/articles\/how-build-multilingual-website-nuxtjs\/2-current-state-homepage.png\"><\/p>\n<p> <img decoding=\"async\" loading=\"lazy\" width=\"800\" height=\"338\" src=\"data:image\/gif;base64,R0lGODlhAQABAAAAACH5BAEKAAEALAAAAAABAAEAAAICTAEAOw==\" alt=\"Showing the current state of the homepage.\" class=\"lazyload\" data-src=\"https:\/\/res.cloudinary.com\/indysigner\/image\/fetch\/f_auto,q_80\/w_400\/https:\/\/files.smashing.media\/articles\/how-build-multilingual-website-nuxtjs\/2-current-state-homepage.png\"><\/p>\n<p> <\/a><figcaption class=\"op-vertical-bottom\">\n Showing the current state of the homepage. (<a href=\"https:\/\/files.smashing.media\/articles\/how-build-multilingual-website-nuxtjs\/2-current-state-homepage.png\">Large preview<\/a>)<br \/>\n <\/figcaption><\/figure>\n<figure class=\"\n \n break-out article__image\n \n \n \"><\/p>\n<p> <a href=\"https:\/\/files.smashing.media\/articles\/how-build-multilingual-website-nuxtjs\/3-homepage-french-translation.png\"><\/p>\n<p> <img decoding=\"async\" loading=\"lazy\" width=\"800\" height=\"362\" src=\"data:image\/gif;base64,R0lGODlhAQABAAAAACH5BAEKAAEALAAAAAABAAEAAAICTAEAOw==\" alt=\"Homepage showing a French translation.\" class=\"lazyload\" data-src=\"https:\/\/res.cloudinary.com\/indysigner\/image\/fetch\/f_auto,q_80\/w_400\/https:\/\/files.smashing.media\/articles\/how-build-multilingual-website-nuxtjs\/3-homepage-french-translation.png\"><\/p>\n<p> <\/a><figcaption class=\"op-vertical-bottom\">\n Showing a French translation. (<a href=\"https:\/\/files.smashing.media\/articles\/how-build-multilingual-website-nuxtjs\/3-homepage-french-translation.png\">Large preview<\/a>)<br \/>\n <\/figcaption><\/figure>\n<p>We have a basic layout, but I thought we\u2019d take this a step further and build a playground page we can use to explore more i18n features that are pretty useful when building a multilingual website.<\/p>\n<h2 id=\"interpolation-and-pluralization\">Interpolation and Pluralization<\/h2>\n<p>Interpolation and pluralization are internationalization techniques for handling dynamic content and grammatical variations across different languages. <strong>Interpolation<\/strong> allows developers to insert dynamic variables or expressions into translated strings. <strong>Pluralization<\/strong> addresses the complexities of plural forms in languages by selecting the appropriate grammatical form based on numeric values. With the help of interpolation and pluralization, we can create more natural and accurate translations.<\/p>\n<p>To use pluralization in our Nuxt app, we\u2019ll first add a configuration to the English locale file.<\/p>\n<pre><code class=\"language-javascript\">\/\/ locales\/en.json\n{\n \/\/ etc.\n \"playgroundPage\": {\n \"pluralization\": {\n \"title\": \"Pluralization\",\n \"apple\": \"No Apple | One Apple | {count} Apples\",\n \"addApple\": \"Add\"\n }\n }\n \/\/ etc.\n}\n<\/code><\/pre>\n<p>The pluralization configuration set up for the key <code>apple<\/code> defines an output — <code>No Apple<\/code> — if a count of 0 is passed to it, a second output — <code>One Apple<\/code> — if a count of 1 is passed, and a third — <code>2 Apples<\/code>, <code>3 Apples<\/code>, and so on — if the count passed in is greater than 1.<\/p>\n<p>Here is how we can use it in your component: Whenever you click on the add button, you will see pluralization in action, changing the strings.<\/p>\n<div class=\"break-out\">\n<pre><code class=\"language-html\"><!-- pages\/playground.vue -->\n<script setup>\nlet appleCount = ref(0);\nconst addApple = () => {\n appleCount.value += 1;\n};\n<\/script>\n<template>\n <v-container fluid>\n <!-- PLURALIZATION EXAMPLE -->\n <v-card color=\"cardBackground\">\n <v-card-title class=\"text-overline\">\n {{ $t(\"playgroundPage.pluralization.title\") }}\n <\/v-card-title>\n\n <v-card-text>\n {{ $t(\"playgroundPage.pluralization.apple\", { count: appleCount }) }}\n <\/v-card-text>\n <v-card-actions>\n <v-btn\n @click=\"addApple\"\n color=\"primary\"\n variant=\"outlined\"\n density=\"comfortable\"\n >{{ $t(\"playgroundPage.pluralization.addApple\") }}<\/v-btn\n >\n <\/v-card-actions>\n <\/v-card>\n <\/v-container>\n<\/template>\n<\/code><\/pre>\n<\/div>\n<p>To use interpolation in our Nuxt app, first, add a configuration in the English locale file:<\/p>\n<div class=\"break-out\">\n<pre><code class=\"language-javascript\">\/\/ locales\/en.json\n{\n ...\n \"playgroundPage\": {\n ... \n \"interpolation\": {\n \"title\": \"Interpolation\",\n \"sayHello\": \"Hello, {name}\",\n \"hobby\": \"My favourite hobby is {0}.\",\n \"email\": \"You can reach out to me at {account}{'@'}{domain}.com\"\n },\n \/\/ etc. \n }\n \/\/ etc.\n}\n<\/code><\/pre>\n<\/div>\n<p>The message for <code>sayHello<\/code> expects an object passed to it having a key <code>name<\/code> when invoked — a process known as <a href=\"https:\/\/vue-i18n.intlify.dev\/guide\/essentials\/syntax#named-interpolation\">named interpolation<\/a>.<\/p>\n<p>The message <code>hobby<\/code> expects an array to be passed to it and will pick up the 0<sup>th<\/sup> element, which is known as <a href=\"https:\/\/vue-i18n.intlify.dev\/guide\/essentials\/syntax#list-interpolation\">list interpolation<\/a>.<\/p>\n<p>The message <code>email<\/code> expects an object with keys <code>account<\/code>, and <code>domain<\/code> and joins both with a literal string <code>"@"<\/code>. This is known as <a href=\"https:\/\/vue-i18n.intlify.dev\/guide\/essentials\/syntax#literal-interpolation\">literal interpolation<\/a>.<\/p>\n<p>Below is an example of how to use it in the Vue components:<\/p>\n<div class=\"break-out\">\n<pre><code class=\"language-html\"><!-- pages\/playground.vue -->\n<template>\n <v-container fluid>\n <!-- INTERPOLATION EXAMPLE -->\n <v-card color=\"cardBackground\">\n <v-card-title class=\"text-overline\">\n {{ $t(\"playgroundPage.interpolation.title\") }}\n <\/v-card-title>\n <v-card-text>\n <p>\n {{\n $t(\"playgroundPage.interpolation.sayHello\", {\n name: \"Jane\",\n })\n }}\n <\/p>\n <p>\n {{\n $t(\"playgroundPage.interpolation.hobby\", [\"Football\", \"Cricket\"])\n }}\n <\/p>\n <p>\n {{\n $t(\"playgroundPage.interpolation.email\", {\n account: \"johndoe\",\n domain: \"hygraph\",\n })\n }}\n <\/p>\n <\/v-card-text>\n <\/v-card>\n <\/v-container>\n<\/template>\n<\/code><\/pre>\n<\/div>\n<figure class=\"\n \n break-out article__image\n \n \n \"><\/p>\n<p> <a href=\"https:\/\/files.smashing.media\/articles\/how-build-multilingual-website-nuxtjs\/4-pluralization-interpolation.png\"><\/p>\n<p> <img decoding=\"async\" loading=\"lazy\" width=\"800\" height=\"501\" src=\"data:image\/gif;base64,R0lGODlhAQABAAAAACH5BAEKAAEALAAAAAABAAEAAAICTAEAOw==\" alt=\"Exampe of pluralization and interpolation\" class=\"lazyload\" data-src=\"https:\/\/res.cloudinary.com\/indysigner\/image\/fetch\/f_auto,q_80\/w_400\/https:\/\/files.smashing.media\/articles\/how-build-multilingual-website-nuxtjs\/4-pluralization-interpolation.png\"><\/p>\n<p> <\/a><figcaption class=\"op-vertical-bottom\">\n (<a href=\"https:\/\/files.smashing.media\/articles\/how-build-multilingual-website-nuxtjs\/4-pluralization-interpolation.png\">Large preview<\/a>)<br \/>\n <\/figcaption><\/figure>\n<h2 id=\"date-time-translations\">Date & Time Translations<\/h2>\n<p>Translating dates and times involves translating date and time formats according to the conventions of different locales. We can use Vue I18n\u2019s features for formatting date strings, handling time zones, and translating day and month names for managing date time translations. We can give the configuration for the same using the <code>datetimeFormats<\/code> key inside the <code>vue-i18n<\/code> config object.<\/p>\n<pre><code class=\"language-javascript\">\/\/ i18n.config.ts\nexport default defineI18nConfig(() => ({\n fallbackLocale: \"en\",\n datetimeFormats: {\n en: {\n short: {\n year: \"numeric\",\n month: \"short\",\n day: \"numeric\",\n },\n long: {\n year: \"numeric\",\n month: \"short\",\n day: \"numeric\",\n weekday: \"short\",\n hour: \"numeric\",\n minute: \"numeric\",\n hour12: false,\n },\n },\n fr: {\n short: {\n year: \"numeric\",\n month: \"short\",\n day: \"numeric\",\n },\n long: {\n year: \"numeric\",\n month: \"short\",\n day: \"numeric\",\n weekday: \"long\",\n hour: \"numeric\",\n minute: \"numeric\",\n hour12: true,\n },\n },\n es: {\n short: {\n year: \"numeric\",\n month: \"short\",\n day: \"numeric\",\n },\n long: {\n year: \"2-digit\",\n month: \"short\",\n day: \"numeric\",\n weekday: \"long\",\n hour: \"numeric\",\n minute: \"numeric\",\n hour12: true,\n },\n },\n },\n}));\n<\/code><\/pre>\n<p>Here, we have set up <code>short<\/code> and <code>long<\/code> formats for all three languages. If you are coding along, you will be able to see available configurations for fields, like month and year, thanks to TypeScript and Intellisense features provided by your code editor. To display the translated dates and times in components, we should use the <code>$d<\/code> function and pass the format to it.<\/p>\n<div class=\"break-out\">\n<pre><code class=\"language-html\"><!-- pages.playground.vue -->\n<template>\n <v-container fluid>\n <!-- DATE TIME TRANSLATIONS EXAMPLE -->\n <v-card color=\"cardBackground\">\n <v-card-title class=\"text-overline\">\n {{ $t(\"playgroundPage.dateTime.title\") }}\n <\/v-card-title>\n <v-card-text>\n <p>Short: {{ (new Date(), $d(new Date(), \"short\")) }}<\/p>\n <p>Long: {{ (new Date(), $d(new Date(), \"long\")) }}<\/p>\n <\/v-card-text>\n <\/v-card>\n <\/v-container>\n<\/template>\n<\/code><\/pre>\n<\/div>\n<figure class=\"\n \n break-out article__image\n \n \n \"><\/p>\n<p> <a href=\"https:\/\/files.smashing.media\/articles\/how-build-multilingual-website-nuxtjs\/5-default-date-time.png\"><\/p>\n<p> <img decoding=\"async\" loading=\"lazy\" width=\"800\" height=\"337\" src=\"data:image\/gif;base64,R0lGODlhAQABAAAAACH5BAEKAAEALAAAAAABAAEAAAICTAEAOw==\" alt=\"Showing a default date and time.\" class=\"lazyload\" data-src=\"https:\/\/res.cloudinary.com\/indysigner\/image\/fetch\/f_auto,q_80\/w_400\/https:\/\/files.smashing.media\/articles\/how-build-multilingual-website-nuxtjs\/5-default-date-time.png\"><\/p>\n<p> <\/a><figcaption class=\"op-vertical-bottom\">\n Showing a default date and time. (<a href=\"https:\/\/files.smashing.media\/articles\/how-build-multilingual-website-nuxtjs\/5-default-date-time.png\">Large preview<\/a>)<br \/>\n <\/figcaption><\/figure>\n<figure class=\"\n \n break-out article__image\n \n \n \"><\/p>\n<p> <a href=\"https:\/\/files.smashing.media\/articles\/how-build-multilingual-website-nuxtjs\/6-date-time-translated-spanish.png\"><\/p>\n<p> <img decoding=\"async\" loading=\"lazy\" width=\"800\" height=\"338\" src=\"data:image\/gif;base64,R0lGODlhAQABAAAAACH5BAEKAAEALAAAAAABAAEAAAICTAEAOw==\" alt=\"Showing the date and time translated in Spanish.\" class=\"lazyload\" data-src=\"https:\/\/res.cloudinary.com\/indysigner\/image\/fetch\/f_auto,q_80\/w_400\/https:\/\/files.smashing.media\/articles\/how-build-multilingual-website-nuxtjs\/6-date-time-translated-spanish.png\"><\/p>\n<p> <\/a><figcaption class=\"op-vertical-bottom\">\n Showing the date and time translated in Spanish. (<a href=\"https:\/\/files.smashing.media\/articles\/how-build-multilingual-website-nuxtjs\/6-date-time-translated-spanish.png\">Large preview<\/a>)<br \/>\n <\/figcaption><\/figure>\n<h2 id=\"localization-on-the-hygraph-side\">Localization On the Hygraph Side<\/h2>\n<p>We saw how to implement localization with static content. Now, we\u2019ll attempt to understand how to fetch dynamic localized content in Nuxt.<\/p>\n<p>We can build a blog page in our Nuxt App that fetches data from a server. The server API should accept a locale and return data in that specific locale.<\/p>\n<p>Hygraph has a flexible localization API that allows you to publish and query localized content. If you haven\u2019t created a free Hygraph account yet, <a href=\"https:\/\/hygraph.com\/\">you can do that on the Hygraph website<\/a> to continue following along.<\/p>\n<p>Go to <strong>Project Settings<\/strong> → <strong>Locales<\/strong> and add locales for the API.<\/p>\n<figure class=\"\n \n break-out article__image\n \n \n \"><\/p>\n<p> <a href=\"https:\/\/files.smashing.media\/articles\/how-build-multilingual-website-nuxtjs\/7-hygraph-studio-locales.png\"><\/p>\n<p> <img decoding=\"async\" loading=\"lazy\" width=\"800\" height=\"409\" src=\"data:image\/gif;base64,R0lGODlhAQABAAAAACH5BAEKAAEALAAAAAABAAEAAAICTAEAOw==\" alt=\"Showing Locales on the Hygraph website\" class=\"lazyload\" data-src=\"https:\/\/res.cloudinary.com\/indysigner\/image\/fetch\/f_auto,q_80\/w_400\/https:\/\/files.smashing.media\/articles\/how-build-multilingual-website-nuxtjs\/7-hygraph-studio-locales.png\"><\/p>\n<p> <\/a><figcaption class=\"op-vertical-bottom\">\n (<a href=\"https:\/\/files.smashing.media\/articles\/how-build-multilingual-website-nuxtjs\/7-hygraph-studio-locales.png\">Large preview<\/a>)<br \/>\n <\/figcaption><\/figure>\n<p>We have added two locales: English and French. Now we need aq <code>localized_post<\/code> model in our schema that only two fields: <code>title<\/code> and <code>body.<\/code> Ensure to make these \u201cLocalized\u201d fields while creating them.<\/p>\n<figure class=\"\n \n break-out article__image\n \n \n \"><\/p>\n<p> <a href=\"https:\/\/files.smashing.media\/articles\/how-build-multilingual-website-nuxtjs\/8-hygraph-studio-schema.png\"><\/p>\n<p> <img decoding=\"async\" loading=\"lazy\" width=\"800\" height=\"331\" src=\"data:image\/gif;base64,R0lGODlhAQABAAAAACH5BAEKAAEALAAAAAABAAEAAAICTAEAOw==\" alt=\"Showing Schema, which has title and body fields, on the Htygraph website\" class=\"lazyload\" data-src=\"https:\/\/res.cloudinary.com\/indysigner\/image\/fetch\/f_auto,q_80\/w_400\/https:\/\/files.smashing.media\/articles\/how-build-multilingual-website-nuxtjs\/8-hygraph-studio-schema.png\"><\/p>\n<p> <\/a><figcaption class=\"op-vertical-bottom\">\n (<a href=\"https:\/\/files.smashing.media\/articles\/how-build-multilingual-website-nuxtjs\/8-hygraph-studio-schema.png\">Large preview<\/a>)<br \/>\n <\/figcaption><\/figure>\n<p>Add permissions to consume the localized content, go to <strong>Project settings<\/strong> → <strong>Access<\/strong> → <strong>API Access<\/strong> → <strong>Public Content API<\/strong>, and assign <strong>Read<\/strong> permissions to the <code>localized_post<\/code> model.<\/p>\n<figure class=\"\n \n break-out article__image\n \n \n \"><\/p>\n<p> <a href=\"https:\/\/files.smashing.media\/articles\/how-build-multilingual-website-nuxtjs\/9-hygraph-studio-permissions.png\"><\/p>\n<p> <img decoding=\"async\" loading=\"lazy\" width=\"800\" height=\"394\" src=\"data:image\/gif;base64,R0lGODlhAQABAAAAACH5BAEKAAEALAAAAAABAAEAAAICTAEAOw==\" alt=\"Project settings on the Htygraph website with Public Content API and an assigned Read permission\" class=\"lazyload\" data-src=\"https:\/\/res.cloudinary.com\/indysigner\/image\/fetch\/f_auto,q_80\/w_400\/https:\/\/files.smashing.media\/articles\/how-build-multilingual-website-nuxtjs\/9-hygraph-studio-permissions.png\"><\/p>\n<p> <\/a><figcaption class=\"op-vertical-bottom\">\n (<a href=\"https:\/\/files.smashing.media\/articles\/how-build-multilingual-website-nuxtjs\/9-hygraph-studio-permissions.png\">Large preview<\/a>)<br \/>\n <\/figcaption><\/figure>\n<p>Now, we can go to the <a href=\"https:\/\/hygraph.com\/docs\/api-reference\/basics\/api-playground\">Hygrapgh API playground<\/a> and add some localized data to the database with the help of GraphQL mutations. To limit the scope of this example, I am simply adding data from the Hygraph API playground. In an ideal world, a create\/update mutation would be triggered from the front end after receiving user input.<\/p>\n<p>Run this mutation in the Hygraph API playground:<\/p>\n<div class=\"break-out\">\n<pre><code class=\"language-javascript\">mutation createLocalizedPost {\n createLocalizedPost(\n data: {\n title: \"A Journey Through the Alps\", \n body: \"Exploring the majestic mountains of the Alps offers a thrilling experience. The stunning landscapes, diverse wildlife, and pristine environment make it a perfect destination for nature lovers.\", \n localizations: {\n create: [\n {locale: fr, data: {title: \"Un voyage \u00e0 travers les Alpes\", body: \"Explorer les majestueuses montagnes des Alpes offre une exp\u00e9rience palpitante. Les paysages \u00e9poustouflants, la faune diversifi\u00e9e et l'environnement immacul\u00e9 en font une destination parfaite pour les amoureux de la nature.\"}}\n ]\n }\n }\n ) {\n id\n }\n}\n<\/code><\/pre>\n<\/div>\n<p>The mutation above creates a post with the <code>en<\/code> locale and includes a <code>fr<\/code> version of the same post. Feel free to add more data to your model if you want to see things work from a broader set of data.<\/p>\n<h2 id=\"putting-things-together\">Putting Things Together<\/h2>\n<p>Now that we have Hygraph API content ready for consumption let\u2019s take a moment to understand how it\u2019s consumed inside the Nuxt app.<\/p>\n<p>To do this, we\u2019ll install <a href=\"https:\/\/nuxt.com\/modules\/graphql-client\">nuxt-graphql-client<\/a> to serve as the app\u2019s GraphQL client. This is a minimal GraphQL client for performing GraphQL operations without having to worry about complex configurations, code generation, typing, and other setup tasks.<\/p>\n<pre><code class=\"language-bash\">npx nuxi@latest module add graphql-client\n<\/code><\/pre>\n<pre><code class=\"language-javascript\">\/\/ nuxt.config.ts\nexport default defineNuxtConfig({\n modules: [\n \/\/ ...\n \"nuxt-graphql-client\"\n \/\/ ...\n ],\n runtimeConfig: {\n public: {\n GQL_HOST: 'ADD_YOUR_GQL_HOST_URL_HERE_OR_IN_.env'\n }\n },\n});\n<\/code><\/pre>\n<p>Next, let’s add our GraphQL queries in <code>graphql\/queries.graphql<\/code>.<\/p>\n<pre><code class=\"language-javascript\">query getPosts($locale: [Locale!]!) {\n localizedPosts(locales: $locale) {\n title\n body\n }\n}\n<\/code><\/pre>\n<p>The GraphQL client will automatically scan <code>.graphql<\/code> and <code>.gql<\/code> files and generate client-side code and typings in the <code>.nuxt\/gql<\/code> folder. All we need to do is stop and restart the Nuxt application. After restarting the app, the GraphQL client will allow us to use a <code>GqlGetPosts<\/code> function to trigger the query.<\/p>\n<p>Now, we will build the Blog page where by querying the Hygraph server and showing the dynamic data.<\/p>\n<pre><code class=\"language-javascript\">\/\/ pages\/blog.vue\n<script lang=\"ts\" setup>\n import type { GetPostsQueryVariables } from \"#gql\";\n import type { PostItem, Locale } from \"..\/types\/types\";\n\n const { locale } = useI18n();\n const posts = ref<PostItem[]>([]);\n const isLoading = ref(false);\n const isError = ref(false);\n\n const fetchPosts = async (localeValue: Locale) => {\n try {\n isLoading.value = true;\n const variables: GetPostsQueryVariables = {\n locale: [localeValue],\n };\n const data = await GqlGetPosts(variables);\n posts.value = data?.localizedPosts ?? [];\n } catch (err) {\n console.log(\"Fetch Error, Something went wrong\", err);\n isError.value = true;\n } finally {\n isLoading.value = false;\n }\n };\n\n \/\/ Fetch posts on component mount\n onMounted(() => {\n fetchPosts(locale.value as Locale);\n });\n\n \/\/ Watch for locale changes\n watch(locale, (newLocale) => {\n fetchPosts(newLocale as Locale);\n });\n<\/script>\n<\/code><\/pre>\n<p>This code fetches only the current locale from the <code>useI18n<\/code> hook and sends it to the <code>fetchPosts<\/code> function when the Vue component is mounted. The <code>fetchPosts<\/code> function will pass the locale to the GraphQL query as a variable and obtain localized data from the Hygraph server. We also have a watcher on the <code>locale<\/code> so that whenever the global locale is changed by the user we make an API call to the server again and fetch posts in that locale.<\/p>\n<p>And, finally, let\u2019s add markup for viewing our fetched data!<\/p>\n<div class=\"break-out\">\n<pre><code class=\"language-html\"><!-- pages\/blog.vue -->\n<template>\n <v-container fluid>\n <v-card-title class=\"text-overline\">Blogs<\/v-card-title>\n <div v-if=\"isLoading\">\n <v-skeleton-loader type=\"card\" v-for=\"n in 2\" :key=\"n\" class=\"mb-4\" \/>\n <\/div>\n <div v-else-if=\"isError\">\n <p>Something went wrong while getting blogs please check the logs.<\/p>\n <\/div>\n <div v-else>\n <div\n v-for=\"(post, index) in posts\"\n :key=\"post.title || index\"\n class=\"mb-4\"\n >\n <v-card color=\"cardBackground\">\n <v-card-title class=\"text-h6\">{{ post.title }}<\/v-card-title>\n <v-card-text>{{ post.body }}<\/v-card-text>\n <\/v-card>\n <\/div>\n <\/div>\n <\/v-container>\n<\/template>\n<\/code><\/pre>\n<\/div>\n<p>Awesome! If all goes according to plan, then your app should look something like the one in the following video.<\/p>\n<figure class=\"video-embed-container break-out\">\n<div class=\"video-embed-container--wrapper\"><\/div>\n<\/figure>\n<h2 id=\"wrapping-up\">Wrapping Up<\/h2>\n<p>Check that out — we just made the <strong>functionality for translating content for a multilingual website<\/strong>! Now, a user can select a locale from a list of options, and the app fetches content for the selected locale and automatically updates the displayed content.<\/p>\n<p>Did you think that translations would require more difficult steps? It\u2019s pretty amazing that we\u2019re able to cobble together a couple of libraries, hook them up to an API, and wire everything up to render on a page.<\/p>\n<p>Of course, there are other libraries and resources for handling internationalization in a multilingual context. The exact tooling is less the point than it is seeing what pieces are needed to handle dynamic translations and how they come together.<\/p>\n<div class=\"signature\">\n <img decoding=\"async\" src=\"data:image\/gif;base64,R0lGODlhAQABAAAAACH5BAEKAAEALAAAAAABAAEAAAICTAEAOw==\" alt=\"Smashing Editorial\" width=\"35\" height=\"46\" loading=\"lazy\" class=\"lazyload\" data-src=\"https:\/\/www.smashingmagazine.com\/images\/logo\/logo--red.png\"><br \/>\n <span>(gg, yk)<\/span>\n<\/div>\n<\/article>\n","protected":false},"excerpt":{"rendered":"<p>How To Build A Multilingual Website With Nuxt.js How To Build A Multilingual Website With Nuxt.js Tim Benniks 2024-08-01T15:00:00+00:00 2024-10-15T23:05:45+00:00 This article is sponsored by Hygraph Internationalization, often abbreviated as i18n, is the process of designing and developing software applications in a way that they can be easily adapted to various spoken languages like English,…<\/p>\n","protected":false},"author":1,"featured_media":0,"comment_status":"open","ping_status":"open","sticky":false,"template":"","format":"standard","meta":{"footnotes":""},"categories":[10],"tags":[],"class_list":["post-437","post","type-post","status-publish","format-standard","hentry","category-javascript"],"_links":{"self":[{"href":"https:\/\/fdswebdesign.com\/index.php\/wp-json\/wp\/v2\/posts\/437","targetHints":{"allow":["GET"]}}],"collection":[{"href":"https:\/\/fdswebdesign.com\/index.php\/wp-json\/wp\/v2\/posts"}],"about":[{"href":"https:\/\/fdswebdesign.com\/index.php\/wp-json\/wp\/v2\/types\/post"}],"author":[{"embeddable":true,"href":"https:\/\/fdswebdesign.com\/index.php\/wp-json\/wp\/v2\/users\/1"}],"replies":[{"embeddable":true,"href":"https:\/\/fdswebdesign.com\/index.php\/wp-json\/wp\/v2\/comments?post=437"}],"version-history":[{"count":1,"href":"https:\/\/fdswebdesign.com\/index.php\/wp-json\/wp\/v2\/posts\/437\/revisions"}],"predecessor-version":[{"id":438,"href":"https:\/\/fdswebdesign.com\/index.php\/wp-json\/wp\/v2\/posts\/437\/revisions\/438"}],"wp:attachment":[{"href":"https:\/\/fdswebdesign.com\/index.php\/wp-json\/wp\/v2\/media?parent=437"}],"wp:term":[{"taxonomy":"category","embeddable":true,"href":"https:\/\/fdswebdesign.com\/index.php\/wp-json\/wp\/v2\/categories?post=437"},{"taxonomy":"post_tag","embeddable":true,"href":"https:\/\/fdswebdesign.com\/index.php\/wp-json\/wp\/v2\/tags?post=437"}],"curies":[{"name":"wp","href":"https:\/\/api.w.org\/{rel}","templated":true}]}}