i18next/i18next-scanner

Updating default values in code from json values

ekeuus opened this issue · 4 comments

Is it possible to update default values in code from json values since we update keys in an external platform and not in the code itself?

I was able to implement a solution that can keep code default translations in sync with json file values, that in our case comes from a translation platform.

So if someone is interested in also doing it, let me know. I might implement a public package that can do that.

@ekeuus were you able to do so?

@ekeuus were you able to do so?

Yep, I didn't make a package due to lazyness, but I have something that works for most cases (one still has to go through the files if you have strings with newlines). I made a "reverse" scanner that works as follows:

  1. Scan the current strings from the files that you normally scan from
  2. Get your strings from the platform in the same format
  3. Run script from package.json to which just goes through files and replaces the strings

Probably the script can be made nicer, but I just threw it together quickly when I needed it. :)

package json

"scripts": {
    "translation-update-from-json": "i18next-scanner --config i18next-reverse-scanner.config.js && node --experimental-json-modules ./defaultValueUpdaterFromJson.mjs",
  },

i18next-reverse-scanner.config.js

const originalConfig = require('./i18next-scanner.config');

const originalConfigCopy = Object.assign(
	{},
	originalConfig,
);

delete originalConfigCopy.options.resource.loadPath;
originalConfigCopy.options.resource.savePath = 'src/config/translations/.{{lng}}_reverse.json';

module.exports = Object.assign(
	{},
	originalConfigCopy,
);

defaultValueUpdaterFromJson.mjs

import fs from 'fs';
import glob from 'glob';

import en from './en.json';
import code_en from './.en_reverse.json';

const separator = '::';

function flatten(data) {
	const result = {};
	function recurse(cur, prop) {
		if (Object(cur) !== cur) {
			result[prop] = cur;
		} else {
			let isEmpty = true;
			for (const p in cur) {
				isEmpty = false;
				recurse(cur[p], prop ? prop+separator+p : p);
			}
			if (isEmpty && prop) result[prop] = {};
		}
	}
	recurse(data, '');
	return result;
}

function replace(string, oldString, newString) {
	return string.split(oldString).join(newString);
}

function main() {
	const flatObject = flatten(en);
	const flatCodeObject = flatten(code_en);
	const keysLength = Object.keys(flatCodeObject).length;

	const rootDir = path.join(path.resolve(), 'src');

	const getDirectories = function getDirectories(src, callback) {
		glob(`${src}/**/*.{js,jsx,ts,tsx}`, callback);
	};

	getDirectories(rootDir, (err, files) => {
		if (err) {
			throw err;
		}
		const filesToCheck = files;
		// const filesToCheck = [files[1]];
		filesToCheck.forEach((file) => {
			const buffer = fs.readFileSync(file);
			console.info('file start:', file);
			let content = buffer.toString();
			Object.entries(flatCodeObject).forEach(([key, oldValue], index) => {
				if (!(index % 200)) {
					console.info('file progress:', index, 'of', keysLength);
				}
				const oldString = `'${key}', '${oldValue}'`;
				// there might be multiple instances
				if (content.includes(oldString)) {
					const newValue = flatObject[key];
					const newString = `'${key}', '${newValue}'`;
					content = replace(content, oldString, newString);
				}
			});
			console.info('file end:', file);
			fs.writeFileSync(file, content);
		});
	});
}

main();