From 8e36765c2efa48e09ee3fd0da1b1e82eae3b2c0e Mon Sep 17 00:00:00 2001 From: cpsdqs Date: Mon, 2 Oct 2017 11:40:44 +0200 Subject: [PATCH 1/3] Use webpack to load locale data --- .gitignore | 1 - dump_js_lang.php | 28 ------- js/lang.js | 5 ++ lang/keys.js | 12 +++ lang/lang-loader.js | 178 ++++++++++++++++++++++++++++++++++++++++++++ webpack.config.js | 11 +++ 6 files changed, 206 insertions(+), 29 deletions(-) delete mode 100755 dump_js_lang.php create mode 100644 js/lang.js create mode 100644 lang/keys.js create mode 100644 lang/lang-loader.js diff --git a/.gitignore b/.gitignore index 015a031..0de9aaf 100644 --- a/.gitignore +++ b/.gitignore @@ -7,4 +7,3 @@ node_modules/ .idea .sass-cache *.map -js/lang.js diff --git a/dump_js_lang.php b/dump_js_lang.php deleted file mode 100755 index 80196de..0000000 --- a/dump_js_lang.php +++ /dev/null @@ -1,28 +0,0 @@ - match[1]) + } else { + // double-quoted string + // \n -> 0x0A + // \r -> 0x0D + // \t -> 0x09 + // \v -> 0x0B + // \e -> 0x1B + // \f -> 0x0C + // \\ -> \ + // \$ -> $ + // \" -> " + // \[0-7]{1,3} -> char + // \x[\da-f]{1,2} -> char + // \u{[\da-f]+} -> char + let content = token.content + content = content.replace(/\\[nrtvef\\$"]/g, match => match[1]) + content = content.replace(/\\[0-7]{1,3}/g, match => { + return String.fromCodePoint(parseInt(match.substr(1), 8) % 0xFF) + }) + content = content.replace(/\\x[\da-f]{1,2}/ig, match => { + return String.fromCodePoint(parseInt(match.substr(1), 16) % 0xFF) + }) + content = content.replace(/\\u{[\da-f]+}/ig, match => { + return String.fromCodePoint(parseInt(match.substr(1), 16)) + }) + return content + } +} + +module.exports = function (source) { + let originalSource = source + + // remove PHP header + source = source.replace(/^\s*<\?(?:php)?\s*/, '') + + // remove return statement + source = source.replace(/^return\s*/, '') + + // remove trailing semicolon + source = source.replace(/;\s*$/, '') + + // strings + let tokens = tokenizeRest([source], tokenizers.simpleType, /(['"])((?:\\.|[^\\\1])*?)\1/, 'string', 2) + + // comments + tokenizeRest(tokens, tokenizers.simpleType, /\/\/(.*)/, 'comment') + + // map delimiters + tokenizeRest(tokens, tokenizers.simpleType, /([[\]])/, 'map') + + // arrows + tokenizeRest(tokens, tokenizers.simpleType, /(=>)/, 'arrow') + + // commas + tokenizeRest(tokens, tokenizers.simpleType, /(,)/, 'comma') + + // whitespace + tokenizeRest(tokens, tokenizers.simpleType, /(\s+)/, 'whitespace') + + // remove whitespace tokens and comments + tokens = tokens.filter(token => !(['whitespace', 'comment'].includes(token.type))) + + // split tokens into an array of items, separated by commas + let currentItem = [] + let items = [currentItem] + + for (let token of tokens) { + let { type } = token + + if (type === 'map') continue + if (type === 'comma') items.push(currentItem = []) + else currentItem.push(token) + } + + // remove null items + items = items.filter(item => item.length) + + // assume that every item will contain [string, arrow, string] and create + // an object + let data = {} + + for (let item of items) { + let key = item[0] + let value = item[2] + + if (!key || !value) { + console.error('Item has no key or value!', item) + continue + } + + data[unescapeString(key)] = unescapeString(value) + } + + let result = {} + + // put selected keys in result + for (let key of selectedKeys) { + result[key] = data[key] + } + + // adapted from webpack/loader-utils + let remainingRequest = this.remainingRequest + if (!remainingRequest) { + remainingRequest = this.loaders.slice(this.loaderIndex + 1) + .map(obj => obj.request) + .concat([this.resource]).join('!') + } + + let currentRequest = this.currentRequest + if (!currentRequest) { + remainingRequest = this.loaders.slice(this.loaderIndex) + .map(obj => obj.request) + .concat([this.resource]).join('!') + } + + let map = { + version: 3, + file: currentRequest, + sourceRoot: '', + sources: [remainingRequest], + sourcesContent: [originalSource], + names: [], + mappings: 'AAAA;AAAA' + } + + this.callback(null, `/* Generated language file */ +module.exports=${JSON.stringify(result)}`, map) +} diff --git a/webpack.config.js b/webpack.config.js index e332745..94b9866 100644 --- a/webpack.config.js +++ b/webpack.config.js @@ -20,6 +20,13 @@ plugins.push(new webpack.optimize.UglifyJsPlugin({ sourceMap: devtool === 'source-map' })) +// replace "locale-data" with path to locale data +let locale = process.env.LOCALE || 'en' +plugins.push(new webpack.NormalModuleReplacementPlugin( + /^locale-data$/, + path.resolve(`lang/${locale}.php`) +)) + module.exports = { entry: './js', output: { @@ -34,6 +41,10 @@ module.exports = { path.resolve(__dirname, 'node_modules') ], loader: 'babel-loader' + }, + { + test: /lang\/.+?\.php$/, + loader: './lang/lang-loader.js' } ] }, From dcdfa511e7cc907c61781d881f6095f9d4640475 Mon Sep 17 00:00:00 2001 From: cpsdqs Date: Tue, 3 Oct 2017 10:09:43 +0200 Subject: [PATCH 2/3] [lang] Use a PHP script instead of parsing PHP --- _build_js.sh | 2 - lang/dump_selected.php | 14 ++++ lang/lang-loader.js | 159 +++++------------------------------------ webpack.config.js | 2 +- 4 files changed, 32 insertions(+), 145 deletions(-) create mode 100755 lang/dump_selected.php diff --git a/_build_js.sh b/_build_js.sh index fea3428..32ff64a 100755 --- a/_build_js.sh +++ b/_build_js.sh @@ -2,8 +2,6 @@ source "_build_common.sh" mkdir -p out/js -echo 'Generating lang.js...' -php ./dump_js_lang.php $@ echo 'Processing JS...' npm run webpack diff --git a/lang/dump_selected.php b/lang/dump_selected.php new file mode 100755 index 0000000..411d6f2 --- /dev/null +++ b/lang/dump_selected.php @@ -0,0 +1,14 @@ +#! /usr/bin/env php + match[1]) - } else { - // double-quoted string - // \n -> 0x0A - // \r -> 0x0D - // \t -> 0x09 - // \v -> 0x0B - // \e -> 0x1B - // \f -> 0x0C - // \\ -> \ - // \$ -> $ - // \" -> " - // \[0-7]{1,3} -> char - // \x[\da-f]{1,2} -> char - // \u{[\da-f]+} -> char - let content = token.content - content = content.replace(/\\[nrtvef\\$"]/g, match => match[1]) - content = content.replace(/\\[0-7]{1,3}/g, match => { - return String.fromCodePoint(parseInt(match.substr(1), 8) % 0xFF) - }) - content = content.replace(/\\x[\da-f]{1,2}/ig, match => { - return String.fromCodePoint(parseInt(match.substr(1), 16) % 0xFF) - }) - content = content.replace(/\\u{[\da-f]+}/ig, match => { - return String.fromCodePoint(parseInt(match.substr(1), 16)) - }) - return content - } -} - module.exports = function (source) { - let originalSource = source - - // remove PHP header - source = source.replace(/^\s*<\?(?:php)?\s*/, '') - - // remove return statement - source = source.replace(/^return\s*/, '') - - // remove trailing semicolon - source = source.replace(/;\s*$/, '') - - // strings - let tokens = tokenizeRest([source], tokenizers.simpleType, /(['"])((?:\\.|[^\\\1])*?)\1/, 'string', 2) - - // comments - tokenizeRest(tokens, tokenizers.simpleType, /\/\/(.*)/, 'comment') - - // map delimiters - tokenizeRest(tokens, tokenizers.simpleType, /([[\]])/, 'map') - - // arrows - tokenizeRest(tokens, tokenizers.simpleType, /(=>)/, 'arrow') - - // commas - tokenizeRest(tokens, tokenizers.simpleType, /(,)/, 'comma') - - // whitespace - tokenizeRest(tokens, tokenizers.simpleType, /(\s+)/, 'whitespace') - - // remove whitespace tokens and comments - tokens = tokens.filter(token => !(['whitespace', 'comment'].includes(token.type))) - - // split tokens into an array of items, separated by commas - let currentItem = [] - let items = [currentItem] - - for (let token of tokens) { - let { type } = token - - if (type === 'map') continue - if (type === 'comma') items.push(currentItem = []) - else currentItem.push(token) - } - - // remove null items - items = items.filter(item => item.length) - - // assume that every item will contain [string, arrow, string] and create - // an object - let data = {} - - for (let item of items) { - let key = item[0] - let value = item[2] - - if (!key || !value) { - console.error('Item has no key or value!', item) - continue - } - - data[unescapeString(key)] = unescapeString(value) - } + let child = spawnSync(path.resolve(__dirname, 'dump_selected.php'), selectedKeys, { + timeout: 10000 + }) - let result = {} + let data + try { + data = JSON.parse(child.stdout.toString().trim()) + } catch (err) { + console.error(`\x1b[31;1m[lang-loader] Failed to parse JSON:`) + console.error(child.stdout.toString().trim()) + console.error(`\x1b[m`) - // put selected keys in result - for (let key of selectedKeys) { - result[key] = data[key] + if (err) throw err } // adapted from webpack/loader-utils @@ -168,11 +43,11 @@ module.exports = function (source) { file: currentRequest, sourceRoot: '', sources: [remainingRequest], - sourcesContent: [originalSource], + sourcesContent: [source], names: [], mappings: 'AAAA;AAAA' } this.callback(null, `/* Generated language file */ -module.exports=${JSON.stringify(result)}`, map) +module.exports=${JSON.stringify(data)}`, map) } diff --git a/webpack.config.js b/webpack.config.js index 94b9866..e70fcb1 100644 --- a/webpack.config.js +++ b/webpack.config.js @@ -21,7 +21,7 @@ plugins.push(new webpack.optimize.UglifyJsPlugin({ })) // replace "locale-data" with path to locale data -let locale = process.env.LOCALE || 'en' +let locale = process.env.ESP_LANG || 'en' plugins.push(new webpack.NormalModuleReplacementPlugin( /^locale-data$/, path.resolve(`lang/${locale}.php`) From 1c673a5a718779aabf1c5eb5e61ec66e95ed3486 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ond=C5=99ej=20Hru=C5=A1ka?= Date: Tue, 3 Oct 2017 19:16:53 +0200 Subject: [PATCH 3/3] renamed files to make it more clear what are actual translations in the lang foler --- lang/{dump_selected.php => _js-dump.php} | 0 lang/{lang-loader.js => _js-lang-loader.js} | 11 ++++++----- lang/{keys.js => js-keys.js} | 0 webpack.config.js | 2 +- 4 files changed, 7 insertions(+), 6 deletions(-) rename lang/{dump_selected.php => _js-dump.php} (100%) rename lang/{lang-loader.js => _js-lang-loader.js} (81%) rename lang/{keys.js => js-keys.js} (100%) diff --git a/lang/dump_selected.php b/lang/_js-dump.php similarity index 100% rename from lang/dump_selected.php rename to lang/_js-dump.php diff --git a/lang/lang-loader.js b/lang/_js-lang-loader.js similarity index 81% rename from lang/lang-loader.js rename to lang/_js-lang-loader.js index 4561768..8027abc 100644 --- a/lang/lang-loader.js +++ b/lang/_js-lang-loader.js @@ -5,11 +5,11 @@ const { spawnSync } = require('child_process') const path = require('path') -const selectedKeys = require('./keys') +const selectedKeys = require('./js-keys') module.exports = function (source) { - let child = spawnSync(path.resolve(__dirname, 'dump_selected.php'), selectedKeys, { - timeout: 10000 + let child = spawnSync(path.resolve(__dirname, '_js-dump.php'), selectedKeys, { + timeout: 1000 }) let data @@ -48,6 +48,7 @@ module.exports = function (source) { mappings: 'AAAA;AAAA' } - this.callback(null, `/* Generated language file */ -module.exports=${JSON.stringify(data)}`, map) + this.callback(null, + `/* Generated language file */\n` + + `module.exports=${JSON.stringify(data)}\n`, map) } diff --git a/lang/keys.js b/lang/js-keys.js similarity index 100% rename from lang/keys.js rename to lang/js-keys.js diff --git a/webpack.config.js b/webpack.config.js index e70fcb1..29ba3f4 100644 --- a/webpack.config.js +++ b/webpack.config.js @@ -44,7 +44,7 @@ module.exports = { }, { test: /lang\/.+?\.php$/, - loader: './lang/lang-loader.js' + loader: './lang/_js-lang-loader.js' } ] },