From 7c905823cea9facb44f248dae866d78d60ac5bcb Mon Sep 17 00:00:00 2001 From: Patryk Wychowaniec Date: Thu, 13 Aug 2020 14:16:23 +0200 Subject: [PATCH 1/2] #1 add MyMemory translation service * add option to change translation service * normalize paths --- package-lock.json | 7 +++- package.json | 3 +- src/commands/translateCommand.js | 9 ++--- src/dictionaryService.js | 14 +++++--- src/factories/TranslationServiceFactory.js | 15 +++++++++ src/index.js | 6 ++-- src/translate/MyMemoryTranslateService.js | 35 ++++++++++++++++++++ test/command/MyMemoryCommand.spec.js | 23 +++++++++++++ test/command/TranslateCommand.spec.js | 24 ++++++++++---- test/factory/TranslateServiceFactory.spec.js | 18 ++++++++++ 10 files changed, 135 insertions(+), 19 deletions(-) create mode 100644 src/factories/TranslationServiceFactory.js create mode 100644 src/translate/MyMemoryTranslateService.js create mode 100644 test/command/MyMemoryCommand.spec.js create mode 100644 test/factory/TranslateServiceFactory.spec.js diff --git a/package-lock.json b/package-lock.json index bc50a99..bbeff1d 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1,6 +1,6 @@ { "name": "aem-polyglot", - "version": "0.2.3", + "version": "0.2.2", "lockfileVersion": 1, "requires": true, "dependencies": { @@ -2152,6 +2152,11 @@ } } }, + "node-fetch": { + "version": "2.6.0", + "resolved": "https://registry.npmjs.org/node-fetch/-/node-fetch-2.6.0.tgz", + "integrity": "sha512-8dG4H5ujfvFiqDmVu9fQ5bOHUC15JMjMY/Zumv26oOvvVJjM67KF8koCWIabKQ1GJIa9r2mMZscBq/TbdOcmNA==" + }, "normalize-package-data": { "version": "2.4.0", "resolved": "https://registry.npmjs.org/normalize-package-data/-/normalize-package-data-2.4.0.tgz", diff --git a/package.json b/package.json index 92be581..5d0c7fa 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "aem-polyglot", - "version": "0.2.2", + "version": "0.3.0", "description": "translate i18n dictionairies with ease using Google Translate. Tool designed mainly for developers. ", "repository": "https://github.com/zietas/aem-polyglot", "main": "src/index.js", @@ -31,6 +31,7 @@ "dotenv": "^8.0.0", "google-translate": "^2.2.0", "lodash": "^4.17.11", + "node-fetch": "^2.6.0", "xml-js": "^1.6.11", "yandex-translate": "^2.1.2" }, diff --git a/src/commands/translateCommand.js b/src/commands/translateCommand.js index 1365a98..ba2bd79 100644 --- a/src/commands/translateCommand.js +++ b/src/commands/translateCommand.js @@ -1,19 +1,20 @@ const dictionaryService = require('../dictionaryService'); -const YandexTranslateService = require('../translate/YandexTranslateService'); const TranslateDictionaryService = require('../translate/TranslateDictionaryService'); const translationLog = require('../../src/translate/TranslationLog'); +const TranslateServiceFactory = require('../factories/TranslationServiceFactory'); async function translateCommand (source, target, options) { - const apiKey = process.env.YANDEX_API_KEY || options.yandexApiKey; + const apiKey = process.env.API_KEY || options.apiKey; if (!apiKey) { - console.log('Yandex API key is not defined'); + console.log('Translation Service API key is not defined. If your service does not require API key, provide any text.'); return; } try { console.log(`Translating '${source}' to ${target}`); const sourceDict = await dictionaryService.readDict(source); const targetDict = await dictionaryService.readDict(target); - const translationService = new YandexTranslateService(apiKey); + const CurrentTranslationService = TranslateServiceFactory(options.service); + const translationService = new CurrentTranslationService(apiKey); const translateDictionaryService = new TranslateDictionaryService(translationService, { keys: options.keys }); diff --git a/src/dictionaryService.js b/src/dictionaryService.js index 49e5d10..de72e76 100644 --- a/src/dictionaryService.js +++ b/src/dictionaryService.js @@ -1,4 +1,5 @@ const fs = require('fs'); +const path = require('path'); const _ = require('lodash'); const converter = require('xml-js'); const alphabetize = require('alphabetize-object-keys'); @@ -73,20 +74,23 @@ function sort (dict) { return alphabetize(dict); } -function listDict (path) { - const paths = fs.readdirSync(path); +function listDict (_path) { + const normalizedPath = path.resolve(_path); + const paths = fs.readdirSync(normalizedPath); return _.filter(paths, (item) => { return !item.startsWith('.') && item.endsWith('.xml'); }); } -function exists (path) { - return fs.existsSync(path); +function exists (_path) { + const normalizedPath = path.resolve(_path); + return fs.existsSync(normalizedPath); } async function readDict (source) { return new Promise((resolve, reject) => { - fs.readFile(source, 'utf-8', (err, data) => { + const normalizedPath = path.resolve(source); + fs.readFile(normalizedPath, 'utf-8', (err, data) => { if (err) { reject(err); } else { diff --git a/src/factories/TranslationServiceFactory.js b/src/factories/TranslationServiceFactory.js new file mode 100644 index 0000000..9438671 --- /dev/null +++ b/src/factories/TranslationServiceFactory.js @@ -0,0 +1,15 @@ +const YandexTranslateService = require('../translate/YandexTranslateService'); +const MyMemoryTranslateService = require('../translate/MyMemoryTranslateService'); + +const serviceMap = { + yandex: YandexTranslateService, + mymemory: MyMemoryTranslateService +}; + +function TranslateServiceFactory (service = 'yandex') { + const translateService = serviceMap[service] || YandexTranslateService; + + return translateService; +} + +module.exports = TranslateServiceFactory; diff --git a/src/index.js b/src/index.js index b85517a..91d9c97 100644 --- a/src/index.js +++ b/src/index.js @@ -43,7 +43,8 @@ program .command('translate ') .description('based on master dictionary it searches for missing keys in dictionary and translates them') .option('--disableSorting', 'Disable dictionary sorting') - .option('--yandexApiKey ', 'Yandex API Key') + .option('--service ', 'Translation Service, Yandex default') + .option('--apiKey ', 'Translation Service API Key') .option('--keys ', 'A comma separated list of dictionaries keys. I.e. --keys=key1,key2,key3') .action(translateCommand); @@ -51,7 +52,8 @@ program .command('translate-batch ') .description('batch translates all dictionaries in a based on defined ') .option('--disableSorting', 'Disable dictionary sorting') - .option('--yandexApiKey ', 'Yandex API Key') + .option('--service ', 'Translation Service, Yandex default') + .option('--apiKey ', 'Translation Service API Key') .option('--keys ', 'A comma separated list of dictionaries keys. I.e. --keys=key1,key2,key3') .action(translateBatchCommand); diff --git a/src/translate/MyMemoryTranslateService.js b/src/translate/MyMemoryTranslateService.js new file mode 100644 index 0000000..607d2dc --- /dev/null +++ b/src/translate/MyMemoryTranslateService.js @@ -0,0 +1,35 @@ +const fetch = require('node-fetch'); + +const TranslateService = require('./TranslateService'); + +const API = 'https://api.mymemory.translated.net/get'; + +class MyMemoryTranslateService extends TranslateService { + async translate (key, text, from, to) { + return new Promise((resolve, reject) => { + this.log.addEntry(key, from, text); + + const URL = this.getUrl(this.getParams(text, from, to)); + + fetch(URL) + .then(data => data.json()) + .then(data => resolve(data.responseData.translatedText)) + .catch(err => reject(err)); + }); + } + + getUrl (params) { + return `${API}?${params}`; + } + + getParams (text, from, to) { + const params = new URLSearchParams(); + + params.append('q', text); + params.append('langpair', `${from}|${to}`); + + return params.toString(); + } +} + +module.exports = MyMemoryTranslateService; diff --git a/test/command/MyMemoryCommand.spec.js b/test/command/MyMemoryCommand.spec.js new file mode 100644 index 0000000..59115e7 --- /dev/null +++ b/test/command/MyMemoryCommand.spec.js @@ -0,0 +1,23 @@ +const chai = require('chai'); +const expect = chai.expect; + +const MyMemoryTranslateService = require('../../src/translate/MyMemoryTranslateService'); + +describe('myMemoryTranslateService specific', () => { + beforeEach(() => { + this.myMemoryTranslateService = new MyMemoryTranslateService('dummy'); + }); + + it('should generate proper param string', () => { + const params = this.myMemoryTranslateService.getParams('test', 'en', 'pl'); + + expect(params.toString()).to.be.equal('q=test&langpair=en%7Cpl'); + }); + + it('should generate proper URL', () => { + const params = this.myMemoryTranslateService.getParams('test', 'en', 'pl'); + const url = this.myMemoryTranslateService.getUrl(params.toString()); + + expect(url).to.be.equal('https://api.mymemory.translated.net/get?q=test&langpair=en%7Cpl'); + }); +}); diff --git a/test/command/TranslateCommand.spec.js b/test/command/TranslateCommand.spec.js index 3f379d4..ef66455 100644 --- a/test/command/TranslateCommand.spec.js +++ b/test/command/TranslateCommand.spec.js @@ -6,8 +6,14 @@ const dictionaryService = require('../../src/dictionaryService'); const translationLog = require('../../src/translate/TranslationLog'); const yandexTranslateStub = sinon.stub(); +const myMemoryTranslateStub = sinon.stub(); const dictionaryTranslateStub = sinon.stub(); const tested = proxyquire('../../src/commands/translateCommand', { + '../../src/translate/MyMemoryTranslateService': function () { + return { + translate: myMemoryTranslateStub + }; + }, '../../src/translate/YandexTranslateService': function () { return { translate: yandexTranslateStub @@ -26,6 +32,12 @@ yandexTranslateStub.callsFake(() => { }); }); +myMemoryTranslateStub.callsFake(() => { + return new Promise((resolve) => { + resolve({ newDict: true }); + }); +}); + describe('translateCommand', () => { beforeEach(() => { this.consoleSpy = sinon.spy(console, 'log'); @@ -47,16 +59,16 @@ describe('translateCommand', () => { dictionaryTranslateStub.resetHistory(); }); - it('should not process without Yandex API key', async () => { + it('should not process without Service API key', async () => { await tested('./source.xml', './target.xml', {}); expect(dictionaryTranslateStub).to.not.have.been.called; expect(this.saveDictStub).to.not.have.been.called; - expect(this.consoleSpy).to.have.been.calledWithExactly('Yandex API key is not defined'); + expect(this.consoleSpy).to.have.been.calledWithExactly('Translation Service API key is not defined. If your service does not require API key, provide any text.'); }); it('should not sort translated dict if sorting is diabled', async () => { - await tested('./source.xml', './target.xml', { yandexApiKey: 'fakeKey', disableSorting: true }); + await tested('./source.xml', './target.xml', { apiKey: 'fakeKey', disableSorting: true }); expect(dictionaryTranslateStub).to.have.been.calledOnce; expect(this.saveDictStub).to.have.been.calledOnce; @@ -64,7 +76,7 @@ describe('translateCommand', () => { }); it('should process command normally', async () => { - await tested('./source.xml', './target.xml', { yandexApiKey: 'fakeKey', disableLogOutput: false }); + await tested('./source.xml', './target.xml', { apiKey: 'fakeKey', disableLogOutput: false }); expect(dictionaryTranslateStub).to.have.been.calledOnce; expect(this.saveDictStub).to.have.been.calledOnce; @@ -73,7 +85,7 @@ describe('translateCommand', () => { }); it('should not print log at the end when disabledLog options is set', async () => { - await tested('./source.xml', './target.xml', { yandexApiKey: 'fakeKey', disableLogOutput: true }); + await tested('./source.xml', './target.xml', { apiKey: 'fakeKey', disableLogOutput: true }); expect(dictionaryTranslateStub).to.have.been.calledOnce; expect(this.saveDictStub).to.have.been.calledOnce; @@ -84,7 +96,7 @@ describe('translateCommand', () => { const err = new Error('what a terrible failure!'); dictionaryTranslateStub.throws(err); - await tested('./source.xml', './target.xml', { yandexApiKey: 'fakeKey' }); + await tested('./source.xml', './target.xml', { apiKey: 'fakeKey' }); expect(dictionaryTranslateStub).to.have.been.calledOnce; expect(this.saveDictStub).to.not.have.been.called; diff --git a/test/factory/TranslateServiceFactory.spec.js b/test/factory/TranslateServiceFactory.spec.js new file mode 100644 index 0000000..23b66f9 --- /dev/null +++ b/test/factory/TranslateServiceFactory.spec.js @@ -0,0 +1,18 @@ +const chai = require('chai'); +const expect = chai.expect; + +const TranslateServiceFactory = require('../../src/factories/TranslationServiceFactory'); + +describe('translateServiceFactory', () => { + it('should by Yandex by default', () => { + expect(TranslateServiceFactory().name).to.equal('YandexTranslateService'); + }); + + it('should by Yandex when service does not exist', () => { + expect(TranslateServiceFactory('DummyService').name).to.equal('YandexTranslateService'); + }); + + it('should by MyMemory', () => { + expect(TranslateServiceFactory('mymemory').name).to.equal('MyMemoryTranslateService'); + }); +}); From 8feef0dc42780916fa65cfe0bbe006ab55a6896b Mon Sep 17 00:00:00 2001 From: Patryk Wychowaniec Date: Thu, 13 Aug 2020 14:33:24 +0200 Subject: [PATCH 2/2] #1 create entry in my memory translation service --- src/translate/MyMemoryTranslateService.js | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/src/translate/MyMemoryTranslateService.js b/src/translate/MyMemoryTranslateService.js index 607d2dc..e9e8fe6 100644 --- a/src/translate/MyMemoryTranslateService.js +++ b/src/translate/MyMemoryTranslateService.js @@ -1,6 +1,7 @@ const fetch = require('node-fetch'); const TranslateService = require('./TranslateService'); +const dictionaryService = require('../dictionaryService'); const API = 'https://api.mymemory.translated.net/get'; @@ -13,7 +14,11 @@ class MyMemoryTranslateService extends TranslateService { fetch(URL) .then(data => data.json()) - .then(data => resolve(data.responseData.translatedText)) + .then(data => { + const translatedText = data.responseData.translatedText; + this.log.addEntry(key, to, translatedText); + resolve(dictionaryService.createEntry(key, translatedText)); + }) .catch(err => reject(err)); }); }