@ddict/translate
Advanced tools
Comparing version 0.1.3 to 1.0.0
{ | ||
"name": "@ddict/translate", | ||
"version": "0.1.3", | ||
"version": "1.0.0", | ||
"description": "", | ||
@@ -8,3 +8,6 @@ "main": "src", | ||
"scripts": { | ||
"test": "ava src" | ||
"test": "ava src", | ||
"lint": "eslint . --ext .js --fix --ignore-path .gitignore", | ||
"format": "prettier --write src/", | ||
"format-check": "prettier --check src/" | ||
}, | ||
@@ -14,13 +17,9 @@ "repository": "git@github.com:ddict/translate.git", | ||
"license": "UNLICENSED", | ||
"dependencies": {}, | ||
"devDependencies": { | ||
"ava": "^4.0.1", | ||
"esm": "^3.2.25", | ||
"node-fetch": "^2.1.2" | ||
}, | ||
"ava": { | ||
"require": [ | ||
"esm" | ||
] | ||
"@rushstack/eslint-patch": "^1.10.3", | ||
"ava": "^6.1.3", | ||
"eslint": "^8.57.0", | ||
"node-fetch": "^3.3.2", | ||
"prettier": "^3.3.3" | ||
} | ||
} |
export default { | ||
getLanguages, | ||
translate, | ||
tts, | ||
getLanguages, | ||
translate, | ||
tts, | ||
reviseTranslation, | ||
} | ||
@@ -9,3 +10,2 @@ | ||
const DEFAULT_COUNTRY = 'US' | ||
const DEFAULT_LANG = 'en' | ||
@@ -21,47 +21,47 @@ const DEFAULT_ENCODING = 'UTF-8' | ||
function getLanguages(lang = 'en') { | ||
const endpoint = 'https://translate.google.com/translate_a/l' | ||
const qs = { | ||
hl: lang, | ||
client: 'it', | ||
oe: DEFAULT_ENCODING, | ||
ie: DEFAULT_ENCODING, | ||
} | ||
return { | ||
url: Helper.makeURL(endpoint, qs), | ||
method: 'GET', | ||
headers: { | ||
'User-Agent': USER_AGENT, | ||
}, | ||
} | ||
const endpoint = 'https://translate.google.com/translate_a/l' | ||
const qs = { | ||
hl: lang, | ||
client: 'it', | ||
oe: DEFAULT_ENCODING, | ||
ie: DEFAULT_ENCODING, | ||
} | ||
return { | ||
url: Helper.makeURL(endpoint, qs), | ||
method: 'GET', | ||
headers: { | ||
'User-Agent': USER_AGENT, | ||
}, | ||
} | ||
} | ||
function translate(lang = DEFAULT_LANG, question, src, target, simple = false) { | ||
if (question.length > MAX_LENGTH) { | ||
throw new Error(`Maximum text length exceeded: ${MAX_LENGTH}`) | ||
} | ||
if (question.length > MAX_LENGTH) { | ||
throw new Error(`Maximum text length exceeded: ${MAX_LENGTH}`) | ||
} | ||
const endpoint = 'https://translate.google.com/translate_a/single' | ||
const qs = { | ||
q: question, | ||
sl: src, | ||
tl: target, | ||
hl: lang, | ||
client: 'it', | ||
dt: ['t', 'rmt', 'bd', 'rms', 'qca', 'ss', 'md', 'ld', 'ex', 'rw'], | ||
otf: '2', // ? | ||
dj: '1', // json object instead of array | ||
ie: DEFAULT_ENCODING, | ||
oe: DEFAULT_ENCODING, | ||
} | ||
const endpoint = 'https://translate.google.com/translate_a/single' | ||
const qs = { | ||
q: question, | ||
sl: src, | ||
tl: target, | ||
hl: lang, | ||
client: 'it', | ||
dt: ['t', 'rmt', 'bd', 'rms', 'qca', 'ss', 'md', 'ld', 'ex', 'rw'], | ||
otf: '2', // ? | ||
dj: '1', // json object instead of array | ||
ie: DEFAULT_ENCODING, | ||
oe: DEFAULT_ENCODING, | ||
} | ||
// simple | ||
if (simple) qs.dt = 't' | ||
// simple | ||
if (simple) qs.dt = 't' | ||
return { | ||
url: Helper.makeURL(endpoint, qs), | ||
method: 'GET', | ||
headers: { | ||
'User-Agent': USER_AGENT, | ||
}, | ||
} | ||
return { | ||
url: Helper.makeURL(endpoint, qs), | ||
method: 'GET', | ||
headers: { | ||
'User-Agent': USER_AGENT, | ||
}, | ||
} | ||
} | ||
@@ -72,33 +72,101 @@ | ||
function tts(lang = DEFAULT_LANG, question, src = 'input', target) { | ||
// TODO: break by \n \r\n . , | ||
// TODO: break by \n \r\n . , | ||
// cap by MAX_TTS_LENGTH | ||
question = question.substring(0, MAX_TTS_LENGTH) | ||
// cap by MAX_TTS_LENGTH | ||
question = question.substring(0, MAX_TTS_LENGTH) | ||
// src can be 'input' or 'target' | ||
if (src !== 'input' && src !== 'target') { | ||
src = 'input' | ||
} | ||
// src can be 'input' or 'target' | ||
if (src !== 'input' && src !== 'target') { | ||
src = 'input' | ||
} | ||
const endpoint = 'https://translate.google.com/translate_tts' | ||
const qs = { | ||
q: question, | ||
tl: target, | ||
hl: lang, | ||
client: 'it', | ||
total: '1', // 1 or 2 | ||
idx: '0', | ||
textlen: question.length, | ||
prev: src, | ||
ie: DEFAULT_ENCODING, | ||
} | ||
const endpoint = 'https://translate.google.com/translate_tts' | ||
const qs = { | ||
q: question, | ||
tl: target, | ||
hl: lang, | ||
client: 'it', | ||
total: '1', // 1 or 2 | ||
idx: '0', | ||
textlen: question.length, | ||
prev: src, | ||
ie: DEFAULT_ENCODING, | ||
} | ||
return { | ||
url: Helper.makeURL(endpoint, qs), | ||
method: 'GET', | ||
headers: { | ||
'User-Agent': USER_AGENT, | ||
Connection: 'keep-alive', | ||
}, | ||
} | ||
} | ||
return { | ||
url: Helper.makeURL(endpoint, qs), | ||
method: 'GET', | ||
headers: { | ||
'User-Agent': USER_AGENT, | ||
Connection: 'keep-alive', | ||
}, | ||
} | ||
} | ||
function reviseTranslation(googleData, target, text) { | ||
if (!target || typeof target !== 'string' || target.length < 2) { | ||
throw new Error('Invalid input format: target code language') | ||
} | ||
if (!googleData || typeof googleData !== 'object') { | ||
throw new Error('Invalid input format: expected an object') | ||
} | ||
const { sentences, dict, src, definitions, examples, synsets } = googleData | ||
if (!sentences || !Array.isArray(sentences) || sentences.length === 0) { | ||
throw new Error('Invalid input format: missing or empty sentences array') | ||
} | ||
const ddictData = { | ||
service: 'google', | ||
src: src || '', | ||
target, | ||
text, | ||
phonetic: {}, | ||
result: sentences.map((sentence) => sentence.trans).join(''), | ||
dictionary: [], | ||
definitions: [], | ||
synonyms: [], | ||
examples: [], | ||
} | ||
// Set phonetic | ||
ddictData.phonetic.src = sentences.find((s) => s.src_translit)?.src_translit || '' | ||
// Set dictionary | ||
if (dict && Array.isArray(dict)) { | ||
ddictData.dictionary = dict.map((entry) => ({ | ||
pos: entry.pos || '', | ||
terms: entry.terms || [], | ||
items: entry.entry || [], | ||
})) | ||
} | ||
// Set definitions | ||
if (definitions && Array.isArray(definitions)) { | ||
ddictData.definitions = definitions.map((def) => ({ | ||
pos: def.pos || '', | ||
items: def.entry.map((item) => ({ | ||
gloss: item.gloss || '', | ||
example: item.example || '', | ||
})), | ||
})) | ||
} | ||
// Set examples | ||
if (examples && examples.example && Array.isArray(examples.example)) { | ||
ddictData.examples = examples.example.map((ex) => ex.text || '') | ||
} | ||
// Set synonyms | ||
if (synsets && Array.isArray(synsets)) { | ||
ddictData.synonyms = synsets.map((syn) => ({ | ||
pos: syn.pos || '', | ||
terms: syn.terms || [], | ||
items: syn.entry || [], | ||
})) | ||
} | ||
return ddictData | ||
} |
import test from 'ava' | ||
import { readFileSync } from 'fs' | ||
@@ -10,2 +11,3 @@ import Google from './index.js' | ||
const TEST_QUESTION = 'hello' | ||
const TEST_QUESTION_2 = 'hello world, my name is John' | ||
const TEST_SRC = 'en' | ||
@@ -16,74 +18,163 @@ const TEST_TARGET = 'vi' | ||
const agent = new https.Agent({ | ||
keepAlive: true, | ||
keepAlive: true, | ||
}) | ||
test('getLanguages', async (t) => { | ||
const rq = Google.getLanguages(TEST_LANG) | ||
const rq = Google.getLanguages(TEST_LANG) | ||
const res = await fetch(rq.url, { | ||
method: rq.method, | ||
headers: rq.headers, | ||
agent: agent, | ||
}) | ||
const res = await fetch(rq.url, { | ||
method: rq.method, | ||
headers: rq.headers, | ||
agent: agent, | ||
}) | ||
const json = await res.json() | ||
// console.log('getLanguages:', JSON.stringify(json, undefined, 2)) | ||
const json = await res.json() | ||
// console.log('getLanguages:', JSON.stringify(json, undefined, 2)) | ||
t.true(json.hasOwnProperty('sl')) | ||
t.true(json.hasOwnProperty('tl')) | ||
t.true(Object.prototype.hasOwnProperty.call(json, 'sl')) | ||
t.true(Object.prototype.hasOwnProperty.call(json, 'tl')) | ||
}) | ||
test('translate', async (t) => { | ||
const rq = Google.translate(TEST_LANG, TEST_QUESTION, TEST_SRC, TEST_TARGET) | ||
const rq = Google.translate(TEST_LANG, TEST_QUESTION, TEST_SRC, TEST_TARGET) | ||
const res = await fetch(rq.url, { | ||
method: rq.method, | ||
headers: rq.headers, | ||
agent: agent, | ||
}) | ||
const res = await fetch(rq.url, { | ||
method: rq.method, | ||
headers: rq.headers, | ||
agent: agent, | ||
}) | ||
const json = await res.json() | ||
// console.log('translate:', JSON.stringify(json, undefined, 2)) | ||
const json = await res.json() | ||
// console.log('translate:', JSON.stringify(json, undefined, 2)) | ||
t.true(json.hasOwnProperty('sentences')) | ||
t.true(json.hasOwnProperty('dict')) | ||
t.true(json.hasOwnProperty('src')) | ||
t.true(json.hasOwnProperty('definitions')) | ||
t.true(json.hasOwnProperty('examples')) | ||
// synsets | ||
// related_words | ||
t.true(Object.prototype.hasOwnProperty.call(json, 'sentences')) | ||
t.true(Object.prototype.hasOwnProperty.call(json, 'dict')) | ||
t.true(Object.prototype.hasOwnProperty.call(json, 'src')) | ||
t.true(Object.prototype.hasOwnProperty.call(json, 'definitions')) | ||
t.true(Object.prototype.hasOwnProperty.call(json, 'examples')) | ||
// synsets | ||
// related_words | ||
}) | ||
test('translate simple', async (t) => { | ||
const rq = Google.translate( | ||
TEST_LANG, | ||
TEST_QUESTION, | ||
TEST_SRC, | ||
TEST_TARGET, | ||
true | ||
) | ||
const rq = Google.translate(TEST_LANG, TEST_QUESTION, TEST_SRC, TEST_TARGET, true) | ||
const res = await fetch(rq.url, { | ||
method: rq.method, | ||
headers: rq.headers, | ||
agent: agent, | ||
}) | ||
const res = await fetch(rq.url, { | ||
method: rq.method, | ||
headers: rq.headers, | ||
agent: agent, | ||
}) | ||
const json = await res.json() | ||
const json = await res.json() | ||
t.true(json.hasOwnProperty('sentences')) | ||
t.true(json.hasOwnProperty('src')) | ||
t.true(Object.prototype.hasOwnProperty.call(json, 'sentences')) | ||
t.true(Object.prototype.hasOwnProperty.call(json, 'src')) | ||
}) | ||
test('tts', async (t) => { | ||
const rq = Google.tts(TEST_LANG, TEST_QUESTION, 'input', TEST_SRC) | ||
const rq = Google.tts(TEST_LANG, TEST_QUESTION, 'input', TEST_SRC) | ||
const res = await fetch(rq.url, { | ||
method: rq.method, | ||
headers: rq.headers, | ||
agent: agent, | ||
}) | ||
const res = await fetch(rq.url, { | ||
method: rq.method, | ||
headers: rq.headers, | ||
agent: agent, | ||
}) | ||
const data = await res.blob() | ||
t.is(data.type, 'audio/mpeg') | ||
}) | ||
const data = await res.blob() | ||
t.is(data.type, 'audio/mpeg') | ||
}) | ||
test('reviseTranslation-data-1', async (t) => { | ||
const sample = JSON.parse(readFileSync('src/sample-data/google-translate-test-1.json')) | ||
const expected = JSON.parse(readFileSync('src/sample-data/google-translate-expect-1.json')) | ||
const target = 'vi' | ||
const text = sample.sentences.map((sentence) => sentence.orig).join('') | ||
t.deepEqual(Google.reviseTranslation(sample, target, text), expected) | ||
}) | ||
test('reviseTranslation-data-2', async (t) => { | ||
const sample = JSON.parse(readFileSync('src/sample-data/google-translate-test-2.json')) | ||
const expected = JSON.parse(readFileSync('src/sample-data/google-translate-expect-2.json')) | ||
const target = 'vi' | ||
const text = sample.sentences.map((sentence) => sentence.orig).join('') | ||
t.deepEqual(Google.reviseTranslation(sample, target, text), expected) | ||
}) | ||
test('reviseTranslation-data-3', async (t) => { | ||
const sample = JSON.parse(readFileSync('src/sample-data/google-translate-test-3.json')) | ||
const expected = JSON.parse(readFileSync('src/sample-data/google-translate-expect-3.json')) | ||
const target = 'vi' | ||
const text = sample.sentences.map((sentence) => sentence.orig).join('') | ||
t.deepEqual(Google.reviseTranslation(sample, target, text), expected) | ||
}) | ||
test('throw an error for invalid input', (t) => { | ||
t.throws(() => Google.reviseTranslation(null, null), { | ||
message: 'Invalid input format: target code language', | ||
}) | ||
t.throws(() => Google.reviseTranslation(null, 'vi'), { | ||
message: 'Invalid input format: expected an object', | ||
}) | ||
t.throws(() => Google.reviseTranslation({}, 'vi'), { | ||
message: 'Invalid input format: missing or empty sentences array', | ||
}) | ||
}) | ||
test('handle missing optional fields', (t) => { | ||
const minimalGoogleData = { | ||
sentences: [{ orig: 'test', trans: 'kiểm tra' }], | ||
src: 'en', | ||
} | ||
const target = 've' | ||
const text = 'test' | ||
const expected = { | ||
src: 'en', | ||
target, | ||
text, | ||
phonetic: { | ||
src: '', | ||
}, | ||
result: 'kiểm tra', | ||
dictionary: [], | ||
definitions: [], | ||
synonyms: [], | ||
examples: [], | ||
} | ||
t.deepEqual(Google.reviseTranslation(minimalGoogleData, target, text), expected) | ||
}) | ||
test('reviseTranslation from GG-Translate real-1', async (t) => { | ||
const rq = Google.translate(TEST_LANG, TEST_QUESTION, TEST_SRC, TEST_TARGET) | ||
const res = await fetch(rq.url, { | ||
method: rq.method, | ||
headers: rq.headers, | ||
agent: agent, | ||
}) | ||
const sample = await res.json() | ||
const expected = JSON.parse(readFileSync('src/sample-data/google-translate-expect-real-1.json')) | ||
t.deepEqual(Google.reviseTranslation(sample, TEST_TARGET, TEST_QUESTION), expected) | ||
}) | ||
test('reviseTranslation from GG-Translate real-2', async (t) => { | ||
const rq = Google.translate(TEST_LANG, TEST_QUESTION_2, TEST_SRC, TEST_TARGET) | ||
const res = await fetch(rq.url, { | ||
method: rq.method, | ||
headers: rq.headers, | ||
agent: agent, | ||
}) | ||
const sample = await res.json() | ||
const expected = JSON.parse(readFileSync('src/sample-data/google-translate-expect-real-2.json')) | ||
t.deepEqual(Google.reviseTranslation(sample, TEST_TARGET, TEST_QUESTION_2), expected) | ||
}) |
export default { makeURL, qs } | ||
function makeURL(endpoint = '', query = {}) { | ||
return `${endpoint}?${qs(query)}` | ||
return `${endpoint}?${qs(query)}` | ||
} | ||
function qs(query = {}) { | ||
const nonArray = {} | ||
const array = {} | ||
let str = '' | ||
const nonArray = {} | ||
const array = {} | ||
let str = '' | ||
for (const key in query) { | ||
const val = query[key] | ||
if (!Array.isArray(val)) { | ||
nonArray[key] = val | ||
continue | ||
} | ||
array[key] = val | ||
for (const key in query) { | ||
const val = query[key] | ||
if (!Array.isArray(val)) { | ||
nonArray[key] = val | ||
continue | ||
} | ||
str = new URLSearchParams(nonArray).toString() | ||
array[key] = val | ||
} | ||
for (const key in array) { | ||
str += '&' | ||
str = new URLSearchParams(nonArray).toString() | ||
const val = query[key] | ||
const arr = [] | ||
for (const key in array) { | ||
str += '&' | ||
for (const ele of val) { | ||
arr.push(new URLSearchParams({ [key]: ele }).toString()) | ||
} | ||
const val = query[key] | ||
const arr = [] | ||
str += arr.join('&') | ||
for (const ele of val) { | ||
arr.push(new URLSearchParams({ [key]: ele }).toString()) | ||
} | ||
return str | ||
str += arr.join('&') | ||
} | ||
return str | ||
} |
@@ -6,21 +6,20 @@ import test from 'ava' | ||
test('qs', (t) => { | ||
const qs = Helper.qs({a: 1, b: 2}) | ||
t.is(qs, 'a=1&b=2') | ||
const qs = Helper.qs({ a: 1, b: 2 }) | ||
t.is(qs, 'a=1&b=2') | ||
}) | ||
test('qs:word', (t) => { | ||
const qs = Helper.qs({a: 1, b: 'hello world'}) | ||
t.is(qs, 'a=1&b=hello+world') | ||
const qs = Helper.qs({ a: 1, b: 'hello world' }) | ||
t.is(qs, 'a=1&b=hello+world') | ||
}) | ||
test('qs:array', (t) => { | ||
const qs = Helper.qs({a: 1, b: [2, 3]}) | ||
t.is(qs, 'a=1&b=2&b=3') | ||
const qs = Helper.qs({ a: 1, b: [2, 3] }) | ||
t.is(qs, 'a=1&b=2&b=3') | ||
}) | ||
test('qs:arrayWord', (t) => { | ||
const qs = Helper.qs({a: 'hello world', b: ['ok then', 'i\'m fine']}) | ||
console.log(qs) | ||
t.is(qs, 'a=hello+world&b=ok+then&b=i%27m+fine') | ||
const qs = Helper.qs({ a: 'hello world', b: ['ok then', "i'm fine"] }) | ||
console.log(qs) | ||
t.is(qs, 'a=hello+world&b=ok+then&b=i%27m+fine') | ||
}) |
import google from './google' | ||
export default { | ||
google, | ||
} | ||
google, | ||
} |
License Policy Violation
LicenseThis package is not allowed per your license policy. Review the package's license to ensure compliance.
Found 1 instance in 1 package
Major refactor
Supply chain riskPackage has recently undergone a major refactor. It may be unstable or indicate significant internal changes. Use caution when updating to versions that include significant changes.
Found 1 instance in 1 package
Filesystem access
Supply chain riskAccesses the file system, and could potentially read sensitive data.
Found 1 instance in 1 package
License Policy Violation
LicenseThis package is not allowed per your license policy. Review the package's license to ensure compliance.
Found 1 instance in 1 package
No v1
QualityPackage is not semver >=1. This means it is not stable and does not support ^ ranges.
Found 1 instance in 1 package
46581
19
1435
1
5
2
8