Skip to content

Commit

Permalink
(#29) Implementing i18n-js2properties CLI for migrate translations
Browse files Browse the repository at this point in the history
  • Loading branch information
ddivin-sc committed Nov 28, 2019
1 parent 4df025e commit f1f9bab
Show file tree
Hide file tree
Showing 5 changed files with 237 additions and 2 deletions.
24 changes: 24 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -245,6 +245,30 @@ stnc.valueToString(null) // returns ''
converter.stringToValue('') // returns null
```

## Migrate i18n-js to properties CLI

You can use i18n-js2properties CLI in your build process when package Grails plugins.
The properties need to provide property keys and values for text localization service editor.

for example:
```
i18n-js2properties --source src/client/components/i18n --target ../plugin/grails-app/i18n/boilerplateEditor
```

If you want following translations in JavaScript frontend style
```
src/client/components/i18n/index.js
src/client/components/i18n/en.js
src/client/components/i18n/de.js
```

After that CLI will be generate following properties files
```
../plugin/grails-app/i18n/boilerplateEditor.properties
../plugin/grails-app/i18n/boilerplateEditor_en.properties
../plugin/grails-app/i18n/boilerplateEditor_de.properties
```

## Contributors

| <img src="https://avatars.githubusercontent.com/u/24733803?v=3" width="100px;"/> | [**Dmitry Divin**](https://github.com/ddivin-sc) |
Expand Down
85 changes: 85 additions & 0 deletions bin/i18n-js2properties.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,85 @@
#!/usr/bin/env node
//for the source definitions in style ES6
require("babel-register")({
presets: ["es2015", "stage-0"],
plugins: ["istanbul"]
});
const properties = require("properties");
const fs = require("fs");
const path = require("path");
const argv = require('yargs')
.usage("Usage: i18n-js2properties --source src/client/components/i18n --target ../plugin/grails-app/i18n/boilerplateEditor")
.demandOption(["source", "target"])
.argv;

const {source, target} = argv;
const sourceFilePath = path.resolve(process.cwd(), source);
console.log(`Reading source import [${sourceFilePath}]`);
let sourceObject = require(sourceFilePath);
//in case if export module with es6
if (sourceObject.default) {
sourceObject = sourceObject.default;
}

const defaultLanguage = "en";

if (!sourceObject[defaultLanguage]) {
console.error(`Default language [${defaultLanguage}] should be defined in translations [${source}]`);
} else {
const targetPath = path.resolve(process.cwd(), path.dirname(target));

if (!fs.existsSync(targetPath)) {
console.error(`Target path [${targetPath}] doesn't exists`);
} else {
const bundleName = path.basename(target);

const languages = Object.keys(sourceObject);

function flattenTranslationTexts(object, parentKey) {
let result = {};
const keys = Object.keys(object);

for (let i = 0, l = keys.length; i < l; i++) {
const key = keys[i];
const propertiesKey = parentKey ? `${parentKey}.${key}` : key;
const value = object[key];

if (typeof value === "object") {
result = {
...result,
...flattenTranslationTexts(value, propertiesKey)
};
} else {
result[propertiesKey] = value.toString();
}
}

return result;
}

for (let i = 0; i < languages.length; i++) {
const language = languages[i];
const objectTranslationTexts = sourceObject[language];
const translationTexts = flattenTranslationTexts(objectTranslationTexts);
const propertiesText = properties.stringify(translationTexts, {
// unicode: true,
});
const filePath = path.join(targetPath, `${bundleName}_${language}.properties`);

console.log(`Writing target file [${filePath}]`);
fs.writeFileSync(filePath, propertiesText);
}

const fromFilePath = path.join(targetPath, `${bundleName}_en.properties`);
const toFilePath = path.join(targetPath, `${bundleName}.properties`);
console.log(`Copy file [${fromFilePath}] to [${toFilePath}]`);
//save english properties as default
fs.createReadStream(fromFilePath).pipe(
fs.createWriteStream(toFilePath)
);
}
}




2 changes: 2 additions & 0 deletions config/mocha-setup.js
Original file line number Diff line number Diff line change
@@ -1,6 +1,8 @@
// set node evn
process.env.NODE_ENV = 'test';

