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
-
The
/content
folder is where Nuxt/Content will look for the articles we've written
-
The
/en
en
/nl
folders are used to keep the languages seperate
-
The
/blog
folder is for the specfic content we want to write
-
The
blog-post.md
file is the actual blog post that will be visible on the website
Now 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>
<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 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>
<nuxt-link v-if="$i18n.locale !== 'en'" :to="switchLocalePath('en')">
</nuxt-link>
<nuxt-link v-if="$i18n.locale !== 'nl'" :to="switchLocalePath('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>