I'm using create-react-app for my React application and I'm currently trying to figure out translations.
Right now I have one big JSON file per locale and each locale file includes translations for all components - which is not ideal and I would like to have separate translation files for each component.
So the structure of locales will look like this: ComponentA - locales - en.json - sk.json
ComponentB - locales - en.json - sk.json
What is the best way to achieve this? Which modules should I use to achieve this?
I do not want to include these locale files into build because we will support 10 languages so the bundle size will be too big.
I'm going to answer my own question. After research I figured out that my use-case is rare and I decided to do some locale pre-processing to achieve desired functionality.
I'm running default config of create-react-app
together with react-i18next
and my file structure looks like this:
src
- Component A
- locales
- en.json
- sk.json
- Component B
- locales
- en.json
Here are settings for the react-i18next
module:
i18n
.use(Backend)
.use('en')
.use(reactI18nextModule)
.init({
fallbackLng: 'en',
debug: false,
backend: {
loadPath: function() {
return `${config.BASE_URL}locales/{{lng}}.json`;
}
},
react: {
wait: true
}
});
Since I'm using default config of create-react-app
then the locales are served from public
folder in the app and build itself.
And now it gets tricky. Since the locale files are located in the component tree (and not in public folder) then we need to do some pre-processing of locales - basically we need to concat all JSON files and move them to the public folder.
Files for public folder are created on the fly using node task (prepare-locales.js
) below:
const jsonConcat = require('json-concat');
const minifyJson = require('minify-json');
const glob = require('glob');
const availableLocales = ['en', 'sk'];
const path = require('path');
module.exports.development = function() {
availableLocales.forEach(locale => {
glob(`./src/**/locales/${locale}.json`, {}, (err, globFiles) => {
if (globFiles.length) {
const file = `./public/locales/${locale}.json`;
jsonConcat(
{
src: globFiles,
dest: file
},
json => {
minifyJson(file);
}
);
}
});
});
};
module.exports.production = function() {
glob('./build/static/js/main.*.js', {}, (err, globFiles) => {
const files = require('source-map-explorer')(globFiles[0]).files;
let localeFolders = [];
Object.keys(files).forEach(fileName => {
localeFolders.push(path.dirname(fileName).replace('./', ''));
});
localeFolders = [...new Set(localeFolders)];
availableLocales.forEach(locale => {
const localeFiles = localeFolders.map(
lf => `./src/${lf}/locales/${locale}.json`
);
if (localeFiles.length) {
const file = `./build/locales/${locale}.json`;
jsonConcat(
{
src: localeFiles,
dest: file
},
json => {
minifyJson(file);
}
);
}
});
});
};
For the development version I'm concating all JSON files which are found in the tree.
For the production version I'm using source-map-explorer
and I'm concating only those locales which are used in the build.
Here is my simplified package.json
with tasks required for this to work:
{
"devDependencies": {
"json-concat": "0.0.1",
"minify-json": "^1.0.0",
"npm-watch": "^0.4.0",
"path": "^0.12.7",
"react-scripts": "2.1.1",
"source-map-explorer": "^1.6.0"
},
"scripts": {
"start": "react-scripts start & npm run prepare-locales:development:watch",
"build:production": "react-scripts build && npm run prepare-locales:production",
"prepare-locales:development:watch": "npm-watch prepare-locales:development",
"prepare-locales:development": "node -e 'require(\"./prepare-locales.js\").development()'",
"prepare-locales:production": "node -e 'require(\"./prepare-locales.js\").production()'"
},
"watch": {
"prepare-locales:development": {
"patterns": "src/**/locales/",
"extensions": "json"
}
}
}
Maybe it's not the best solution but it works for my use-case and hopefully it will help someone with similar issues.
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With