i18n load translations from s3bucket

2.8k Views Asked by At

I am using the i18n module in my react app which is hosted as an app (which I will refer to as live app) on S3 and has cloudfront sitting in front of it.

I want to store the s3 url in a config file as to avoid having it hardcoded in my app so that I can work against translation files stored locally in the public/locales folder when I'm developing.

Initially I had my backend options set-up so that it would always try and look up the files in the locales path. And this worked locally but stopped working on S3 although the locales folder was present in the S3 bucket. I also noticed that no request was being sent from the app to retrieve the translation files.

backend: {
    loadPath: 'locales'
  }

I then decided to upload the translation files to another S3 bucket and host them there to see if this would fix the issue. I changed my config to hardcode the s3 bucket path. This worked both locally and on the live app. But this means that I can't use my config file to determine the loadPath option.

 backend: {
    loadPath: '<myhardcoded-s3-bucket-url>/{{lng}}/translation.json',
    crossDomain: true
  }

I then thought I could construct the url in place as so:

/*global AWS_CONFIG */
/*eslint no-undef: "error"*/
...
...
...
backend: {
    loadPath: `${AWS_CONFIG.aws_app_translations_path}/{{lng}}/translation.json`,
    crossDomain: true
  }

Strangely again this worked locally when AWS_CONFIG.aws_app_translations_path was both http://localhost:3000/locales and <myhardcoded-s3-bucket-url>. However once I pushed it live, it failed again. This time making a request to https://<my-apps-base-path>/undefined/en-GB/translation.json for example. So it's trying to use the app path and append what I have defined in loadPath.

I then saw that I could have loadPath as a function to construct my url. This didn't work for the live app either, but again worked locally.

backend: {
    loadPath: function (lng) {
      return `${AWS_CONFIG.aws_app_translations_path}/${lng}/translation.json`
    },
    crossDomain: true
  }

This is my entire i18n file

/*global AWS_CONFIG */
/*eslint no-undef: "error"*/
import i18n from "i18next";
import LanguageDetector from "i18next-browser-languagedetector";
import Backend from "i18next-http-backend";
import { initReactI18next } from "react-i18next";


const options = {
  order: ['navigator', 'localStorage'],
  caches: ['localStorage'],
  fallbackLng: "en-GB",
  debug: true,
  interpolation: {
    escapeValue: false // not needed for react as it escapes by default
  },
  backend: {
    loadPath: function (lng) {
      return `${AWS_CONFIG.aws_app_translations_path}/${lng}/translation.json`
    },
    crossDomain: true
  }
}

i18n
  .use(Backend)
  .use(LanguageDetector)
  .use(initReactI18next)
  .init(options);

export default i18n;

What could explain this odd behaviour? Am I missing configuration here?

2

There are 2 best solutions below

5
On

Based on your responses in the comments, looks like you are missing the translation part, and it pre-appending undefined as a namespace in the url.

Try this:

// i18n

const options = {
  order: ['navigator', 'localStorage'],
  caches: ['localStorage'],
  fallbackLng: "en-GB",
  debug: true,
  interpolation: {
    escapeValue: false // not needed for react as it escapes by default
  },
  backend: {
    loadPath: function () {
      return `${AWS_CONFIG.aws_app_translations_path}/{{lng}}/{{ns}}.json`
    },
    crossDomain: true
  }
}

Based on the loadPath config doc:

path where resources get loaded from, or a function returning a path: function(lngs, namespaces) { return customPath; }

The returned path will interpolate lng, ns if provided like giving a static path

0
On

Once I changed the way I was importing the config file it worked. I am guessing there is a loading issue on live, but having changed my i18n file from

/*global AWS_CONFIG */
/*eslint no-undef: "error"*/
import i18n from "i18next";
import LanguageDetector from "i18next-browser-languagedetector";
import Backend from "i18next-http-backend";
import { initReactI18next } from "react-i18next";


const options = {
  order: ['navigator', 'localStorage'],
  caches: ['localStorage'],
  fallbackLng: "en-GB",
  debug: true,
  interpolation: {
    escapeValue: false // not needed for react as it escapes by default
  },
  backend: {
    loadPath: function (lng) {
      return `${AWS_CONFIG.aws_app_translations_path}/${lng}/translation.json`
    },
    crossDomain: true
  }
}

i18n
  .use(Backend)
  .use(LanguageDetector)
  .use(initReactI18next)
  .init(options);

export default i18n;

to

import i18n from "i18next";
import LanguageDetector from "i18next-browser-languagedetector";
import Backend from "i18next-http-backend";
import { initReactI18next } from "react-i18next";
import {config} from './config';

const {aws_app_translations_path} = config;


const options = {
  order: ['navigator', 'localStorage'],
  caches: ['localStorage'],
  fallbackLng: "en-GB",
  debug: true,
  defaultNS: 'translation',
  load: 'currentOnly',
  interpolation: {
    escapeValue: false // not needed for react as it escapes by default
  },
  backend: {
    loadPath: (lng, ns) => {
      return `${aws_app_translations_path}/${lng}/${ns}.json`;
    },
    crossDomain: true
  }
}

i18n
  .use(Backend)
  .use(LanguageDetector)
  .use(initReactI18next)
  .init(options);

export default i18n;

Fixed it. Initially AWS_CONFIG was being returned as undefined despite working for the rest of the entire app. Changing the config file to live in the root directory of the project and the way it was imported solved the issue.