i18n in STRAPI
Published: 2022-03-14 07:35:08

As per [wikipedia](https://en.wikipedia.org/wiki/Internationalization_and_localization)

> `Internationalisation` and `Localisation`, often abbreviated `i18n` and `L10n`, is the process of designing a software application so that it can be adapted to various languages and regions without engineering changes.

Let's take an example use-case; so that we both are on the same page. 
- We have one collection named `home-page` having the following fields. ( My use-case was to provide support for 4-5 languages, may require more in future).

![collection](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/qwhslcqa3yxv9ourns4k.png)

where banner(single component) and brand(repeatable component) are components. 
Fields description:
- ***title***
    - `home-page` having three fields `title`, `title_ar`, `title_ru`, values of title `English(en)`, `Arabic(ar)` and `Russian(ru)` language repectively.
- ***Banner and Brand***
    - `title` in banner and `name` in the brand are language-dependent and the rest of the fields are the same for all languages.
    - And, when selected languageCode is `ar`, my frontend will need `title` = `titile_ar` in response for banner and `name` = `name_ar` in response for brand.

#### My Approach ( didn't use any plugin ):
- Made a file specifying the language dependent fields.
    > This is generated automatically (based on the `collection`) when server starts.
    projec_dir/constants/i18n.config.json
    ```json
    {
      "home-page": [
        "title"
      ],
      "component.banner-component": [
        "title"
      ],
      "component.brand-component": [
        "name"
      ]
    }
    ```
    
- get data from the model using find, findOne or any other customised service.
- fetch schema of the model (required for the name of the attribute to get language dependent fields from the JSON imported from project-dir/constants/i18n.config.json)
- replace `${field}` with `${field}_${languageCode}`. 
- delete `${field}_${languageCode}`.

#### Code:
 - project_dir/constants/i18n.constants.js
     ```javascript
    const i18nFields = require('./i18n.config.json');
    const DEFAULT_LANGUAGE = 'en';
    const LANGUAGE_LIST = ['en', 'ar', 'ka', 'ru', 'uzb'];
    const I_18_FIELDS = i18nFields;
    const I18N_LIST = ['home-page'];
    
    module.exports = {
      DEFAULT_LANGUAGE,
      LANGUAGE_LIST,
      I_18_FIELDS,
      I18N_LIST,
    };
    
     ```
 - project_dir/utils/i18n.utils.js
     ```javascript
     const _ = require('lodash');
    const {
      LANGUAGE_LIST,
      DEFAULT_LANGUAGE,
    } = require('../constants/i18n.constants');
    
    function internationalizeFields(components, langCode, fields) {
      if (!components) {
        return undefined;
      }
      const i18Component = { ...components };
      if (fields) {
        fields.forEach((field) => {
          const currentLanguageFieldValue = _.get(
            i18Component,
            `${field}_${langCode}`
          );
          if (langCode !== DEFAULT_LANGUAGE) {
            _.set(i18Component, field, currentLanguageFieldValue);
          }
          LANGUAGE_LIST.forEach((lang) =>
            _.unset(i18Component, `${field}_${lang}`)
          );
        });
      }
      return i18Component;
    }
    module.exports = {
      internationalizeFields,
    };
     ```
- project_dir/utils/strapi.util.js
    ```javascript
    /* global strapi */
    
    function getAttributes({ modelName }) {
      try {
        return strapi.api[modelName].models[modelName].__schema__.attributes;
      } catch (err) {
        //throw error
      }
    }
    function addComponentSchema({ parentSchema }) {
      const result = Object.keys(parentSchema).reduce((acc, curr) => {
        acc[curr] = parentSchema[curr];
        if (parentSchema[curr].type !== 'component') {
          return acc;
        }
        const component = strapi.components[acc[curr].component];
        if (!component) {
          throw error
        }
        acc[curr].schema = component.attributes;
        acc[curr].schema = addComponentSchema({ parentSchema: acc[curr].schema });
        return acc;
      }, {});
      if (!result || Object.keys(result).length === 0) {
        //through error
      }
      return result;
    }
    
    module.exports = {
      getAttributes,
      addComponentSchema,
    };

    ```
- project_dir/utils/controller.util.js
    ```javascript

    const { DEFAULT_LANGUAGE } = require('../common/constants/i18n.constants');
    const { I_18_FIELDS } = require('../common/constants/i18n.constants');
    const { internationalizeFields } = require('../common/utils/i18n.utils');
    const { getAttributes, addComponentSchema } = require('./strapi.utils');
    
    /**
     * @param {*} modelName modelName for keys in I_18_FIELDS
     * @param {*} data data to be internationlised
     * @param {*} schema schema corresponding to modelName
     * @param {*} lang
     * @returns internationalised data
     */
    async function internationalizeData({
      modelName,
      data,
      schema,
      lang = DEFAULT_LANGUAGE,
    }) {
      if (!data) {
        return data;
      }
      if (!schema) {
        const modelSchema = getAttributes({ modelName });
        schema = addComponentSchema({ parentSchema: modelSchema });
      }
      const schemaKeys = Object.keys(schema);
      if (!schemaKeys.length) {
        return data;
      }
      if (Array.isArray(data)) {
        data.forEach(async (entry, index) => {
          data[index] = await internationalizeData({
            modelName,
            data: entry,
            schema,
            lang,
          });
        });
        return data;
      } else {
        data = internationalizeFields(data, lang, I_18_FIELDS[modelName], null);
      }
      schemaKeys.forEach(async (curr) => {
        if (
          schema[curr] &&
          (schema[curr].type === 'component' || schema[curr].collection)
        ) {
          if ((schema[curr].repeatable || schema[curr].collection) && data[curr]) {
            data[curr].forEach(async (entry, index) => {
              data[curr][index] = await internationalizeData({
                modelName: schema[curr].component || schema[curr].collection,
                data: entry,
                schema: schema[curr].schema,
                lang,
              });
            });
          } else if (modelName.indexOf('.') === -1) {
            data[curr] = await internationalizeData({
              modelName: schema[curr].component,
              data: data[curr],
              schema: schema[curr].schema,
              lang,
            });
          }
        }
      });
      return data;
    }
    module.exports = {
      internationalizeData,
    };
    ```
    
- project_dir/api/homepage/controller.js
    ```javascript
    /* global strapi */
    
    const { sanitizeEntity } = require('strapi-utils');
    const { internationalizeData } = require('../../../utils/controller.util');

    async function findOne(ctx) {
      const { id } = ctx.params;
      const { lang } = ctx.request.header;
      const entity = await strapi.services['home-page'].find({ id });
      const sanitizedEntity = sanitizeEntity(entity, {
        model: strapi.models['home-page'],
      });
      const data = internationalizeData({
        modelName: 'home-page',
        data: sanitizedEntity,
        schema: null,
        lang,
      });
      return data;
    }
    module.exports = {
      findOne,
    };

    ```
    Similarly, you can do for other apis.

Let's discuss your approach in the discussion box or you can hit me up at aastha.talwaria29@gmail.com.

Thanks for reading.