diff --git a/package.json b/package.json index 44a4967..d2063df 100644 --- a/package.json +++ b/package.json @@ -15,6 +15,8 @@ "lint": "eslint ./src", "i18n:collect": "babel-node ./scripts/i18n-collect.js", "i18n:unescape": "babel-node ./scripts/unescape-i18n.js", + "i18n:pull": "babel-node --presets es2015,stage-0 ./scripts/i18n-onesky.js pull", + "i18n:publish": "babel-node --presets es2015,stage-0 ./scripts/i18n-onesky.js publish", "build": "npm run clean && npm run build:webpack -- --progress", "build:webpack": "webpack --colors -p", "build:quite": "npm run clean && npm run build:webpack -- --quite", diff --git a/scripts/i18n-collect.js b/scripts/i18n-collect.js index 5745fcd..7f419de 100644 --- a/scripts/i18n-collect.js +++ b/scripts/i18n-collect.js @@ -38,7 +38,7 @@ if (duplicateIds.length) { console.log('\nFound duplicated ids:'); duplicateIds.forEach((id) => console.log(`${chalk.yellow(id)}:\n - ${idToFileMap[id].join('\n - ')}\n`)); console.log(chalk.red('Please correct the errors above to proceed further!')); - return; + process.exit(); } duplicateIds = null; @@ -84,7 +84,8 @@ keysToUpdate = Object.entries(prevMessages).reduce((acc, [key, message]) => }); if (!keysToAdd.length && !keysToRemove.length && !keysToUpdate.length && !keysToRename.length) { - return console.log(chalk.green('Everything is up to date!')); + console.log(chalk.green('Everything is up to date!')); + process.exit(); } console.log(chalk.magenta(`The diff relative to default locale (${DEFAULT_LOCALE}) is:`)); diff --git a/scripts/i18n-onesky.js b/scripts/i18n-onesky.js new file mode 100644 index 0000000..0c96131 --- /dev/null +++ b/scripts/i18n-onesky.js @@ -0,0 +1,131 @@ +/* eslint-env node */ +/* eslint-disable no-console */ + +import onesky from 'onesky-utils'; +import fs from 'fs'; +import chalk from 'chalk'; + +const LANG_DIR = `${__dirname}/../src/i18n`; +const SOURCE_LANG = 'en'; // Базовый язык, относительно которого будут формироваться все остальные переводы +const SOURCE_FILE_NAME = 'i18n.json'; // Название файла с исходными строками внутри OneSky + +/** + * Массив локалей для соответствия каноничному виду в OneSky и нашему представлению + * о том, каким должны быть имена локалей + */ +const LOCALES_MAP = { + ru: 'ru-RU', + en: 'en-GB', +}; + +// https://ely-translates.oneskyapp.com/admin/site/settings +const defaultOptions = { + apiKey: '5MaW9TYp0S3qdJgkZ5QLgEIDeabkFDzB', + secret: 'qd075hUNpop4DItD6KOXKQnbqWPLZilf', + projectId: 201323, +}; + +/** + * Переводит из кода языка в OneSky в наше представление + * + * @param {string} code + * @return {string} + */ +function code2locale(code) { + for (const locale in LOCALES_MAP) { + if (code === LOCALES_MAP[locale]) { + return locale; + } + } + + return code; +} + +/** + * Переводит из нашего формата локалей в ожидаемое значение OneSky + * + * @param {string} locale + * @return {string} + */ +function locale2code(locale) { + return LOCALES_MAP[locale] || locale; +} + +/** + * Форматирует входящий объект с переводами в итоговую строку в том формате, в каком они + * хранятся в самом приложении + * + * @param {object} translates + * @return {string} + */ +function formatTranslates(translates) { + return JSON.stringify(sortByKeys(translates), null, 4) + '\n'; // eslint-disable-line prefer-template +} + +/** + * http://stackoverflow.com/a/29622653/5184751 + * + * @param {object} object + * @return {object} + */ +function sortByKeys(object) { + return Object.keys(object).sort().reduce((result, key) => { + result[key] = object[key]; + return result; + }, {}); +} + +async function pullReadyLanguages() { + const languages = JSON.parse(await onesky.getLanguages({...defaultOptions})); + return languages.data + .filter((elem) => elem.is_ready_to_publish) + .map((elem) => elem.custom_locale || elem.code); +} + +async function pullTranslate(language) { + const rawResponse = await onesky.getFile({...defaultOptions, language, fileName: SOURCE_FILE_NAME}); + const response = JSON.parse(rawResponse); + fs.writeFileSync(`${LANG_DIR}/${code2locale(language)}.json`, formatTranslates(response)); +} + +async function pull() { + console.log('Pulling locales list...'); + const langs = await pullReadyLanguages(); + + console.log(chalk.green('Pulled locales: ') + langs.map((lang) => code2locale(lang)).join(', ')); + + console.log('Pulling translates...'); + await Promise.all(langs.map(async (lang) => { + await pullTranslate(lang); + console.log(chalk.green('Locale ') + chalk.white.bold(code2locale(lang)) + chalk.green(' successfully pulled')); + })); +} + +async function publish() { + console.log(`Publishing ${chalk.bold(SOURCE_LANG)} translates file...`); + await onesky.postFile({ + ...defaultOptions, + format: 'HIERARCHICAL_JSON', + content: fs.readFileSync(`${LANG_DIR}/${SOURCE_LANG}.json`, 'utf8'), + keepStrings: false, + language: locale2code(SOURCE_LANG), + fileName: SOURCE_FILE_NAME, + }); + console.log('Success'); +} + +try { + const action = process.argv[2]; + switch (action) { + case 'pull': + pull(); + break; + case 'publish': + publish(); + break; + default: + throw new Error(`Unknown action ${action}`); + } +} catch (exception) { + console.error(exception); +} diff --git a/scripts/package.json b/scripts/package.json index 25bea1e..4497cfc 100644 --- a/scripts/package.json +++ b/scripts/package.json @@ -12,6 +12,7 @@ "dependencies": { "chalk": "^1.1.3", "node-babel": "^0.1.2", + "onesky-utils": "erickskrauch/nodejs-onesky-utils#locales_list", "prompt": "^1.0.0" } } diff --git a/scripts/test-async-await.js b/scripts/test-async-await.js deleted file mode 100644 index 811b012..0000000 --- a/scripts/test-async-await.js +++ /dev/null @@ -1,13 +0,0 @@ -const test = async () => { -}; - -const obj = {a: 1, b: 1}; - -const {a, b} = obj; - -console.log(a, b, 'ok'); - - -// how to: -// cd to frontend -// > ./node_modules/.bin/babel-node --presets es2015,es2017,stage-0 ./scripts/test-async-await.js diff --git a/scripts/unescape-i18n.js b/scripts/unescape-i18n.js deleted file mode 100644 index b4d9191..0000000 --- a/scripts/unescape-i18n.js +++ /dev/null @@ -1,16 +0,0 @@ -/* eslint-env node */ -/* eslint-disable no-console */ -import fs from 'fs'; -import {sync as globSync} from 'glob'; - -const LANG_DIR = `${__dirname}/../src/i18n`; - -/** - * При выгрузке из OneSky мы получаем json, в котором все не-латинские символы за-escape-ны. - * Это увеличивает вес переводов и портит дифы. Поэтому мы просто прокручиваем их json - * и на выходе получаем чистые файлы, без escape-последовательностей. - */ -globSync(`${LANG_DIR}/*.json`).forEach((filename) => { - const json = JSON.parse(fs.readFileSync(filename, 'utf8')); - fs.writeFileSync(filename, JSON.stringify(json, null, 4) + "\n"); -}); diff --git a/src/i18n/be.json b/src/i18n/be.json index 928972d..711c8b8 100644 --- a/src/i18n/be.json +++ b/src/i18n/be.json @@ -21,7 +21,7 @@ "components.auth.chooseAccount.addAccount": "Увайсці ў другі акаўнт", "components.auth.chooseAccount.chooseAccountTitle": "Выбар акаўнта", "components.auth.chooseAccount.description": "Вы выканалі ўваход у некалькі акаўнтаў. Пазначце, які вы жадаеце выкарыстаць для аўтарызацыі {appName}", - "components.auth.chooseAccount.logoutAll": "Выйсці з усіх акаўтаў", + "components.auth.chooseAccount.logoutAll": "Выйсці з усіх акаўнтаў", "components.auth.finish.authForAppFailed": "Аўтарызацыя для {appName} не атрымалася", "components.auth.finish.authForAppSuccessful": "Аўтарызацыя для {appName} паспяхова выканана", "components.auth.finish.copy": "Скапіяваць",