From 6c3c4fc547e5ce673f8b53c7cae04e029d542321 Mon Sep 17 00:00:00 2001 From: David Baker Date: Fri, 26 May 2017 16:48:21 +0100 Subject: [PATCH] Language generation and file structure * Move language files to strings/ subdir to be consistent with react-sdk * Only copy static list of languages (to avoid including languages that are only a few percent translated) * Make copy-res script work with watch mode * Other general cleanups like only write each language file once, rather than n times. --- scripts/copy-res.js | 160 ++++++++++++++------------- src/i18n/{ => strings}/basefile.json | 0 src/i18n/{ => strings}/be.json | 0 src/i18n/{ => strings}/da.json | 0 src/i18n/{ => strings}/de_DE.json | 0 src/i18n/{ => strings}/en_EN.json | 0 src/i18n/{ => strings}/fr.json | 0 src/i18n/{ => strings}/ml.json | 0 src/i18n/{ => strings}/pl.json | 0 src/i18n/{ => strings}/pt.json | 0 src/i18n/{ => strings}/pt_BR.json | 0 src/i18n/{ => strings}/ru.json | 0 12 files changed, 81 insertions(+), 79 deletions(-) rename src/i18n/{ => strings}/basefile.json (100%) rename src/i18n/{ => strings}/be.json (100%) rename src/i18n/{ => strings}/da.json (100%) rename src/i18n/{ => strings}/de_DE.json (100%) rename src/i18n/{ => strings}/en_EN.json (100%) rename src/i18n/{ => strings}/fr.json (100%) rename src/i18n/{ => strings}/ml.json (100%) rename src/i18n/{ => strings}/pl.json (100%) rename src/i18n/{ => strings}/pt.json (100%) rename src/i18n/{ => strings}/pt_BR.json (100%) rename src/i18n/{ => strings}/ru.json (100%) diff --git a/scripts/copy-res.js b/scripts/copy-res.js index 4736d401..9672a98c 100755 --- a/scripts/copy-res.js +++ b/scripts/copy-res.js @@ -3,6 +3,23 @@ // copies the resources into the webapp directory. // +// Languages are listed manually so we can choose when to include +// a translation in the app (because having a translation with only +// 3 strings translated is just frustrating) +// This could readily be automated, but it's nice to explicitly +// control when we languages are available. +const INCLUDE_LANGS = [ + //'be' Omitted because no translations in react-sdk + 'en_EN', + 'da', + 'de_DE', + 'fr', + 'be', + 'pt', + 'pt_BR', + 'ru', +]; + // cpx includes globbed parts of the filename in the destination, but excludes // common parents. Hence, "res/{a,b}/**": the output will be "dest/a/..." and // "dest/b/...". @@ -14,32 +31,20 @@ const COPY_LIST = [ ["node_modules/emojione/assets/svg/*", "webapp/emojione/svg/"], ["node_modules/emojione/assets/png/*", "webapp/emojione/png/"], ["./config.json", "webapp", { directwatch: 1 }], - ["src/i18n/", "webapp/i18n/", { languages: 1 }], - ["node_modules/matrix-react-sdk/src/i18n/strings/", "webapp/i18n/", { languages: 1 }], ]; +INCLUDE_LANGS.forEach(function(l) { + COPY_LIST.push([ + l, "webapp/i18n/", { lang: 1 }, + ]); +}); + const parseArgs = require('minimist'); const Cpx = require('cpx'); const chokidar = require('chokidar'); const fs = require('fs'); const rimraf = require('rimraf'); -// cleanup language files before copying them. -//rimraf("webapp/", function () { console.log('cleanup language files'); }); - -//From http://stackoverflow.com/a/20525865/4929236 -function generateFileArray(dir, files_) { - files_ = files_ || []; - var files = fs.readdirSync(dir); - for (var i in files) { - var name = files[i]; - if (name != 'basefile.json') { - files_.push(name); - } - } - return files_; -} - const argv = parseArgs( process.argv.slice(2), {} ); @@ -54,6 +59,15 @@ function errCheck(err) { } } +// Check if webapp exists +if (!fs.existsSync('webapp')) { + fs.mkdirSync('webapp'); +} +// Check if i18n exists +if (!fs.existsSync('webapp/i18n/')) { + fs.mkdirSync('webapp/i18n/'); +} + function next(i, err) { errCheck(err); @@ -67,32 +81,11 @@ function next(i, err) { const opts = ent[2] || {}; let cpx = undefined; - if (opts.languages) { - const sourceFiles = generateFileArray(source); - let Sourcelanguages = {}; - if (!fs.existsSync(dest)) { - fs.mkdirSync(dest); - } - sourceFiles.forEach(file => { - const fileContents = fs.readFileSync(source + file).toString(); - Sourcelanguages[file] = JSON.parse(fileContents); - }); - sourceFiles.forEach(file => { - if (!fs.existsSync(dest + file)) { - let o = Object.assign({}, Sourcelanguages[file]); - fs.writeFileSync(dest + file, JSON.stringify(o, null, 4)); - } else { - const fileContents = fs.readFileSync(dest + file).toString(); - let o = Object.assign(JSON.parse(fileContents), Sourcelanguages[file]); - fs.writeFileSync(dest + file, JSON.stringify(o, null, 4)); - } - }); - - } else { + if (!opts.lang) { cpx = new Cpx.Cpx(source, dest); } - if (verbose) { + if (verbose && cpx) { cpx.on("copy", (event) => { console.log(`Copied: ${event.srcPath} --> ${event.dstPath}`); }); @@ -115,59 +108,68 @@ function next(i, err) { .on('change', copy) .on('ready', cb) .on('error', errCheck); - } else if (opts.languages) { - if (verbose) { - console.log('don\'t copy language file'); - } + } else if (opts.lang) { + const reactSdkFile = 'node_modules/matrix-react-sdk/src/i18n/strings/' + source + '.json'; + const riotWebFile = 'src/i18n/strings/' + source + '.json'; + + const translations = {}; + const makeLang = () => { genLangFile(source, dest) }; + [reactSdkFile, riotWebFile].forEach(function(f) { + chokidar.watch(f) + .on('add', makeLang) + .on('change', makeLang) + //.on('ready', cb) We'd have to do this when both files are ready + .on('error', errCheck); + }); next(i + 1, err); } else { cpx.on('watch-ready', cb); cpx.on("watch-error", cb); cpx.watch(); } - } else if (opts.languages) { - if (verbose) { - console.log('don\'t copy language file'); - } + } else if (opts.lang) { + genLangFile(source, dest); next(i + 1, err); } else { cpx.copy(cb); } } -// Generate Language List +function genLangFile(lang, dest) { + const reactSdkFile = 'node_modules/matrix-react-sdk/src/i18n/strings/' + lang + '.json'; + const riotWebFile = 'src/i18n/strings/' + lang + '.json'; -const testFolder = 'src/i18n/'; -let languages = {}; -// Check if webapp exists -if (!fs.existsSync('webapp')) { - fs.mkdirSync('webapp'); -} -// Check if i18n exists -if (!fs.existsSync('webapp/i18n/')) { - fs.mkdirSync('webapp/i18n/'); -} - -if (!fs.existsSync('webapp/i18n/languages.json')) { - rimraf("webapp/i18n/languages.json", function() { console.log('cleanup languages.json file'); }); -} - -fs.readdir(testFolder, function(err, files) { - if (err) { - throw err; + const translations = {}; + [reactSdkFile, riotWebFile].forEach(function(f) { + if (fs.existsSync(f)) { + Object.assign( + translations, + JSON.parse(fs.readFileSync(f).toString()) + ); + } + }); + fs.writeFileSync(dest + lang + '.json', JSON.stringify(translations, null, 4)); + if (verbose) { + console.log("Generated language file: " + lang); } - files.forEach(function(file) { - var normalizedLanguage = file.toLowerCase().replace("_", "-").split('.json')[0]; - var languageParts = normalizedLanguage.split('-'); - if (file != 'basefile.json') { - if (languageParts.length == 2 && languageParts[0] == languageParts[1]) { - languages[languageParts[0]] = file; - } else { - languages[normalizedLanguage] = file; - } +} + +function genLangList() { + const languages = {}; + INCLUDE_LANGS.forEach(function(lang) { + const normalizedLanguage = lang.toLowerCase().replace("_", "-"); + const languageParts = normalizedLanguage.split('-'); + if (languageParts.length == 2 && languageParts[0] == languageParts[1]) { + languages[languageParts[0]] = lang; + } else { + languages[normalizedLanguage] = lang; } }); fs.writeFile('webapp/i18n/languages.json', JSON.stringify(languages, null, 4)); -}) + if (verbose) { + console.log("Generated language list"); + } +} -next(0); \ No newline at end of file +genLangList(); +next(0); diff --git a/src/i18n/basefile.json b/src/i18n/strings/basefile.json similarity index 100% rename from src/i18n/basefile.json rename to src/i18n/strings/basefile.json diff --git a/src/i18n/be.json b/src/i18n/strings/be.json similarity index 100% rename from src/i18n/be.json rename to src/i18n/strings/be.json diff --git a/src/i18n/da.json b/src/i18n/strings/da.json similarity index 100% rename from src/i18n/da.json rename to src/i18n/strings/da.json diff --git a/src/i18n/de_DE.json b/src/i18n/strings/de_DE.json similarity index 100% rename from src/i18n/de_DE.json rename to src/i18n/strings/de_DE.json diff --git a/src/i18n/en_EN.json b/src/i18n/strings/en_EN.json similarity index 100% rename from src/i18n/en_EN.json rename to src/i18n/strings/en_EN.json diff --git a/src/i18n/fr.json b/src/i18n/strings/fr.json similarity index 100% rename from src/i18n/fr.json rename to src/i18n/strings/fr.json diff --git a/src/i18n/ml.json b/src/i18n/strings/ml.json similarity index 100% rename from src/i18n/ml.json rename to src/i18n/strings/ml.json diff --git a/src/i18n/pl.json b/src/i18n/strings/pl.json similarity index 100% rename from src/i18n/pl.json rename to src/i18n/strings/pl.json diff --git a/src/i18n/pt.json b/src/i18n/strings/pt.json similarity index 100% rename from src/i18n/pt.json rename to src/i18n/strings/pt.json diff --git a/src/i18n/pt_BR.json b/src/i18n/strings/pt_BR.json similarity index 100% rename from src/i18n/pt_BR.json rename to src/i18n/strings/pt_BR.json diff --git a/src/i18n/ru.json b/src/i18n/strings/ru.json similarity index 100% rename from src/i18n/ru.json rename to src/i18n/strings/ru.json