//polyfill async/await calls
require("babel-polyfill");
// register babel presets
require('babel-register')({
presets: ['es2015', 'stage-0'],
Expand Down
11 changes: 9 additions & 2 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,8 @@
"description": "OpusCapita simple i18n mechanism",
"main": "lib/index.js",
"files": [
"lib"
"lib",
"bin"
],
"scripts": {
"lint": "eslint src",
Expand All @@ -15,6 +16,9 @@
"npm-publish": "npm run npm-build && npm publish",
"publish-release": "npm run npm-publish"
},
"bin": {
"i18n-js2properties": "bin/i18n-js2properties.js"
},
"homepage": "https://github.com/OpusCapita/i18n.git",
"contributors": [
"Dmitry Divin <dmirty.divin@jcatalog.com>",
Expand All @@ -29,7 +33,9 @@
"license": "Apache-2.0",
"dependencies": {
"flat": "2.0.1",
"lodash": "4.17.11"
"lodash": "4.17.11",
"properties": "1.2.1",
"yargs": "15.0.2"
},
"devDependencies": {
"@opuscapita/npm-scripts": "2.0.0-beta.2",
Expand All @@ -49,6 +55,7 @@
"eslint-plugin-react": "7.1.0",
"mocha": "3.4.2",
"mocha-junit-reporter": "1.13.0",
"mock-fs": "4.10.4",
"nyc": "11.0.3",
"rimraf": "2.6.1"
}
Expand Down
117 changes: 117 additions & 0 deletions src/cli/i18n-js2properties.spec.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,117 @@
const fs = require("fs");
const rimraf = require("rimraf");
const { expect } = require("chai");
const { spawn } = require("child_process");
const concat = require("concat-stream");
const properties = require("properties");

function createProcess(processPath, args = [], env = {}) {
return spawn("node", [processPath].concat(args), {
env: {
...env,
...{ NODE_ENV: "test" }
}
});
}

function execute(processPath, args = [], opts = {}) {
const { env = null } = opts;
const childProcess = createProcess(processPath, args, env);
childProcess.stdin.setEncoding("utf-8");
return new Promise((resolve, reject) => {
childProcess.stderr.once("data", err => {
reject(err.toString());
});
childProcess.on('error', reject);
childProcess.stdout.pipe(
concat(result => {
resolve(result.toString());
})
);
});
}


describe("i18n-js to properties CLI", () => {
beforeEach(() => {
rimraf.sync("tmp");
fs.mkdirSync("tmp");
fs.mkdirSync("tmp/i18n");
fs.mkdirSync("tmp/grails-app");
fs.mkdirSync("tmp/grails-app/i18n");
fs.writeFileSync("tmp/i18n/index.js", `
import en from './en';
import de from './de';
export default {
en,
de
};
`);
fs.writeFileSync("tmp/i18n/en.js", `
const header = {
title: "test title"
};
const form = {
"a.b": "test a b",
"a.b.c": "test a b c"
};
export default { messages: {header, form} }
`);
fs.writeFileSync("tmp/i18n/de.js", `
const header = {
title: "de test title"
};
const form = {
"a.b": "de test a b",
"a.b.c": "de test a b c"
};
export default { messages: {header, form} }
`);
});

afterEach(() => {
rimraf.sync("tmp");
});

it("should generate properties", async () => {
await execute("bin/i18n-js2properties.js", ["--source", "tmp/i18n", "--target", "tmp/grails-app/i18n/messages"]);

expect(
fs.existsSync("tmp/grails-app/i18n/messages.properties")
).to.be.equal(true);

expect(
fs.existsSync("tmp/grails-app/i18n/messages_en.properties")
).to.be.equal(true);

expect(
fs.existsSync("tmp/grails-app/i18n/messages_de.properties")
).to.be.equal(true);

const englishProperties = properties.parse(
fs.readFileSync("tmp/grails-app/i18n/messages_en.properties").toString()
);

expect(englishProperties).to.deep.include(
{ "messages.header.title": "test title", "messages.form.a.b": "test a b", "messages.form.a.b.c": "test a b c" }
);

const germanProperties = properties.parse(fs.readFileSync("tmp/grails-app/i18n/messages_de.properties").toString());

expect(germanProperties).to.deep.include(
{
"messages.header.title": "de test title",
"messages.form.a.b": "de test a b",
"messages.form.a.b.c": "de test a b c"
}
);

const defaultProperties = properties.parse(fs.readFileSync("tmp/grails-app/i18n/messages.properties").toString());
expect(defaultProperties).to.deep.include(englishProperties);
});
});

0 comments on commit f1f9bab

Please sign in to comment.