Studio Terabyte is a full stack web development studio who finds and builds solutions that fit your project
When you manage a website which is being visited by people from multiple countries, it is a good idea to offer you content in multiple languages. This way you make your content accessible for as many people as possible without them being depenedent on external tools such as Google Translate. To make this possible for this website @Nuxt/Content
is being used to write and display the content and nuxt-i18n
to make the content availble in multiple languages.
@Nuxt/Content
is a NuxtJS module which you can use to easily use Git as an headless CMS to build a blog or case study archive for your website. This means that instead of a CMS like Wordpress or Strapi where you would write your content normally, you can simply write the article in Markdown (a .md
file), add this file to your Git repository and your NuxtJS app can retrieve the content as if it communicates with a fullblown CMS. This makes for an easy to maintain compact web app where the layout and content live in one place.
i18n or internationalization is the process where the structure of a website is designed in such a way that the content being made for the website can easily be presented in multiple different languages. Making the content itself ready for different locales is also known as localization or i10n. nuxt-i18n
is a NuxtJS module that will take of the heavy lifting for us. This module will add the correct country code to the URL ('/nl', '/en' etc.), detect the visitors language based on the browser and will do SEO optimizations. Together with the correct app structure and a translation of the content on this modules makes it easy to localise the website.
First of all we need to setup a new NuxtJS project. Open your favorite CLI and make sure that Node and NPM are installed. Now you can run the following command to start the setup process:
npm init nuxt-app <project-name>
Now you need to answer a number of questions for the setup (name, Nuxt options, UI framework, TypeScript, linter, testing framework, etc), choose the option you need for your project.
Now navigate to the new folder with your project files and start the development server:
cd <project-name>
npm run dev
Your Nuxt web app is now live at http://localhost:3000 π
Now that the basis is built, we can install the @Nuxt/Content
module
npm install @nuxt/content
After the instalation is complete you have to add the module to the nuxt.config.js
file
{
modules: [
'@nuxt/content'
],
content: {
// Options
}
}
Now we can install the nuxt-i18n
modules
npm install nuxt-i18n
This also has to be added to the nuxt.config.js
file
{
modules: [
'@nuxt/content',
'nuxt-i18n'
],
i18n: {
// Options
},
}
To start we first need to configure nuxt-i18n
for the different languages we want to support. In this case it's Dutch π³π± and English πΊπΈ where Dutch will be the default language. Add the following settings to nuxt-config.js
:
{
modules: [
'@nuxt/content',
'nuxt-i18n'
],
i18n: {
defaultLocale: 'nl',
locales: [{
code: 'en',
iso: 'en-US',
file: 'en-US.json'
},
{
code: 'nl',
iso: 'nl-NL',
file: 'nl-NL.json'
}
],
langDir: 'locales/',
vueI18n: {
fallbackLocale: 'nl'
}
},
}
Here we set Dutch and English as the two "locales", both with their own country code, iso code and a reference to a .json
file. This file contains the translations of the static pages of the website such as the homepage and the contact page. Translations for non-static pages, such as blog posts, will be saved in a different location. We will get back to those.
Now that the nuxt-i18n
options have been updated we can create a new folder in the project root named locales
containing the .json
files for the static English and Dutch text.
.
βββ .github
βββ .nuxt
βββ components
βββ content
βββ locales <--- This folder
β βββ en-US.json <--- English text
β βββ nl-NL.json <--- Dutch text
βββ node_modules
βββ pages
βββ static
βββ store
βββ .editconfig
βββ .eslintrc.js
βββ .gitignore
βββ .prettierrc
βββ jsconfig.json
βββ nuxt.config.js
βββ package-lock.json
βββ package.json
βββ .README.md
Now open the en-US.json
file and add the following:
{
"welcome": "Hello, World! π"
}
Do the same for the nl-NL.json
file:
{
"welcome": "Hallo, Wereld! π"
}
Now that the translation file for the statich page are finished, we can start using them. Start by deleting components/Tutorial.vue
. Next, open pages/index.vue
and replace here the <Tutorial />
component by <h1>{{ $t('welcome') }}</h1>
.
If you now start the development server and navigate to http://localhost:3000 you should see "Hallo, Wereld! π"! Not just that, if you navigate to http://localhost:3000/en you should see "Hello, World! π". The static content is now availble in both languages!
Besides the internationalization of the static content, it is also important to offer the dynamic content in multiple languages. In the case of Studio Terabye these are the cases studies and the blog posts (the one you are reading now π).
To make use of Nuxt/Content to write the dynamic content you first need to create a /content
directory. Within you need to create the following file structure:
.
βββ .github
βββ .nuxt
βββ components
βββ content <--- This folder
β βββ en <--- A folder per language
β β βββ blog <--- A folder for the content catagory
β β βββ blog-post.md <--- A file for the blog post
β βββ nl <--- A folder per language
β βββ blog <--- A folder for the content catagory
β βββ blog-post.md <--- A file for the blog post
βββ locales
β βββ en-US.json
β βββ nl-NL.json
βββ node_modules
βββ pages
βββ static
βββ store
βββ .editconfig
βββ .eslintrc.js
βββ .gitignore
βββ .prettierrc
βββ jsconfig.json
βββ nuxt.config.js
βββ package-lock.json
βββ package.json
βββ .README.md
/content
folder is where Nuxt/Content will look for the articles we've written/en
en /nl
folders are used to keep the languages seperate/blog
folder is for the specfic content we want to writeblog-post.md
file is the actual blog post that will be visible on the websiteNow open the blog-post.md
file in the /en
folder and add the following:
---
title: The first post
short: This is the first blog post
tags:
- content
- i18n
- markdown
---
## This is the first blog post on your brand new website in multiple languages
Now open the blog-post.md
file in the /nl
folder and add the following:
---
title: De eerste post
short: Dit is de eerste blog post
tags:
- content
- i18n
- markdown
---
## Dit is de eerste blog post op je gloednieuwe website in meerdere talen
The structure is now ready to support blog posts in multiple languages, however they still need their own page. For this we need a new folder and two new files:
.
βββ .github
βββ .nuxt
βββ components
βββ content
β βββ en
β β βββ blog
β β βββ blog-post.md
β βββ nl
β βββ blog
β βββ blog-post.md
βββ locales
β βββ en-US.json
β βββ nl-NL.json
βββ node_modules
βββ pages
β βββ blog <-- This folder
β βββ _slug.vue <-- This file
β βββ index.vue <-- This file
βββ static
βββ store
βββ .editconfig
βββ .eslintrc.js
βββ .gitignore
βββ .prettierrc
βββ jsconfig.json
βββ nuxt.config.js
βββ package-lock.json
βββ package.json
βββ .README.md
The pages/blog
folder in combination with the index.vue
file tells NuxtJS that we want a page for www.example.com/blog. The _slug.vue
file in the same folder tells NuxtJS that we also want to create a page for the individual blog posts like www.example.com/blog/blog-post where _slug.vue
represents the individual posts
Now open the _slug.vue
file and add the following code:
<template>
<div>
<h1>{{ post.title }}</h1>
<nuxt-content :document="post" />
</div>
</template>
<script>
export default {
name: 'BlogSlug',
async asyncData({ $content, params, app, error }) {
const post = await $content(app.i18n.locale, 'blog', params.slug)
.fetch()
.catch(() => {
error({ statusCode: 404, message: 'Page not found' })
})
return { post }
},
}
</script>
Okay, let's go through the code. The first part, between the <template>
tags is responsible for what is visisble on screen. In this case there is an <h1>
tag (a heading) which shows the title of the blog post by retriving it from the post
object. Below this tag is another tag which you might not recognize: <nuxt-content :document="post" />
. This is a tag which lets the @Nuxt/Content
module know that we want to show the blog post content here.
The second part of the code, between the <script>
tags, is the code responsible for retrieving the blog post itself. First we define the name of the file so that it can be used internally if needed: name: 'BlogSlug',
.
Next we use the asyncData
function which is called by NuxtJS when the page is being loaded. Within this function we can access the /content
folder by using the $content
variable, which is automatically availble. Here we pass the country code of the locale which is currently selected (nl/en) via app.i18n.locale
, the name of the folder where we want to look for blog content via blog
and the URL parameters (www.example.com/blog/this-text-here) via params.slug
. Now that all the data that we want to retrieve has been filled in, we fetch the blog post data via the .fetch()
function. Possible errors are caught and handled by the .catch()
function.
Now start the development server and in your browser navigate to http://localhost:3000/blog/blog-post. You should see your fist blog post! π If you want to see the English version you can navigate to http://localhost:3000/en/blog/blog-post.
It's great that we can show the individual blog posts, now your visitors can read your content, but an overview page for your blog posts can't be missing. This will help your visitors discover your content and will make the website more clear.
Open now the index.vue
file and add the following code:
<template>
<div>
<div v-for="(post, index) in posts" :key="index">
<p>{{ post.short }}</p>
<nuxt-link :to="localePath(post.path)">LINK</nuxt-link>
</div>
</div>
</template>
<script>
export default {
name: 'BlogOverview',
async asyncData({ $content, app, error }) {
const posts = await $content(app.i18n.locale, 'blog')
.only(['short', 'path'])
.sortBy('createdAt', 'asc')
.fetch()
.catch(() => {
error({ statusCode: 404, message: 'Page not found' })
})
return {
posts: posts.map((posts) => ({
...posts,
path: posts.path.replace(`/${app.i18n.locale}`, ''),
}))
}
},
}
</script>
The code above looks a lot like the code we've used to retrieve the individual blog posts, in both cases we retrieve blog posts data, but there are enough differences here for it to be worth it to go through the code again. Let's start again with the code between the <template>
tags. To prevent that we need to manually add a seperate piece of code for each blog post that we want to show on the overview page, we will build the layout dynamically. We do this by using a for loop: <div v-for="(post, index) in posts" :key="index">
. This will get all the posts we've retrieved and makes a <div>
element for each one automatically. This way it doesn't matter how many blog posts you write, they will automatically be displayed. Now that we have an element on the page for each blog post, something should of course be displayed there. In this case we show two things: a short summary of the post (the short) and a link to the post itself:
<!-- The short of the post -->
<p>{{ post.short }}</p>
<!-- The link to the post -->
<nuxt-link :to="localePath(post.path)">LINK</nuxt-link>
As you can see in the code we call the localePath
function before we give the post URL to the <nuxt-link>
tag. This function will take care of adjusting the link so that it matched the currently selected language. So if the selected website language is English, /en
will be added to the URL.
The piece of code between the <script>
tags is, just as the code for the individual blog posts, responsible for retrieving the blog posts data. We make use of the asyncData
function provided by NuxtJS to do this. The differences here are the .only(['short', 'path'])
function which indicates what blog specific data we want to retrieve and the .sortBy('createdAt', 'asc')
function wich sorts the blog posts in ascending order based on the creation date.
Finally we take the blog posts which have been retrieved and remove the locale from the URL (/nl or /en). We do this because @Nuxt/Content
returns that URL to the blog post as www.example.com/nl/blog/post-naam which isn't a valid URL because in our settings we've indicated that we don't want to show /nl since this is the default language.
Now start the development server and open http://localhost:3000/blog in your browser. You should see an overview with your first blog post! π If you want to see the English version you can navigate to http://localhost:3000/en/blog.
Now that we have an overview of the blog posts and the individual blog posts themselves availble in multiple languages, we are only missing the option to easiliy switch between the languages. To build this functionality we need a new component. Add LanguageSwitcher.vue
to the /components
folder:
.
βββ .github
βββ .nuxt
βββ components
β βββ LanguageSwitcher.vue <-- This file
βββ content
β βββ en
β β βββ blog
β β βββ blog-post.md
β βββ nl
β βββ blog
β βββ blog-post.md
βββ locales
β βββ en-US.json
β βββ nl-NL.json
βββ node_modules
βββ pages
β βββ blog <-- Deze map
β βββ _slug.vue
β βββ index.vue
βββ static
βββ store
βββ .editconfig
βββ .eslintrc.js
βββ .gitignore
βββ .prettierrc
βββ jsconfig.json
βββ nuxt.config.js
βββ package-lock.json
βββ package.json
βββ .README.md
Now open LanguageSwitcher.vue
and add the following code:
<template>
<div>
<nuxt-link v-if="$i18n.locale !== 'en'" :to="switchLocalePath('en')">
EN
</nuxt-link>
<nuxt-link v-if="$i18n.locale !== 'nl'" :to="switchLocalePath('nl')">
NL
</nuxt-link>
</div>
</template>
<script>
export default {
name: 'LanguageSwitcher',
}
</script>
Between the <template>
tags you will see that we've added two nuxt-link
tags, both with a v-if
condition. The first nuxt-link will change the current page to the English version and will only be visible if the current locale isn't English. The second nuxt-link change the current page to the Dutch version and is only visisble if the current locale isn't Dutch.
To show this link above each page we need to create a new folder, /layouts
containing a new file default.vue
:
.
βββ .github
βββ .nuxt
βββ components
β βββ LanguageSwitcher.vue
βββ content
β βββ en
β β βββ blog
β β βββ blog-post.md
β βββ nl
β βββ blog
β βββ blog-post.md
βββ layouts <-- This folder
β βββ default.vue <-- This file
βββ locales
β βββ en-US.json
β βββ nl-NL.json
βββ node_modules
βββ pages
β βββ blog
β βββ _slug.vue
β βββ index.vue
βββ static
βββ store
βββ .editconfig
βββ .eslintrc.js
βββ .gitignore
βββ .prettierrc
βββ jsconfig.json
βββ nuxt.config.js
βββ package-lock.json
βββ package.json
βββ .README.md
default.vue
indicates what the layout of the website should look like. This way you can for example easily add a menu or a footer to each page without copying the code to all the individual pages.
Now open default.vue
and add the following code:
<template>
<div>
<LanguageSwitcher />
<Nuxt />
</div>
</template>
Here we tell NuxtJS that for every page we first want to show the language switcher component and below the rest of the content.
Now start the development server and navigate to http://localhost:3000/blog in your browser. You should see EN above the page. When you click on this the language, and thus the content on the page, will be automatically changed to English!
Congratulations, you now have a basis for a modern multi-language website! π