Building Multilingual Strapi CMS - Harnessing Apollo GraphQL and Nuxt.js for Seamless Integration
Build a searchable multilingual help center website on top of Strapi CMS, Nuxt.js, Chakra UI, and Apollo GraphQL endpoint.
Web StoryPrerequisite
I got a project to develop a help center website with multilingual and search features for users to get information about the products on our company website. Previously I already had experience in developing multilingual websites like this one. The difference was in the technology behind it, previously I used PHP Laravel framework stack, and now I am using Node.js JavaScript-based framework.
Previously I am using a CMS (Custom Management System) complete with Role Base Access Control that I build from the scratch with other great libraries on top of it. For this one, I am using Strapi CMS since it already had the similarity to the CMS that I already developed, and of course other great features.
For an introduction preview, this is the preview of what I am developing:
Strapi Front-End on default English language
Strapi Front-End on default Indonesian language
Strapi Front-End on default Japanese language
This project contain two source code, for the back-end is the Strapi CMS and the front-end is for Nuxt.js.
Strapi CMS Internationalization Multilingual Setup
I am using Strapi CMS 4.1.10
for this project, MySQL for the database driver, and GraphQL for the API endpoint.
Setup the database connection, and environment, also make sure that all is already connected. Read our other blog post on how to set up MySQL on your local Docker container.
Using PostgreSQL is also possible with Strapi CMS. Read our guide on how to connect PostgreSQL with PgAdmin for improved database administration. This is the default installation on Strapi CMS for a multilingual website using Apollo GraphQL:
./package.json
{
"name": "strapi-mysql",
"private": true,
"version": "0.1.0",
"description": "A Strapi application",
"scripts": {
"develop": "strapi develop",
"start": "strapi start",
"build": "strapi build",
"strapi": "strapi"
},
"devDependencies": {},
"dependencies": {
"@strapi/plugin-graphql": "4.1.10",
"@strapi/plugin-i18n": "4.1.1",
"@strapi/plugin-users-permissions": "4.1.10",
"@strapi/strapi": "4.1.10",
"mysql": "2.18.1"
},
"author": {
"name": "A Strapi developer"
},
"strapi": {
"uuid": "88806ec6-21ce-47ab-b6ee-b243f020e412"
},
"engines": {
"node": ">=12.x.x <=16.x.x",
"npm": ">=6.0.0"
},
"license": "MIT"
}
After the installation some setup need to be written, a database credential setup and character set. Database Setup for Strapi CMS Multilingual Website:
module.exports = ({ env }) => ({
connection: {
client: 'mysql',
connection: {
host: env('DATABASE_HOST', '127.0.0.1'),
port: env.int('DATABASE_PORT', 3307),
database: env('DATABASE_NAME', 'strapi-mysql'),
user: env('DATABASE_USERNAME', 'root'),
password: env('DATABASE_PASSWORD', 'example'),
charset: env('DATABASE_CHARSET', 'utf8mb4'),
ssl: env.bool('DATABASE_SSL', false)
},
options: {
charset: 'utf8mb4_unicode_ci',
debug: true
}
}
})
CMS Content Types Development
After the Strapi CMS installation, enter the Strapi CMS administration and have the admin credential to log in to the administration dashboard panel. First thing is to develop the CMS multilingual internationalization setup, content types for our CMS needs, and role-based access for the administration dashboard:
Strapi CMS Login administration
Strapi CMS multilingual internationalization set up list
Strapi CMS multilingual internationalization add languages
Strapi CMS multilingual internationalization after add language list
For the GraphQL queries in Strapi CMS, we can access the GraphQL Playground in our localhost: http://localhost:1337/graphql
, and begin to develop the GraphQL queries for the web application to fetch. Schemas and documentation are available for GraphQL queries from the Strapi CMS to start creating the GraphQL queries for our web apps.
Strapi CMS GraphQL Playground (GraphQL query to fetch data with default language)
Strapi CMS GraphQL Playground Schemas and Documentation
Nuxtjs Development
For the front-end side, I am using Chakra UI in Nuxt.js modules to get the component library needs in this multilanguage website.
There are some other node modules packages to install to get this project going and here is the package.json
file to install including @nuxtjs/i18n
, @nuxtjs/apollo
, graphql-tag
and of course @chakra-ui/nuxt
:
./package.json
{
"name": "nuxtjs-i18n-app",
"version": "1.0.0",
"private": true,
"scripts": {
"dev": "nuxt",
"build": "nuxt build",
"start": "nuxt start",
"generate": "nuxt generate",
"lint:js": "eslint --ext \".js,.ts,.vue\" --ignore-path .gitignore .",
"lint:style": "stylelint \"**/*.{css,scss,sass,html,vue}\" --ignore-path .gitignore",
"lint:prettier": "prettier --check .",
"lint": "yarn lint:js && yarn lint:style && yarn lint:prettier",
"lintfix": "prettier --write --list-different . && yarn lint:js --fix && yarn lint:style --fix",
"prepare": "husky install",
"test": "jest"
},
"lint-staged": {
"*.{js,ts,vue}": "eslint --cache",
"*.{css,scss,sass,html,vue}": "stylelint",
"*.**": "prettier --check --ignore-unknown"
},
"dependencies": {
"@chakra-ui/nuxt": "^0.4.2",
"@nuxtjs/apollo": "^4.0.1-rc.5",
"@nuxtjs/date-fns": "^1.5.0",
"@nuxtjs/emotion": "^0.1.0",
"@nuxtjs/i18n": "^7.2.0",
"core-js": "^3.19.3",
"graphql-tag": "^2.12.6",
"nuxt": "^2.15.8",
"vue": "^2.6.14",
"vue-server-renderer": "^2.6.14",
"vue-template-compiler": "^2.6.14",
"webpack": "^4.46.0"
},
"devDependencies": {
"@babel/eslint-parser": "^7.16.3",
"@commitlint/cli": "^15.0.0",
"@commitlint/config-conventional": "^15.0.0",
"@nuxt/types": "^2.15.8",
"@nuxt/typescript-build": "^2.1.0",
"@nuxtjs/eslint-config-typescript": "^8.0.0",
"@nuxtjs/eslint-module": "^3.0.2",
"@nuxtjs/stylelint-module": "^4.1.0",
"@vue/test-utils": "^1.3.0",
"babel-core": "7.0.0-bridge.0",
"babel-jest": "^27.4.4",
"eslint": "^8.4.1",
"eslint-config-prettier": "^8.3.0",
"eslint-plugin-nuxt": "^3.1.0",
"eslint-plugin-prettier": "^4.0.0",
"eslint-plugin-vue": "^8.2.0",
"husky": "^7.0.4",
"jest": "^27.4.4",
"lint-staged": "^12.1.2",
"postcss-html": "^1.3.0",
"prettier": "^2.5.1",
"stylelint": "^14.1.0",
"stylelint-config-prettier": "^9.0.3",
"stylelint-config-recommended-vue": "^1.1.0",
"stylelint-config-standard": "^24.0.0",
"ts-jest": "^27.1.1",
"vue-jest": "^3.0.4"
}
}
After the installation, nuxt.config.js
has to have some default config for the i18n
default, date-fns
, apollo
, chakra
, and meta tags to begin with:
./nuxt.config.js
import { defaultLocale, locales, i18nLocales } from './constants/i18nLocales'
import { chakraTheme } from './constants/theme'
export default {
// Target: https://go.nuxtjs.dev/config-target
// 'server' | 'static'
target: 'static',
// Global page headers: https://go.nuxtjs.dev/config-head
head() {
const vm = this
const i18nHead = vm.$nuxtI18nHead({ addSeoAttributes: true })
return {
titleTemplate: 'Faqly | %s',
title: 'Help center service CMS',
description: 'A Website with Custom Management System for Help Center',
htmlAttrs: {
// lang: defaultLocale
...i18nHead.htmlAttrs
},
// Metas
meta: [
{ charset: 'utf-8' },
{ name: 'viewport', content: 'width=device-width, initial-scale=1' },
{ hid: 'description', name: 'description', content: '' },
{ name: 'format-detection', content: 'telephone=no' },
...i18nHead.meta
],
// Links
link: [
{
rel: 'icon',
type: 'image/x-icon',
href: '/favicon.ico'
},
{
rel: 'preconnect',
href: 'https://fonts.googleapis.com'
},
{
rel: 'preconnect',
href: 'https://fonts.gstatic.com',
crossorigin: true
},
{
rel: 'stylesheet',
href: 'https://fonts.googleapis.com/css2?family=Playfair+Display:wght@100;200;300;400;500;600;700;800;900&family=Roboto:wght@100;200;300;400;500;600;700;800;900&Work+Sans:wght@100;200;300;400;500;600;700;800;900&Source+Sans+Pro:wght@100;200;300;400;500;600;700;800;900&family=Montserrat:wght@100;200;300;400;500;600;700;800;900&display=swap'
},
...i18nHead.link
]
}
},
// Global CSS: https://go.nuxtjs.dev/config-css
css: [],
// Plugins to run before rendering page: https://go.nuxtjs.dev/config-plugins
plugins: [],
// Auto import components: https://go.nuxtjs.dev/config-components
components: false,
// Modules for dev and build (recommended): https://go.nuxtjs.dev/config-modules
buildModules: [
// https://go.nuxtjs.dev/typescript
'@nuxt/typescript-build',
// https://go.nuxtjs.dev/stylelint
'@nuxtjs/stylelint-module'
],
// Modules: https://go.nuxtjs.dev/config-modules
modules: [
'@nuxtjs/i18n',
'@nuxtjs/apollo',
// https://go.nuxtjs.dev/chakra
'@chakra-ui/nuxt',
// https://go.nuxtjs.dev/emotion
'@nuxtjs/emotion',
// https://github.com/nuxt-community/date-fns-module
[
'@nuxtjs/date-fns',
{
locales: locales
.map((locale) => locale.code)
.map((code) => (code === defaultLocale ? 'en-US' : code))
}
]
],
/**
* Add extend the plugin options under the `chakra` key.
**/
chakra: chakraTheme,
// Modules: https://github.com/nuxt-community/apollo-module
apollo: {
clientConfigs: {
default: {
httpEndpoint: 'http://localhost:1337/graphql'
}
}
},
// i18n module: https://i18n.nuxtjs.org/setup
i18n: {
defaultLocale,
vueI18nLoader: true,
locales,
pages: {
_slug: {
en: '/:slug?',
id: '/:slug?',
ja: '/:slug?'
}
},
vueI18n: i18nLocales
},
// Build Configuration: https://go.nuxtjs.dev/config-build
build: {
watch: ['~/constants/*.js']
}
}
Apollo GraphQL Queries development
From the Strapi CMS GraphQL Playground, we can continue using the queries available in our NuxtJS development. To query the Strapi CMS from the front-end side, I need to develop GraphQL apollo client queries from graphql-tag
that could handle requests that involved default language and data content:
./query/index.js
import gql from 'graphql-tag'
const getDataPages = gql`
query ($locale: I18NLocaleCode) {
pages(locale: $locale) {
data {
attributes {
title
description
slug
image {
data {
attributes {
url
}
}
}
}
}
}
}
`
const getDataFaqCategories = gql`
query ($locale: I18NLocaleCode) {
faqCategories(locale: $locale) {
data {
attributes {
title
slug
description
icon
createdAt
}
}
}
}
`
const getDataFaqs = gql`
query ($locale: I18NLocaleCode) {
faqs(locale: $locale, filters: { show: { eq: true } }) {
data {
attributes {
title
slug
description
createdAt
updatedAt
}
}
meta {
pagination {
total
page
pageSize
pageCount
}
}
}
}
`
const getDataSearch = gql`
query ($locale: I18NLocaleCode, $query: StringFilterInput) {
faqs(
locale: $locale
filters: {
or: [{ title: $query }, { description: $query }]
show: { eq: true }
}
) {
data {
attributes {
title
slug
description
createdAt
updatedAt
}
}
}
}
`
export { getDataPages, getDataFaqCategories, getDataFaqs, getDataSearch }
These GraphQL queries will be fetched from NuxtJS graphql-tag
from the Apollo GraphQL modules called on each of the pages that will be fetching the required data. The language variable default in GraphQL will be in $locale
and in the NuxtJS will be in the i18n
global variable in @nuxtjs/i18n
module.
After that, I am starting to develop the pages, components, GraphQL queries, and other assets to complete the project. Most pages in Nuxt.js have a GraphQL query to request content with the default i18n
language, below is the sample:
./pages/index.vue
<script lang="js">
import Vue from 'vue'
import Search from "@/components/global/Search"
/* Gql queries */
import { getDataPages, getDataFaqCategories, getDataFaqs} from '~/query'
export default Vue.extend({
name: 'IndexPage',
components: {
Search,
},
data() {
return {
pages: {},
sortMode:'',
showModal: false,
isExpanded: false,
}
},
methods: {
searchEvent(v) {
this.$router.push(
this.localePath(
{ path: 'search', query: { search: v } })
)
}
},
// Apollo queries server side
apollo: {
pages: {
query: getDataPages,
variables() {
return { locale: this.$i18n.locale }
},
fetchPolicy: 'no-cache'
},
faqs: {
query: getDataFaqs,
variables() {
return { locale: this.$i18n.locale }
},
fetchPolicy: 'no-cache'
},
faqCategories: {
query: getDataFaqCategories,
variables() {
return { locale: this.$i18n.locale }
},
fetchPolicy: 'no-cache'
}
}
})
</script>
Directory Structure
.
├── LICENSE
├── README.md
├── commitlint.config.js
├── components
│ ├── Cards.vue
│ ├── ChakraUiLogo.vue
│ ├── Languages.vue
│ ├── Logo.vue
│ ├── LogoFaqly.vue
│ ├── LogoFaqlySmall.vue
│ ├── NuxtLogo.vue
│ ├── Pages.vue
│ ├── StrapiLogo.vue
│ ├── Tutorial.vue
│ └── global
│ ├── Footer.vue
│ ├── Header.vue
│ └── Search.vue
├── constants
│ ├── i18nLocales.js
│ └── theme.js
├── jest.config.js
├── jsconfig.json
├── layouts
│ ├── default.vue
│ └── error.vue
├── nuxt.config.js
├── package.json
├── pages
│ ├── _slug.vue
│ ├── index.vue
│ └── search.vue
├── plugins
│ ├── apollo-config.js
│ ├── apollo-error-handler.js
│ └── apollo-watch-loading-handler.js
├── query
│ └── index.ts
├── store
│ ├── README.md
│ └── index.js
├── stylelint.config.js
├── test
│ └── NuxtLogo.spec.js
├── tsconfig.json
└── types
├── gql.d.ts
└── vue-shim.d.ts
10 directories, 38 files
Conclusion
If you are working on JavaScript full-stack web development, Strapi is one of the go-to tools that I recommend for your project. Whether you are handling small, medium, or large-scale projects, Strapi will suit your needs. You can use React.js or Vue.js for your front-end, and Strapi CMS will take care of the rest, including content management system API endpoints and other essential functionalities required to complete your project.
Strapi CMS is a great choice for any scaled web application. It has a very nice developer experience to focus more on the content types, internationalization (I18N) front-end development, and communication between the data on the client-side. RestFul or GraphQL API endpoint becomes a clearly easy choice if you want to give your Web Application running on multiple devices.
From this project, I learned that multilingual internationalization web application can be done in Strapi CMS Multilingual with NuxtJS Multilingual Website. With this kind of architecture, you could split the back-end system with the front-end in a different server or host. Hopefully that this project too will be your reference for your next project that involved multilingual web applications.