Strapi CMS Multilingual with Apollo GraphQL, Nuxt.js and Chakra UI.

Building Multilingual Strapi CMS - Harnessing Apollo GraphQL and Nuxt.js for Seamless Integration

June 01, 2022 Dykraf

Build a searchable multilingual help center website on top of Strapi CMS, Nuxt.js, Chakra UI, and Apollo GraphQL endpoint.

Web Story VersionWeb Story

Prerequisite

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 English language

Strapi Front-End on default Indonesian language Strapi Front-End on default Indonesian language

Strapi Front-End on default Japanese 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 Login administration

Strapi CMS multilingual internationalization set up list Strapi CMS multilingual internationalization set up list

Strapi CMS multilingual internationalization add languages Strapi CMS multilingual internationalization add languages

Strapi CMS multilingual internationalization after add language list 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 image

Strapi CMS GraphQL Playground Schemas and Documentation Strapi CMS GraphQL Playground Schemas and Documentation image

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.

Topics

Recent Blog List Content:

Archive