Skip to content

Commit

Permalink
feat(i18n): enable translation of plain strings containing components (
Browse files Browse the repository at this point in the history
  • Loading branch information
machour committed Apr 18, 2018
1 parent 8f8f229 commit e59fd6e
Show file tree
Hide file tree
Showing 26 changed files with 1,294 additions and 433 deletions.
13 changes: 7 additions & 6 deletions __tests__/tests/others/locales.js
Original file line number Diff line number Diff line change
@@ -1,17 +1,18 @@
import { dumpKeysRecursively } from 'recursive-keys';
import { common } from 'config';
import keysDiff from 'keys-diff';
import * as languages from 'locale/languages';
import languages from 'locale/languages';

const baseLanguage = common.defaultLocale;

describe('Locales', () => {
const baseKeys = dumpKeysRecursively(languages[baseLanguage]).sort();

Object.keys(languages).forEach(key => {
if (key === baseLanguage) return;
it(`${key} should contain same keys as ${baseLanguage}`, () => {
expect(keysDiff(languages[baseLanguage], languages[key])).toEqual([
[],
[],
]);
expect(dumpKeysRecursively(languages[key]).sort()).toEqual(
expect.arrayContaining(baseKeys)
);
});
});
});
35 changes: 34 additions & 1 deletion package.json
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,7 @@
"link": "react-native link",
"ios": "react-native run-ios",
"android": "react-native run-android",
"i18n:extract": "node ./scripts/extract-i18n.js && prettier --loglevel silent --write src/locale/**/*.js",
"commitmsg": "minicat $GIT_PARAMS | commitlint",
"precommit": "lint-staged",
"eslint": "eslint .",
Expand Down Expand Up @@ -110,6 +111,8 @@
"babel-plugin-transform-inline-environment-variables": "^6.8.0",
"babel-preset-flow": "^6.23.0",
"babel-preset-react-native": "1.9.1",
"babylon": "^6.18.0",
"chalk": "^2.3.2",
"concurrently": "^3.5.0",
"coveralls": "^3.0.0",
"cz-conventional-changelog": "^2.0.0",
Expand All @@ -126,9 +129,9 @@
"eslint-plugin-react": "^7.1.0",
"eslint-plugin-react-native": "^2.3.2",
"flow-bin": "0.65.0",
"glob": "^7.1.2",
"husky": "^0.14.3",
"jest": "^22.4.3",
"keys-diff": "^1.0.5",
"lint-staged": "^3.2.6",
"minicat": "^1.0.0",
"opencollective": "^1.0.3",
Expand All @@ -139,6 +142,7 @@
"react-test-renderer": "16.3.0-alpha.1",
"reactotron-react-native": "^1.14.0",
"reactotron-redux": "^1.13.0",
"recursive-keys": "^0.9.0",
"stylelint": "^8.2.0",
"stylelint-config-standard": "^18.0.0",
"stylelint-config-styled-components": "^0.1.1",
Expand Down Expand Up @@ -177,5 +181,34 @@
"repository": {
"type": "git",
"url": "https://github.com/gitpoint/git-point"
},
"i18n": {
"sourceLanguage": "en",
"sourcePath": [
"./src/"
],
"ignorePath": [
"./node_modules"
],
"languages": [
"de",
"en",
"eo",
"es",
"eu",
"fr",
"gl",
"nl",
"pl",
"pt",
"ptBr",
"ru",
"tr",
"uk",
"zhCn",
"zhTw"
],
"messagePath": "src/locale/languages",
"translator": "t"
}
}
158 changes: 158 additions & 0 deletions scripts/extract-i18n.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,158 @@
/* eslint-disable import/no-extraneous-dependencies */
/* eslint-disable import/no-dynamic-require */
/* eslint-disable no-console */
/* eslint-disable no-continue */

const chalk = require('chalk');
const glob = require('glob');
const fs = require('fs');
const babylon = require('babylon');

const i18nConfig = require('./../package.json').i18n;

const report = message => {
console.log(message);
};

const extractFromSource = config => {
const messages = {};

config.sourcePath.forEach(source => {
const files = glob.sync(`${source}/**/*.js`, { debug: false });

files.forEach(file => {
for (let i = 0; i < config.ignorePath.length; i++) {
if (file.indexOf(config.ignorePath[i]) !== -1) {
return;
}
}

const contents = fs.readFileSync(file).toString();
const tokens = babylon.parse(contents, {
sourceType: 'module',
plugins: ['jsx', 'classProperties', 'objectRestSpread', 'flow'],
}).tokens;
let counter = 0;

for (let i = 0; i < tokens.length; i++) {
const token = tokens[i];

if (token.type.label === 'name' && token.value === config.translator) {
const parenthesis = tokens[i + 1];
const message = tokens[i + 2];

if (!parenthesis || parenthesis.type.label !== '(') {
continue;
}

if (!message || message.type.label !== 'string') {
continue;
}

if (messages[message.value] === '') {
continue;
}

counter += 1;
messages[message.value] = '';
}
}

report(
` - parsing ${chalk.bold(file)} .. ${chalk.bold(
counter
)} strings found!`
);
});
});

return messages;
};

const generateFiles = (config, messages) => {
report('Generating translations:');

let exports = '/* eslint-disable quote-props */\nexport default {\n';

config.languages.forEach(language => {
const filepath = `${process.cwd()}/${config.messagePath}/${language}.js`;

let oldMessages = {};

try {
oldMessages = require(filepath);
} catch (e) {
console.log(e);
}

const action =
oldMessages && oldMessages.length === 0 ? 'generated' : 'merged';

// Get a fresh copy of found strings
const newMessages = { ...messages };

if (language === config.sourceLanguage) {
Object.keys(newMessages).forEach(key => {
newMessages[key] = key;
});
}

// Iterate on old strings
Object.keys(oldMessages).forEach(key => {
if (typeof newMessages[key] !== 'undefined') {
// Translation needed & found: copy it
if (oldMessages[key] !== '') {
newMessages[key] = oldMessages[key];
}
} else if (typeof oldMessages[key] === 'object') {
// old syntax, copy blindly
newMessages[key] = oldMessages[key];
} else {
// Translation old & gone: surround it with @
newMessages[key] = `@${oldMessages[key].replace(/^@+|@+$/g, '')}@`;
}
});

// Write the new message file
fs.writeFile(
filepath,
`module.exports = ${JSON.stringify(newMessages, null, 2)};`,
'utf8',
err => {
if (err) throw err;
}
);
report(` - ${action} ${chalk.bold(filepath)}`);
exports += ` '${language}': require('./${language}'),\n`;
});

exports += '};\n';

fs.writeFile(
`${process.cwd()}/${config.messagePath}/index.js`,
exports,
'utf8',
err => {
if (err) throw err;
}
);
};

const extractMessages = config => {
report(
`Extracting messages in ${chalk.green(
config.sourcePath
)} using ${chalk.green(config.translator)}()`
);

const messages = extractFromSource(config);

report('Done parsing\n');

generateFiles(config, messages);
report('Done, have fun translating!\n'); // Et voilà!

return true;
};

extractMessages(i18nConfig);
Loading

0 comments on commit e59fd6e

Please sign in to comment.