@l10nmonster/helpers-lqaboss 3.0.0-alpha.7 → 3.0.0-alpha.9
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/index.js +1 -0
- package/lqabossProvider.js +18 -31
- package/lqabossTmStore.js +96 -0
- package/package.json +1 -1
package/index.js
CHANGED
package/lqabossProvider.js
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import JSZip from 'jszip';
|
|
2
|
-
import {
|
|
2
|
+
import { providers, logVerbose, styleString, opsManager } from '@l10nmonster/core';
|
|
3
3
|
|
|
4
4
|
/**
|
|
5
5
|
* @typedef {object} LQABossProviderOptions
|
|
@@ -22,47 +22,34 @@ export class LQABossProvider extends providers.BaseTranslationProvider {
|
|
|
22
22
|
super(options);
|
|
23
23
|
this.#storageDelegate = delegate;
|
|
24
24
|
this.#opNames.startReviewOp = `${this.id}.startReviewOp`;
|
|
25
|
-
this.#opNames.continueReviewOp = `${this.id}.continueReviewOp`;
|
|
26
|
-
this.#opNames.completeReviewOp = `${this.id}.completeReviewOp`;
|
|
27
25
|
opsManager.registerOp(this.startReviewOp.bind(this), { opName: this.#opNames.startReviewOp, idempotent: false });
|
|
28
|
-
opsManager.registerOp(this.continueReviewOp.bind(this), { opName: this.#opNames.continueReviewOp, idempotent: true });
|
|
29
|
-
opsManager.registerOp(this.completeReviewOp.bind(this), { opName: this.#opNames.completeReviewOp, idempotent: true });
|
|
30
26
|
}
|
|
31
27
|
|
|
32
28
|
createTask(job) {
|
|
33
29
|
logVerbose`LQABossProvider creating task for job ${job.jobGuid}`;
|
|
34
|
-
|
|
35
|
-
requestTranslationsTask.rootOp.enqueue(this.#opNames.startReviewOp, { job });
|
|
36
|
-
return requestTranslationsTask;
|
|
30
|
+
return opsManager.createTask(this.id, this.#opNames.startReviewOp, { job });
|
|
37
31
|
}
|
|
38
32
|
|
|
39
33
|
async startReviewOp(op) {
|
|
40
|
-
const {
|
|
34
|
+
const { tus, ...jobResponse } = op.args.job;
|
|
41
35
|
const zip = new JSZip();
|
|
42
|
-
zip.file('job.json', JSON.stringify(
|
|
36
|
+
zip.file('job.json', JSON.stringify({
|
|
37
|
+
...jobResponse,
|
|
38
|
+
tus: tus.map(tu => ({
|
|
39
|
+
rid: tu.rid,
|
|
40
|
+
sid: tu.sid,
|
|
41
|
+
guid: tu.guid,
|
|
42
|
+
nsrc: tu.nsrc,
|
|
43
|
+
notes: tu.notes,
|
|
44
|
+
ntgt: tu.ntgt,
|
|
45
|
+
q: this.quality,
|
|
46
|
+
})),
|
|
47
|
+
}, null, 2));
|
|
43
48
|
const buffer = await zip.generateAsync({ type: 'nodebuffer', compression: 'DEFLATE', compressionOptions: { level: 6 } });
|
|
44
|
-
const filename = `${
|
|
49
|
+
const filename = `${jobResponse.jobGuid}.lqaboss`;
|
|
45
50
|
await this.#storageDelegate.saveFile(filename, buffer);
|
|
46
|
-
logVerbose`LQABoss file ${filename} with ${
|
|
47
|
-
|
|
48
|
-
}
|
|
49
|
-
|
|
50
|
-
/**
|
|
51
|
-
* Fetches the completed review job. This will error out until the review is complete.
|
|
52
|
-
* @param {object} op - The operation context containing fetch parameters.
|
|
53
|
-
* @returns {Promise<*>} The job response.
|
|
54
|
-
*/
|
|
55
|
-
async continueReviewOp(op) {
|
|
56
|
-
const filename = `${op.args.jobGuid}.json`;
|
|
57
|
-
logVerbose`Trying to fetch completed LQABoss file ${filename}`;
|
|
58
|
-
return JSON.parse(await this.#storageDelegate.getFile(filename));
|
|
59
|
-
}
|
|
60
|
-
|
|
61
|
-
async completeReviewOp(op) {
|
|
62
|
-
const { tus, ...jobResponse } = op.inputs[1]; // the second op should be continueReviewOp
|
|
63
|
-
jobResponse.status = 'done';
|
|
64
|
-
const ts = getRegressionMode() ? 1 : new Date().getTime();
|
|
65
|
-
jobResponse.tus = tus.map(tu => ({ ...tu, ts, q: this.quality }));
|
|
51
|
+
logVerbose`Saved LQABoss file ${filename} with ${tus.length} guids and ${buffer.length} bytes`;
|
|
52
|
+
jobResponse.tus = []; // remove tus so that job is cancelled and won't be stored
|
|
66
53
|
return jobResponse;
|
|
67
54
|
}
|
|
68
55
|
|
|
@@ -0,0 +1,96 @@
|
|
|
1
|
+
import { logInfo, logVerbose, utils } from '@l10nmonster/core';
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* Adapter class to expose LQABoss completion files as a TM store.
|
|
5
|
+
*
|
|
6
|
+
* @class LQABossTmStore
|
|
7
|
+
*/
|
|
8
|
+
export class LQABossTmStore {
|
|
9
|
+
id;
|
|
10
|
+
#storageDelegate;
|
|
11
|
+
#tm;
|
|
12
|
+
|
|
13
|
+
get access() {
|
|
14
|
+
return 'readonly';
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
get partitioning() {
|
|
18
|
+
return 'job';
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
/**
|
|
22
|
+
* Creates an LQABossTmStore instance
|
|
23
|
+
* @param {Object} options - Configuration options
|
|
24
|
+
* @param {Object} options.delegate - Required file store delegate implementing file operations
|
|
25
|
+
* @param {string} options.id - Required unique identifier for the store
|
|
26
|
+
* @throws {Error} If no delegate or id is provided
|
|
27
|
+
*/
|
|
28
|
+
constructor(options) {
|
|
29
|
+
const { delegate, id } = options || {};
|
|
30
|
+
if (!delegate || !id) {
|
|
31
|
+
throw new Error('A delegate and an id are required to instantiate an LQABossTmStore');
|
|
32
|
+
}
|
|
33
|
+
this.#storageDelegate = delegate;
|
|
34
|
+
this.id = id;
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
async #getTM() {
|
|
38
|
+
if (!this.#tm) {
|
|
39
|
+
this.#tm = {};
|
|
40
|
+
const files = await this.#storageDelegate.listAllFiles();
|
|
41
|
+
for (const [ fileName ] of files) {
|
|
42
|
+
if (fileName.length === 26 && fileName.endsWith('.json')) {
|
|
43
|
+
const job = JSON.parse(await this.#storageDelegate.getFile(fileName));
|
|
44
|
+
const ts = job.updatedAt ? new Date(job.updatedAt).getTime() : new Date().getTime();
|
|
45
|
+
job.tus = job.tus.map(tu => ({ ...tu, ts }));
|
|
46
|
+
this.#tm[job.sourceLang] ??= {};
|
|
47
|
+
this.#tm[job.sourceLang][job.targetLang] ??= {};
|
|
48
|
+
this.#tm[job.sourceLang][job.targetLang][job.jobGuid] = job;
|
|
49
|
+
}
|
|
50
|
+
}
|
|
51
|
+
}
|
|
52
|
+
return this.#tm;
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
async getAvailableLangPairs() {
|
|
56
|
+
const tm = await this.#getTM();
|
|
57
|
+
const pairs = [];
|
|
58
|
+
for (const [ sourceLang, targets ] of Object.entries(tm)) {
|
|
59
|
+
for (const targetLang of Object.keys(targets)) {
|
|
60
|
+
pairs.push([ sourceLang, targetLang ]);
|
|
61
|
+
}
|
|
62
|
+
}
|
|
63
|
+
return pairs;
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
async listAllTmBlocks(sourceLang, targetLang) {
|
|
67
|
+
const tm = await this.#getTM();
|
|
68
|
+
const blocks = tm[sourceLang]?.[targetLang] ?? {};
|
|
69
|
+
return Object.entries(blocks).map(([ jobGuid, job ]) => [ jobGuid, job.updatedAt ]);
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
async *getTmBlocks(sourceLang, targetLang, blockIds) {
|
|
73
|
+
const tm = await this.#getTM();
|
|
74
|
+
const blocks = tm[sourceLang]?.[targetLang] ?? {};
|
|
75
|
+
for (const jobGuid of blockIds) {
|
|
76
|
+
const job = blocks[jobGuid];
|
|
77
|
+
if (job) {
|
|
78
|
+
yield* utils.getIteratorFromJobPair(job, job);
|
|
79
|
+
}
|
|
80
|
+
}
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
async getTOC(sourceLang, targetLang) {
|
|
84
|
+
const tm = await this.#getTM();
|
|
85
|
+
const blocks = tm[sourceLang]?.[targetLang] ?? {};
|
|
86
|
+
const toc = { v: 1, sourceLang, targetLang, blocks: {} };
|
|
87
|
+
for (const [ jobGuid, job ] of Object.entries(blocks)) {
|
|
88
|
+
toc.blocks[jobGuid] = { blockName: jobGuid, modified: job.updatedAt, jobs: [ [ jobGuid, job.updatedAt ] ] };
|
|
89
|
+
}
|
|
90
|
+
return toc;
|
|
91
|
+
}
|
|
92
|
+
|
|
93
|
+
async getWriter() {
|
|
94
|
+
throw new Error(`Cannot write to readonly TM Store: ${this.id}`);
|
|
95
|
+
}
|
|
96
|
+
}
|