hybridtm 0.8.0 → 0.9.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/README.md +3 -5
- package/dist/backupHandler.d.ts +1 -1
- package/dist/backupHandler.js +1 -1
- package/dist/backupReader.d.ts +1 -1
- package/dist/backupReader.js +1 -1
- package/dist/batchImporter.d.ts +1 -1
- package/dist/batchImporter.js +1 -1
- package/dist/cli/backupCommand.d.ts +5 -3
- package/dist/cli/backupCommand.js +33 -31
- package/dist/cli/backupCommand.js.map +1 -1
- package/dist/cli/cliUtils.d.ts +2 -1
- package/dist/cli/cliUtils.js +13 -1
- package/dist/cli/cliUtils.js.map +1 -1
- package/dist/cli/createCommand.d.ts +5 -3
- package/dist/cli/createCommand.js +33 -31
- package/dist/cli/createCommand.js.map +1 -1
- package/dist/cli/hybridtmCli.d.ts +1 -1
- package/dist/cli/hybridtmCli.js +24 -14
- package/dist/cli/hybridtmCli.js.map +1 -1
- package/dist/cli/importCommand.d.ts +11 -3
- package/dist/cli/importCommand.js +75 -76
- package/dist/cli/importCommand.js.map +1 -1
- package/dist/cli/matchCommand.d.ts +17 -3
- package/dist/cli/matchCommand.js +88 -234
- package/dist/cli/matchCommand.js.map +1 -1
- package/dist/cli/registryCommands.d.ts +8 -5
- package/dist/cli/registryCommands.js +63 -61
- package/dist/cli/registryCommands.js.map +1 -1
- package/dist/cli/restoreCommand.d.ts +8 -3
- package/dist/cli/restoreCommand.js +80 -78
- package/dist/cli/restoreCommand.js.map +1 -1
- package/dist/cli/serveCommand.d.ts +17 -0
- package/dist/cli/serveCommand.js +87 -0
- package/dist/cli/serveCommand.js.map +1 -0
- package/dist/cli/stopCommand.d.ts +15 -0
- package/dist/cli/stopCommand.js +73 -0
- package/dist/cli/stopCommand.js.map +1 -0
- package/dist/hybridtm.d.ts +1 -1
- package/dist/hybridtm.js +48 -24
- package/dist/hybridtm.js.map +1 -1
- package/dist/hybridtmFactory.d.ts +1 -1
- package/dist/hybridtmFactory.js +6 -1
- package/dist/hybridtmFactory.js.map +1 -1
- package/dist/importOptions.d.ts +5 -4
- package/dist/importOptions.js +11 -10
- package/dist/importOptions.js.map +1 -1
- package/dist/index.d.ts +2 -1
- package/dist/index.js +2 -1
- package/dist/index.js.map +1 -1
- package/dist/langEntry.d.ts +1 -1
- package/dist/langEntry.js +1 -1
- package/dist/match.d.ts +1 -1
- package/dist/match.js +1 -1
- package/dist/matchQuality.d.ts +1 -1
- package/dist/matchQuality.js +1 -1
- package/dist/pendingEntry.d.ts +1 -1
- package/dist/pendingEntry.js +1 -1
- package/dist/searchFilters.d.ts +1 -1
- package/dist/searchFilters.js +1 -1
- package/dist/server/hybridtmServer.d.ts +40 -0
- package/dist/server/hybridtmServer.js +527 -0
- package/dist/server/hybridtmServer.js.map +1 -0
- package/dist/server/hybridtmServerMain.d.ts +12 -0
- package/dist/server/hybridtmServerMain.js +18 -0
- package/dist/server/hybridtmServerMain.js.map +1 -0
- package/dist/tmxHandler.d.ts +1 -1
- package/dist/tmxHandler.js +5 -5
- package/dist/tmxHandler.js.map +1 -1
- package/dist/tmxReader.d.ts +1 -1
- package/dist/tmxReader.js +4 -4
- package/dist/tmxReader.js.map +1 -1
- package/dist/utils.d.ts +26 -2
- package/dist/utils.js +153 -2
- package/dist/utils.js.map +1 -1
- package/dist/xliffHandler.d.ts +1 -1
- package/dist/xliffHandler.js +8 -8
- package/dist/xliffHandler.js.map +1 -1
- package/dist/xliffReader.d.ts +1 -1
- package/dist/xliffReader.js +4 -4
- package/dist/xliffReader.js.map +1 -1
- package/package.json +1 -1
|
@@ -2,7 +2,7 @@
|
|
|
2
2
|
* Copyright (c) 2025-2026 Maxprograms.
|
|
3
3
|
*
|
|
4
4
|
* This program and the accompanying materials
|
|
5
|
-
* are made available under the terms of the Eclipse
|
|
5
|
+
* are made available under the terms of the Eclipse Public License v1.0
|
|
6
6
|
* which accompanies this distribution, and is available at
|
|
7
7
|
* https://www.eclipse.org/org/documents/epl-v10.html
|
|
8
8
|
*
|
|
@@ -12,92 +12,91 @@
|
|
|
12
12
|
import { extname } from "node:path";
|
|
13
13
|
import { HybridTMFactory } from '../index.js';
|
|
14
14
|
import { CliUtils } from './cliUtils.js';
|
|
15
|
-
export
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
}
|
|
27
|
-
export async function runImportCommand(args) {
|
|
28
|
-
if (CliUtils.hasFlag(args, '-help')) {
|
|
29
|
-
usage();
|
|
30
|
-
return;
|
|
31
|
-
}
|
|
32
|
-
const name = CliUtils.getFlag(args, '-name');
|
|
33
|
-
const rawFile = CliUtils.getFlag(args, '-file');
|
|
34
|
-
if (!name || !rawFile) {
|
|
35
|
-
usage();
|
|
36
|
-
CliUtils.fail('Missing required -name or -file.');
|
|
15
|
+
export class ImportCommand {
|
|
16
|
+
static usage() {
|
|
17
|
+
console.log('Usage: hybridtm import -name <name> -file <path> [-type xliff|tmx|sdltm]');
|
|
18
|
+
console.log(' [-minState initial|translated|reviewed|final]');
|
|
19
|
+
console.log(' [-noMetadata]');
|
|
20
|
+
console.log();
|
|
21
|
+
console.log(' -name Instance to import into (required)');
|
|
22
|
+
console.log(' -file File to import (required)');
|
|
23
|
+
console.log(' -type Import format; inferred from the file extension when omitted');
|
|
24
|
+
console.log(' -minState Minimum segment state to import (default: translated)');
|
|
25
|
+
console.log(' -noMetadata Skip extracting notes/metadata/extension attributes');
|
|
37
26
|
}
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
CliUtils.
|
|
27
|
+
static async run(args) {
|
|
28
|
+
if (CliUtils.hasFlag(args, '-help')) {
|
|
29
|
+
ImportCommand.usage();
|
|
30
|
+
return;
|
|
31
|
+
}
|
|
32
|
+
const name = CliUtils.getFlag(args, '-name');
|
|
33
|
+
const rawFile = CliUtils.getFlag(args, '-file');
|
|
34
|
+
if (!name || !rawFile) {
|
|
35
|
+
ImportCommand.usage();
|
|
36
|
+
CliUtils.fail('Missing required -name or -file.');
|
|
37
|
+
}
|
|
38
|
+
const filePath = CliUtils.requireExistingFile(rawFile, 'Import file');
|
|
39
|
+
const options = ImportCommand.buildImportOptions(args);
|
|
40
|
+
const tm = HybridTMFactory.getInstance(name);
|
|
41
|
+
if (!tm) {
|
|
42
|
+
CliUtils.fail('No instance named "' + name + '". Run "hybridtm create" or "hybridtm list" first.');
|
|
43
|
+
}
|
|
44
|
+
try {
|
|
45
|
+
const type = ImportCommand.resolveImportType(CliUtils.getFlag(args, '-type'), filePath);
|
|
46
|
+
const count = await ImportCommand.importFile(tm, filePath, type, options);
|
|
47
|
+
console.log('Imported ' + count + ' entries from ' + filePath + ' into "' + name + '".');
|
|
48
|
+
}
|
|
49
|
+
catch (error) {
|
|
50
|
+
CliUtils.fail(error instanceof Error ? error.message : String(error));
|
|
51
|
+
}
|
|
52
|
+
finally {
|
|
53
|
+
await tm.close();
|
|
54
|
+
}
|
|
44
55
|
}
|
|
45
|
-
|
|
46
|
-
let count;
|
|
56
|
+
static async importFile(tm, filePath, type, options) {
|
|
47
57
|
switch (type) {
|
|
48
58
|
case 'xliff':
|
|
49
|
-
|
|
50
|
-
break;
|
|
59
|
+
return await tm.importXLIFF(filePath, options);
|
|
51
60
|
case 'tmx':
|
|
52
|
-
|
|
53
|
-
break;
|
|
61
|
+
return await tm.importTMX(filePath, options);
|
|
54
62
|
case 'sdltm':
|
|
55
|
-
|
|
56
|
-
break;
|
|
63
|
+
return await tm.importSDLTM(filePath, options);
|
|
57
64
|
}
|
|
58
|
-
console.log('Imported ' + count + ' entries from ' + filePath + ' into "' + name + '".');
|
|
59
|
-
}
|
|
60
|
-
catch (error) {
|
|
61
|
-
CliUtils.fail(error instanceof Error ? error.message : String(error));
|
|
62
65
|
}
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
}
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
66
|
+
static resolveImportType(explicit, filePath) {
|
|
67
|
+
if (explicit === 'xliff' || explicit === 'tmx' || explicit === 'sdltm') {
|
|
68
|
+
return explicit;
|
|
69
|
+
}
|
|
70
|
+
if (explicit !== undefined) {
|
|
71
|
+
throw new Error('Unknown import type "' + explicit + '". Expected xliff, tmx, or sdltm.');
|
|
72
|
+
}
|
|
73
|
+
const ext = extname(filePath).toLowerCase();
|
|
74
|
+
if (ext === '.xlf' || ext === '.xliff') {
|
|
75
|
+
return 'xliff';
|
|
76
|
+
}
|
|
77
|
+
if (ext === '.tmx') {
|
|
78
|
+
return 'tmx';
|
|
79
|
+
}
|
|
80
|
+
if (ext === '.sdltm') {
|
|
81
|
+
return 'sdltm';
|
|
82
|
+
}
|
|
83
|
+
throw new Error('Could not infer import type from "' + filePath + '". Pass an explicit type.');
|
|
80
84
|
}
|
|
81
|
-
|
|
82
|
-
return '
|
|
85
|
+
static isTranslationState(value) {
|
|
86
|
+
return value === 'initial' || value === 'translated' || value === 'reviewed' || value === 'final';
|
|
83
87
|
}
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
CliUtils.fail('Unknown -minState value "' + minState + '". Expected initial, translated, reviewed, or final.');
|
|
88
|
+
static buildImportOptions(args) {
|
|
89
|
+
const options = {
|
|
90
|
+
extractMetadata: !CliUtils.hasFlag(args, '-noMetadata')
|
|
91
|
+
};
|
|
92
|
+
const minState = CliUtils.getFlag(args, '-minState');
|
|
93
|
+
if (minState !== undefined) {
|
|
94
|
+
if (!ImportCommand.isTranslationState(minState)) {
|
|
95
|
+
CliUtils.fail('Unknown -minState value "' + minState + '". Expected initial, translated, reviewed, or final.');
|
|
96
|
+
}
|
|
97
|
+
options.minState = minState;
|
|
95
98
|
}
|
|
96
|
-
options
|
|
99
|
+
return options;
|
|
97
100
|
}
|
|
98
|
-
return options;
|
|
99
|
-
}
|
|
100
|
-
function isTranslationState(value) {
|
|
101
|
-
return value === 'initial' || value === 'translated' || value === 'reviewed' || value === 'final';
|
|
102
101
|
}
|
|
103
102
|
//# sourceMappingURL=importCommand.js.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"importCommand.js","sourceRoot":"","sources":["../../ts/cli/importCommand.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;iFAUiF;AAEjF,OAAO,EAAE,OAAO,EAAE,MAAM,WAAW,CAAC;AACpC,OAAO,EAAY,eAAe,EAAmC,MAAM,aAAa,CAAC;AACzF,OAAO,EAAE,QAAQ,EAAE,MAAM,eAAe,CAAC;AAIzC,MAAM,
|
|
1
|
+
{"version":3,"file":"importCommand.js","sourceRoot":"","sources":["../../ts/cli/importCommand.ts"],"names":[],"mappings":"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"}
|
|
@@ -2,12 +2,26 @@
|
|
|
2
2
|
* Copyright (c) 2025-2026 Maxprograms.
|
|
3
3
|
*
|
|
4
4
|
* This program and the accompanying materials
|
|
5
|
-
* are made available under the terms of the Eclipse
|
|
5
|
+
* are made available under the terms of the Eclipse Public License v1.0
|
|
6
6
|
* which accompanies this distribution, and is available at
|
|
7
7
|
* https://www.eclipse.org/org/documents/epl-v10.html
|
|
8
8
|
*
|
|
9
9
|
* Contributors:
|
|
10
10
|
* Maxprograms - initial API and implementation
|
|
11
11
|
*******************************************************************************/
|
|
12
|
-
|
|
13
|
-
export
|
|
12
|
+
import { HybridTM } from '../index.js';
|
|
13
|
+
export interface MatchResult {
|
|
14
|
+
segmentsProcessed: number;
|
|
15
|
+
segmentsWithMatches: number;
|
|
16
|
+
totalMatches: number;
|
|
17
|
+
outputPath: string;
|
|
18
|
+
}
|
|
19
|
+
export declare class MatchCommand {
|
|
20
|
+
static usage(): void;
|
|
21
|
+
static run(args: string[]): Promise<void>;
|
|
22
|
+
static matchXliffFile(tm: HybridTM, filePath: string, outputPath: string, similarity: number, limit?: number): Promise<MatchResult>;
|
|
23
|
+
static resolveOutputPath(explicit: string | undefined, inputPath: string): string;
|
|
24
|
+
private static collectUnits;
|
|
25
|
+
private static declareMatchesNamespace;
|
|
26
|
+
private static parseIntOption;
|
|
27
|
+
}
|
package/dist/cli/matchCommand.js
CHANGED
|
@@ -2,7 +2,7 @@
|
|
|
2
2
|
* Copyright (c) 2025-2026 Maxprograms.
|
|
3
3
|
*
|
|
4
4
|
* This program and the accompanying materials
|
|
5
|
-
* are made available under the terms of the Eclipse
|
|
5
|
+
* are made available under the terms of the Eclipse Public License v1.0
|
|
6
6
|
* which accompanies this distribution, and is available at
|
|
7
7
|
* https://www.eclipse.org/org/documents/epl-v10.html
|
|
8
8
|
*
|
|
@@ -10,68 +10,75 @@
|
|
|
10
10
|
* Maxprograms - initial API and implementation
|
|
11
11
|
*******************************************************************************/
|
|
12
12
|
import { basename, dirname, extname, join } from "node:path";
|
|
13
|
-
import {
|
|
14
|
-
import { TextNode, XMLElement } from "typesxml";
|
|
13
|
+
import { XliffGroup, XliffMatches, XliffParser, XliffSegment } from 'typesxliff';
|
|
15
14
|
import { HybridTMFactory, Utils } from '../index.js';
|
|
16
15
|
import { CliUtils } from './cliUtils.js';
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
CliUtils.
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
16
|
+
export class MatchCommand {
|
|
17
|
+
static usage() {
|
|
18
|
+
console.log('Usage: hybridtm match -name <name> -file <path> -similarity N [-output <path>]');
|
|
19
|
+
console.log(' [-limit N]');
|
|
20
|
+
console.log();
|
|
21
|
+
console.log(' -name TM instance to search against (required)');
|
|
22
|
+
console.log(' -file XLIFF file to enrich with match candidates (required)');
|
|
23
|
+
console.log(' -similarity Minimum hybrid match score 0-100 (required)');
|
|
24
|
+
console.log(' -output Output path (default: <file-without-ext>.matches.xlf)');
|
|
25
|
+
console.log(' -limit Max candidates per segment (default: HybridTM.semanticTranslationSearch\'s own default)');
|
|
26
|
+
console.log();
|
|
27
|
+
}
|
|
28
|
+
static async run(args) {
|
|
29
|
+
if (CliUtils.hasFlag(args, '-help')) {
|
|
30
|
+
MatchCommand.usage();
|
|
31
|
+
return;
|
|
32
|
+
}
|
|
33
|
+
const name = CliUtils.getFlag(args, '-name');
|
|
34
|
+
const rawFile = CliUtils.getFlag(args, '-file');
|
|
35
|
+
const rawSimilarity = CliUtils.getFlag(args, '-similarity');
|
|
36
|
+
if (!name || !rawFile || !rawSimilarity) {
|
|
37
|
+
MatchCommand.usage();
|
|
38
|
+
CliUtils.fail('Missing required -name, -file, or -similarity.');
|
|
39
|
+
}
|
|
40
|
+
const filePath = CliUtils.requireExistingFile(rawFile, 'XLIFF file');
|
|
41
|
+
const rawLimit = CliUtils.getFlag(args, '-limit');
|
|
42
|
+
const limit = rawLimit !== undefined ? MatchCommand.parseIntOption(rawLimit, '-limit', 1) : undefined;
|
|
43
|
+
const similarity = MatchCommand.parseIntOption(rawSimilarity, '-similarity', 0, 100);
|
|
44
|
+
const outputPath = MatchCommand.resolveOutputPath(CliUtils.getFlag(args, '-output'), filePath);
|
|
45
|
+
const tm = HybridTMFactory.getInstance(name);
|
|
46
|
+
if (!tm) {
|
|
47
|
+
CliUtils.fail('No instance named "' + name + '". Run "hybridtm create" or "hybridtm list" first.');
|
|
48
|
+
}
|
|
49
|
+
try {
|
|
50
|
+
const result = await MatchCommand.matchXliffFile(tm, filePath, outputPath, similarity, limit);
|
|
51
|
+
console.log('Segments processed: ' + result.segmentsProcessed);
|
|
52
|
+
console.log('Segments with matches: ' + result.segmentsWithMatches);
|
|
53
|
+
console.log('Total match candidates: ' + result.totalMatches);
|
|
54
|
+
console.log('Output: ' + result.outputPath);
|
|
55
|
+
}
|
|
56
|
+
catch (error) {
|
|
57
|
+
CliUtils.fail(error instanceof Error ? error.message : String(error));
|
|
58
|
+
}
|
|
59
|
+
finally {
|
|
60
|
+
await tm.close();
|
|
61
|
+
}
|
|
55
62
|
}
|
|
56
|
-
|
|
63
|
+
static async matchXliffFile(tm, filePath, outputPath, similarity, limit) {
|
|
57
64
|
const parser = new XliffParser();
|
|
58
65
|
parser.parseFile(filePath);
|
|
59
66
|
const document = parser.getXliffDocument();
|
|
60
67
|
if (!document) {
|
|
61
|
-
|
|
68
|
+
throw new Error('Unable to parse "' + filePath + '".');
|
|
62
69
|
}
|
|
63
70
|
const rawSrcLang = document.getSrcLang();
|
|
64
71
|
const srcLang = Utils.normalizeLanguage(rawSrcLang);
|
|
65
72
|
if (!srcLang) {
|
|
66
|
-
|
|
73
|
+
throw new Error('"' + filePath + '" has an invalid @srcLang value "' + rawSrcLang + '".');
|
|
67
74
|
}
|
|
68
75
|
const rawTgtLang = document.getTrgLang();
|
|
69
76
|
if (!rawTgtLang) {
|
|
70
|
-
|
|
77
|
+
throw new Error('"' + filePath + '" is missing @trgLang; nothing to match against.');
|
|
71
78
|
}
|
|
72
79
|
const tgtLang = Utils.normalizeLanguage(rawTgtLang);
|
|
73
80
|
if (!tgtLang) {
|
|
74
|
-
|
|
81
|
+
throw new Error('"' + filePath + '" has an invalid @trgLang value "' + rawTgtLang + '".');
|
|
75
82
|
}
|
|
76
83
|
let segmentsProcessed = 0;
|
|
77
84
|
let segmentsWithMatches = 0;
|
|
@@ -79,7 +86,7 @@ export async function runMatchCommand(args) {
|
|
|
79
86
|
let usedModule = false;
|
|
80
87
|
for (const file of document.getFiles()) {
|
|
81
88
|
const fileId = file.getId();
|
|
82
|
-
for (const unit of collectUnits(file.getEntries())) {
|
|
89
|
+
for (const unit of MatchCommand.collectUnits(file.getEntries())) {
|
|
83
90
|
const unitMatches = [];
|
|
84
91
|
const unitId = unit.getId();
|
|
85
92
|
// shared so <ph>/<data> ids stay unique across the whole unit
|
|
@@ -88,27 +95,27 @@ export async function runMatchCommand(args) {
|
|
|
88
95
|
if (!(item instanceof XliffSegment)) {
|
|
89
96
|
continue;
|
|
90
97
|
}
|
|
91
|
-
if (
|
|
98
|
+
if (item.getState() === 'final') {
|
|
92
99
|
continue;
|
|
93
100
|
}
|
|
94
101
|
const source = item.getSource();
|
|
95
|
-
const pureSource = source ? getPureText(source.
|
|
102
|
+
const pureSource = source ? Utils.getPureText(source.toElement()) : '';
|
|
96
103
|
if (pureSource.trim().length === 0) {
|
|
97
104
|
continue;
|
|
98
105
|
}
|
|
99
106
|
segmentsProcessed++;
|
|
100
|
-
const results = dedupeMatches(await tm.semanticTranslationSearch(pureSource, srcLang, tgtLang, similarity, limit));
|
|
107
|
+
const results = Utils.dedupeMatches(await tm.semanticTranslationSearch(pureSource, srcLang, tgtLang, similarity, limit));
|
|
101
108
|
if (results.length === 0) {
|
|
102
109
|
continue;
|
|
103
110
|
}
|
|
104
111
|
segmentsWithMatches++;
|
|
105
112
|
totalMatches += results.length;
|
|
106
|
-
const ref =
|
|
107
|
-
results.forEach((match) => unitMatches.push(buildXliffMatch(ref, match, dataCounter)));
|
|
113
|
+
const ref = item.getId() ? '#' + item.getId() : '#/f=' + fileId + '/u=' + unitId;
|
|
114
|
+
results.forEach((match) => unitMatches.push(Utils.buildXliffMatch(ref, match, dataCounter)));
|
|
108
115
|
}
|
|
109
116
|
if (unitMatches.length > 0) {
|
|
110
117
|
const matches = new XliffMatches();
|
|
111
|
-
matches.setNamespacePrefix(
|
|
118
|
+
matches.setNamespacePrefix(Utils.MTC_PREFIX);
|
|
112
119
|
unitMatches.forEach((match) => matches.addMatch(match));
|
|
113
120
|
unit.setMatches(matches);
|
|
114
121
|
usedModule = true;
|
|
@@ -116,196 +123,43 @@ export async function runMatchCommand(args) {
|
|
|
116
123
|
}
|
|
117
124
|
}
|
|
118
125
|
if (usedModule) {
|
|
119
|
-
declareMatchesNamespace(document);
|
|
126
|
+
MatchCommand.declareMatchesNamespace(document);
|
|
120
127
|
}
|
|
121
128
|
document.writeDocument(outputPath, true);
|
|
122
|
-
|
|
123
|
-
console.log('Segments with matches: ' + segmentsWithMatches);
|
|
124
|
-
console.log('Total match candidates: ' + totalMatches);
|
|
125
|
-
console.log('Output: ' + outputPath);
|
|
126
|
-
}
|
|
127
|
-
catch (error) {
|
|
128
|
-
CliUtils.fail(error instanceof Error ? error.message : String(error));
|
|
129
|
+
return { segmentsProcessed, segmentsWithMatches, totalMatches, outputPath };
|
|
129
130
|
}
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
}
|
|
134
|
-
function dedupeMatches(matches) {
|
|
135
|
-
const seen = new Set();
|
|
136
|
-
return matches.filter((match) => {
|
|
137
|
-
const key = extractElementText(match.source).trim() + '\u00A0' + extractElementText(match.target).trim();
|
|
138
|
-
if (seen.has(key)) {
|
|
139
|
-
return false;
|
|
131
|
+
static resolveOutputPath(explicit, inputPath) {
|
|
132
|
+
if (explicit) {
|
|
133
|
+
return CliUtils.resolvePath(explicit);
|
|
140
134
|
}
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
}
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
}
|
|
151
|
-
else {
|
|
152
|
-
units.push(entry);
|
|
153
|
-
}
|
|
154
|
-
});
|
|
155
|
-
return units;
|
|
156
|
-
}
|
|
157
|
-
function needsMatches(segment) {
|
|
158
|
-
const state = segment.getState();
|
|
159
|
-
if (state === undefined || state === 'initial') {
|
|
160
|
-
return true;
|
|
161
|
-
}
|
|
162
|
-
const target = segment.getTarget();
|
|
163
|
-
if (!target) {
|
|
164
|
-
return true;
|
|
165
|
-
}
|
|
166
|
-
return getPureText(target.getContent()).trim().length === 0;
|
|
167
|
-
}
|
|
168
|
-
function buildXliffMatch(ref, match, dataCounter) {
|
|
169
|
-
const xliffMatch = new XliffMatch(ref);
|
|
170
|
-
xliffMatch.setNamespacePrefix(DEFAULT_PREFIX);
|
|
171
|
-
xliffMatch.setType('tm');
|
|
172
|
-
xliffMatch.setOrigin(match.origin);
|
|
173
|
-
// @similarity is source-text similarity; @matchQuality is the overall match rating
|
|
174
|
-
xliffMatch.setSimilarity(String(match.fuzzy));
|
|
175
|
-
xliffMatch.setMatchQuality(String(match.similarity));
|
|
176
|
-
const originalData = new XliffOriginalData();
|
|
177
|
-
const correlations = new Map();
|
|
178
|
-
const source = new XliffSource();
|
|
179
|
-
source.setContent(trimInlineContent(convertInlineContent(match.source, originalData, dataCounter, correlations, collectBptAnchors(match.source))));
|
|
180
|
-
xliffMatch.setSource(source);
|
|
181
|
-
const target = new XliffTarget();
|
|
182
|
-
target.setContent(trimInlineContent(convertInlineContent(match.target, originalData, dataCounter, correlations, collectBptAnchors(match.target))));
|
|
183
|
-
xliffMatch.setTarget(target);
|
|
184
|
-
if (originalData.getDataItems().length > 0) {
|
|
185
|
-
xliffMatch.setOriginalData(originalData);
|
|
186
|
-
}
|
|
187
|
-
return xliffMatch;
|
|
188
|
-
}
|
|
189
|
-
function collectBptAnchors(element) {
|
|
190
|
-
const anchors = new Map();
|
|
191
|
-
const walk = (node) => {
|
|
192
|
-
node.getContent().forEach((child) => {
|
|
193
|
-
if (!(child instanceof XMLElement)) {
|
|
194
|
-
return;
|
|
195
|
-
}
|
|
196
|
-
if (child.getName() === 'bpt') {
|
|
197
|
-
const i = child.getAttribute('i')?.getValue();
|
|
198
|
-
const x = child.getAttribute('x')?.getValue();
|
|
199
|
-
if (i && x) {
|
|
200
|
-
anchors.set(i, x);
|
|
201
|
-
}
|
|
202
|
-
}
|
|
203
|
-
walk(child);
|
|
204
|
-
});
|
|
205
|
-
};
|
|
206
|
-
walk(element);
|
|
207
|
-
return anchors;
|
|
208
|
-
}
|
|
209
|
-
function convertInlineContent(element, originalData, counter, correlations, bptAnchors) {
|
|
210
|
-
const result = [];
|
|
211
|
-
element.getContent().forEach((node) => {
|
|
212
|
-
if (node instanceof TextNode) {
|
|
213
|
-
result.push(node.getValue());
|
|
214
|
-
}
|
|
215
|
-
else if (node instanceof XMLElement) {
|
|
216
|
-
const name = node.getName();
|
|
217
|
-
if (PLACEHOLDER_TAGS.has(name)) {
|
|
218
|
-
let anchor = node.getAttribute('x')?.getValue() ?? node.getAttribute('id')?.getValue();
|
|
219
|
-
if (!anchor && name === 'ept') {
|
|
220
|
-
const i = node.getAttribute('i')?.getValue();
|
|
221
|
-
anchor = i ? bptAnchors.get(i) : undefined;
|
|
222
|
-
}
|
|
223
|
-
const key = anchor ? name + ':' + anchor : undefined;
|
|
224
|
-
let ref = key ? correlations.get(key) : undefined;
|
|
225
|
-
if (!ref) {
|
|
226
|
-
counter.value++;
|
|
227
|
-
ref = { phId: 'mtcph' + counter.value, dataId: 'mtcd' + counter.value };
|
|
228
|
-
const data = new XliffData(ref.dataId);
|
|
229
|
-
data.addText(extractElementText(node));
|
|
230
|
-
originalData.addData(data);
|
|
231
|
-
if (key) {
|
|
232
|
-
correlations.set(key, ref);
|
|
233
|
-
}
|
|
234
|
-
}
|
|
235
|
-
const ph = new XliffPh(ref.phId);
|
|
236
|
-
ph.setDataRef(ref.dataId);
|
|
237
|
-
result.push(ph);
|
|
135
|
+
const ext = extname(inputPath);
|
|
136
|
+
const stem = basename(inputPath, ext);
|
|
137
|
+
return join(dirname(inputPath), stem + '.matches.xlf');
|
|
138
|
+
}
|
|
139
|
+
static collectUnits(entries) {
|
|
140
|
+
const units = [];
|
|
141
|
+
entries.forEach((entry) => {
|
|
142
|
+
if (entry instanceof XliffGroup) {
|
|
143
|
+
units.push(...MatchCommand.collectUnits(entry.getEntries()));
|
|
238
144
|
}
|
|
239
145
|
else {
|
|
240
|
-
|
|
146
|
+
units.push(entry);
|
|
241
147
|
}
|
|
242
|
-
}
|
|
243
|
-
|
|
244
|
-
return result;
|
|
245
|
-
}
|
|
246
|
-
function trimInlineContent(content) {
|
|
247
|
-
const trimmed = [...content];
|
|
248
|
-
while (trimmed.length > 0 && typeof trimmed[0] === 'string' && trimmed[0].trim().length === 0) {
|
|
249
|
-
trimmed.shift();
|
|
250
|
-
}
|
|
251
|
-
while (trimmed.length > 0 && typeof trimmed[trimmed.length - 1] === 'string' && trimmed[trimmed.length - 1].trim().length === 0) {
|
|
252
|
-
trimmed.pop();
|
|
253
|
-
}
|
|
254
|
-
if (trimmed.length > 0 && typeof trimmed[0] === 'string') {
|
|
255
|
-
trimmed[0] = trimmed[0].replace(/^\s+/, '');
|
|
256
|
-
}
|
|
257
|
-
if (trimmed.length > 0 && typeof trimmed[trimmed.length - 1] === 'string') {
|
|
258
|
-
trimmed[trimmed.length - 1] = trimmed[trimmed.length - 1].replace(/\s+$/, '');
|
|
259
|
-
}
|
|
260
|
-
return trimmed;
|
|
261
|
-
}
|
|
262
|
-
function declareMatchesNamespace(document) {
|
|
263
|
-
const alreadyDeclared = document.getOtherAttributes().some((attribute) => attribute.getName().startsWith('xmlns:') && attribute.getValue() === MATCHES_NAMESPACE);
|
|
264
|
-
if (!alreadyDeclared) {
|
|
265
|
-
document.setOtherAttribute('xmlns:' + DEFAULT_PREFIX, MATCHES_NAMESPACE);
|
|
266
|
-
}
|
|
267
|
-
}
|
|
268
|
-
function resolveOutputPath(explicit, inputPath) {
|
|
269
|
-
if (explicit) {
|
|
270
|
-
return CliUtils.resolvePath(explicit);
|
|
271
|
-
}
|
|
272
|
-
const ext = extname(inputPath);
|
|
273
|
-
const stem = basename(inputPath, ext);
|
|
274
|
-
return join(dirname(inputPath), stem + '.matches.xlf');
|
|
275
|
-
}
|
|
276
|
-
function parseIntOption(raw, fallback, flag, min, max) {
|
|
277
|
-
if (raw === undefined) {
|
|
278
|
-
return fallback;
|
|
279
|
-
}
|
|
280
|
-
const parsed = Number(raw);
|
|
281
|
-
if (!Number.isInteger(parsed) || parsed < min || (max !== undefined && parsed > max)) {
|
|
282
|
-
const range = max !== undefined ? 'an integer between ' + min + ' and ' + max : 'an integer of at least ' + min;
|
|
283
|
-
CliUtils.fail('Invalid ' + flag + ' value "' + raw + '"; expected ' + range + '.');
|
|
148
|
+
});
|
|
149
|
+
return units;
|
|
284
150
|
}
|
|
285
|
-
|
|
286
|
-
|
|
287
|
-
|
|
288
|
-
let text = '';
|
|
289
|
-
element.getContent().forEach((node) => {
|
|
290
|
-
if (node instanceof TextNode) {
|
|
291
|
-
text += node.getValue();
|
|
292
|
-
}
|
|
293
|
-
else if (node instanceof XMLElement) {
|
|
294
|
-
text += extractElementText(node);
|
|
295
|
-
}
|
|
296
|
-
});
|
|
297
|
-
return text;
|
|
298
|
-
}
|
|
299
|
-
function getPureText(content) {
|
|
300
|
-
let text = '';
|
|
301
|
-
content.forEach((item) => {
|
|
302
|
-
if (typeof item === 'string') {
|
|
303
|
-
text += item;
|
|
151
|
+
static declareMatchesNamespace(document) {
|
|
152
|
+
if (!Utils.isNamespaceDeclared(document.getOtherAttributes(), Utils.MATCHES_NAMESPACE)) {
|
|
153
|
+
document.setOtherAttribute('xmlns:' + Utils.MTC_PREFIX, Utils.MATCHES_NAMESPACE);
|
|
304
154
|
}
|
|
305
|
-
|
|
306
|
-
|
|
155
|
+
}
|
|
156
|
+
static parseIntOption(raw, flag, min, max) {
|
|
157
|
+
const parsed = Number(raw);
|
|
158
|
+
if (!Number.isInteger(parsed) || parsed < min || (max !== undefined && parsed > max)) {
|
|
159
|
+
const range = max !== undefined ? 'an integer between ' + min + ' and ' + max : 'an integer of at least ' + min;
|
|
160
|
+
CliUtils.fail('Invalid ' + flag + ' value "' + raw + '"; expected ' + range + '.');
|
|
307
161
|
}
|
|
308
|
-
|
|
309
|
-
|
|
162
|
+
return parsed;
|
|
163
|
+
}
|
|
310
164
|
}
|
|
311
165
|
//# sourceMappingURL=matchCommand.js.map
|