@l10nmonster/helpers-lqaboss 3.0.0-alpha.8 → 3.1.0

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/.releaserc.json CHANGED
@@ -20,7 +20,7 @@
20
20
  },
21
21
  {
22
22
  "path": "@semantic-release/npm",
23
- "npmPublish": true
23
+ "npmPublish": false
24
24
  },
25
25
  {
26
26
  "path": "@semantic-release/git",
package/CHANGELOG.md CHANGED
@@ -1,3 +1,23 @@
1
+ # @l10nmonster/helpers-lqaboss [3.1.0](https://public-github/l10nmonster/l10nmonster/compare/@l10nmonster/helpers-lqaboss@3.0.0...@l10nmonster/helpers-lqaboss@3.1.0) (2025-12-20)
2
+
3
+
4
+ ### Bug Fixes
5
+
6
+ * Calibrate log severities ([2b3350a](https://public-github/l10nmonster/l10nmonster/commit/2b3350a3123abb91e7f91a9c1864daeb6275c3ad))
7
+ * **helpers-lqaboss:** Fix typo ([209dd07](https://public-github/l10nmonster/l10nmonster/commit/209dd071724c6f15464a1be0ebf140f23efc3f56))
8
+ * **helpers-lqaboss:** Move lqaboss ingestion to tm syncdown ([ebda63f](https://public-github/l10nmonster/l10nmonster/commit/ebda63f3b1651b44265ba62ce0f4ff876e9c97ed))
9
+ * **helpers-lqaboss:** Relax LQABossTmStore glob to allow for custom flow names ([5568c81](https://public-github/l10nmonster/l10nmonster/commit/5568c81d55f07c95d4af337904bc0367f5f71d68))
10
+ * **helpers-lqaboss:** Support old jobs without updatedAt property ([7c8cf75](https://public-github/l10nmonster/l10nmonster/commit/7c8cf759dfe9df379f537ef8a278b76949c19783))
11
+ * **lqaboss:** Support new response type ([ecaec02](https://public-github/l10nmonster/l10nmonster/commit/ecaec029b509d88267ac66374c5993c1537737c0))
12
+ * **server:** Fix cart cleanup ([9bbcab9](https://public-github/l10nmonster/l10nmonster/commit/9bbcab93e1fd20aeb09f59c828665159f091f37c))
13
+
14
+
15
+ ### Features
16
+
17
+ * **core:** Major refactor ([6992ee4](https://public-github/l10nmonster/l10nmonster/commit/6992ee4d74ad2e25afef6220f92f2e72dfd02457))
18
+ * Improve LQA Boss ([fcb0818](https://public-github/l10nmonster/l10nmonster/commit/fcb0818181f1a7bd46764596c9d2b8d8f362375c))
19
+ * **lqaboss:** Support for new Chrome extension ([dc6f86f](https://public-github/l10nmonster/l10nmonster/commit/dc6f86f417dde5e5942bdad2c81c0fbbac59fb80))
20
+
1
21
  # Changelog
2
22
 
3
23
  All notable changes to this project will be documented in this file.
package/flowCapture.js CHANGED
@@ -1,3 +1,4 @@
1
+ /* eslint-disable complexity */
1
2
  import JSZip from 'jszip';
2
3
  import puppeteer from 'puppeteer';
3
4
  import { logInfo, logVerbose } from '@l10nmonster/core';
@@ -24,7 +25,7 @@ async function extractTextAndMetadataInPageContext() {
24
25
 
25
26
  const textElements = [];
26
27
  const START_MARKER_REGEX = /(?<![''<])\u200B([\uFE00-\uFE0F]+)/g;
27
- const END_MARKER = '\u200B';
28
+ const END_MARKER = '\u200C';
28
29
 
29
30
  if (!document.body) {
30
31
  return { error: 'Document body not found.' };
@@ -221,12 +222,8 @@ export class FlowSnapshotter {
221
222
  const job = {
222
223
  sourceLang: tm.sourceLang,
223
224
  targetLang: tm.targetLang,
224
- tus: [],
225
+ tus: Object.values(await tm.getEntries(Array.from(guids))),
225
226
  };
226
- guids.forEach(guid => {
227
- const tu = tm.getEntryByGuid(guid);
228
- tu && job.tus.push(tu);
229
- });
230
227
  if (job.tus.length > 0) {
231
228
  zip.file('job.json', JSON.stringify(job, null, 2));
232
229
  }
package/index.js CHANGED
@@ -1,3 +1,4 @@
1
1
  export { LQABossActions } from './lqabossActions.js';
2
2
  export { LQABossProvider } from './lqabossProvider.js';
3
3
  export { LQABossTmStore } from './lqabossTmStore.js';
4
+ export { createLQABossRoutes } from './lqabossRoutes.js';
@@ -1,10 +1,14 @@
1
1
  import JSZip from 'jszip';
2
- import { providers, logVerbose, styleString, opsManager } from '@l10nmonster/core';
2
+ import path from 'path';
3
+ import { readFileSync } from 'fs';
4
+ import { providers, logVerbose, styleString, opsManager, getBaseDir } from '@l10nmonster/core';
3
5
 
4
6
  /**
5
7
  * @typedef {object} LQABossProviderOptions
6
8
  * @extends BaseTranslationProvider
7
9
  * @property {Object} delegate - Required file store delegate implementing file operations
10
+ * @property {string} [urlPrefix] - Prefix for the LQA Boss URL
11
+ * @property {string} [qualityFile] - Path to a quality model JSON file
8
12
  */
9
13
 
10
14
  /**
@@ -12,15 +16,19 @@ import { providers, logVerbose, styleString, opsManager } from '@l10nmonster/cor
12
16
  */
13
17
  export class LQABossProvider extends providers.BaseTranslationProvider {
14
18
  #storageDelegate;
19
+ urlPrefix;
20
+ #qualityFilePath;
15
21
  #opNames = {};
16
22
 
17
23
  /**
18
24
  * Initializes a new instance of the LQABossProvider class.
19
25
  * @param {LQABossProviderOptions} options - Configuration options for the provider.
20
26
  */
21
- constructor({ delegate, ...options }) {
27
+ constructor({ delegate, urlPrefix, qualityFile, ...options }) {
22
28
  super(options);
23
29
  this.#storageDelegate = delegate;
30
+ this.urlPrefix = urlPrefix;
31
+ qualityFile && (this.#qualityFilePath = path.resolve(getBaseDir(), qualityFile));
24
32
  this.#opNames.startReviewOp = `${this.id}.startReviewOp`;
25
33
  opsManager.registerOp(this.startReviewOp.bind(this), { opName: this.#opNames.startReviewOp, idempotent: false });
26
34
  }
@@ -31,26 +39,30 @@ export class LQABossProvider extends providers.BaseTranslationProvider {
31
39
  }
32
40
 
33
41
  async startReviewOp(op) {
34
- const { tus, ...jobResponse } = op.args.job;
42
+ const filename = op.args.job.jobName ?
43
+ `${op.args.job.jobName.replace(/\s+/g, '_')}-${op.args.job.jobGuid.substring(0, 5)}.lqaboss` :
44
+ `${op.args.job.jobGuid}.lqaboss`;
45
+ const jobRequest = {
46
+ ...op.args.job,
47
+ statusDescription: `Created LQA Boss file ${this.urlPrefix ? `at ${this.urlPrefix}/${filename}` : `: ${filename}`}`,
48
+ providerData: { quality: this.quality },
49
+ };
35
50
  const zip = new JSZip();
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));
51
+ zip.file('job.json', JSON.stringify(jobRequest, null, 2));
52
+
53
+ // Add quality model if configured
54
+ if (this.#qualityFilePath) {
55
+ const qualityModel = JSON.parse(readFileSync(this.#qualityFilePath, 'utf-8'));
56
+ zip.file('quality.json', JSON.stringify(qualityModel, null, 2));
57
+ }
58
+
48
59
  const buffer = await zip.generateAsync({ type: 'nodebuffer', compression: 'DEFLATE', compressionOptions: { level: 6 } });
49
- const filename = `${jobResponse.jobGuid}.lqaboss`;
50
60
  await this.#storageDelegate.saveFile(filename, buffer);
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
53
- return jobResponse;
61
+ logVerbose`Saved LQABoss file ${filename} with ${jobRequest.tus.length} guids and ${buffer.length} bytes`;
62
+ return {
63
+ ...jobRequest,
64
+ tus: [], // remove tus so that job is cancelled and won't be stored
65
+ };
54
66
  }
55
67
 
56
68
  async info() {
@@ -0,0 +1,28 @@
1
+ import { logInfo, logVerbose, logWarn } from '@l10nmonster/core';
2
+
3
+ export function createLQABossRoutes(mm) {
4
+ return [
5
+ ['post', '/lookup', async (req, res) => {
6
+ logInfo`LQABossRoute:/lookup`;
7
+ try {
8
+ const { sourceLang, targetLang, segments } = req.body;
9
+ const tm = mm.tmm.getTM(sourceLang, targetLang);
10
+ const guids = new Set(segments.map(segment => segment.g));
11
+ let tus = [];
12
+ if (guids.size > 0) {
13
+ tus = await tm.queryByGuids(Array.from(guids));
14
+ }
15
+ const guidMap = new Map(tus.map(tu => [ tu.guid, tu ]));
16
+ const results = segments.map(segment => guidMap.get(segment.g) ?? {});
17
+ logVerbose`Matched ${tus.length} segments out of ${guids.size}`;
18
+ res.json({ results });
19
+ } catch (error) {
20
+ logWarn`Error in LQABossRoute:/lookup: ${error.message}`;
21
+ res.status(500).json({
22
+ error: 'Failed to lookup translation memory',
23
+ message: error.message
24
+ });
25
+ }
26
+ }]
27
+ ]
28
+ }
package/lqabossTmStore.js CHANGED
@@ -1,4 +1,4 @@
1
- import { logInfo, logVerbose, utils } from '@l10nmonster/core';
1
+ import { utils } from '@l10nmonster/core';
2
2
 
3
3
  /**
4
4
  * Adapter class to expose LQABoss completion files as a TM store.
@@ -39,9 +39,13 @@ export class LQABossTmStore {
39
39
  this.#tm = {};
40
40
  const files = await this.#storageDelegate.listAllFiles();
41
41
  for (const [ fileName ] of files) {
42
- if (fileName.length === 26 && fileName.endsWith('.json')) {
42
+ if (fileName.endsWith('.json')) {
43
43
  const job = JSON.parse(await this.#storageDelegate.getFile(fileName));
44
- const ts = job.updatedAt ? new Date(job.updatedAt).getTime() : new Date().getTime();
44
+ if (!job.sourceLang || !job.targetLang || !job.jobGuid) {
45
+ continue;
46
+ }
47
+ !job.updatedAt && (job.updatedAt = '2025-08-29T21:29:36.269Z'); // workaround for old jobs that don't have an updatedAt
48
+ const ts = new Date(job.updatedAt).getTime();
45
49
  job.tus = job.tus.map(tu => ({ ...tu, ts }));
46
50
  this.#tm[job.sourceLang] ??= {};
47
51
  this.#tm[job.sourceLang][job.targetLang] ??= {};
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@l10nmonster/helpers-lqaboss",
3
- "version": "3.0.0-alpha.8",
3
+ "version": "3.1.0",
4
4
  "description": "LQA Boss helper for L10n Monster",
5
5
  "main": "index.js",
6
6
  "type": "module",
@@ -13,6 +13,6 @@
13
13
  "puppeteer": "^24"
14
14
  },
15
15
  "peerDependencies": {
16
- "@l10nmonster/core": "^3.0.0-alpha.0"
16
+ "@l10nmonster/core": "3.1.0"
17
17
  }
18
18
  }