hybridtm 0.5.0 → 0.7.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 CHANGED
@@ -9,12 +9,13 @@ HybridTM is a semantic translation memory engine that stores bilingual content i
9
9
  - Provides `semanticTranslationSearch`, `semanticSearch`, and `concordanceSearch` APIs with metadata-aware filtering
10
10
  - Streams data into LanceDB through a JSONL-based batch importer to keep memory usage predictable
11
11
  - Prevents duplicate segments by rewriting entries with deterministic IDs (`fileId:unitId:segmentIndex:lang`)
12
+ - Ships a `hybridtm` command-line tool for creating instances, importing files, and enriching XLIFF files with TM match candidates from the shell
12
13
 
13
14
  Models download automatically the first time you initialize an instance and are cached in the standard Hugging Face directory.
14
15
 
15
16
  ## Requirements
16
17
 
17
- - Node.js 22 LTS or later
18
+ - Node.js 24 LTS or later
18
19
  - npm 11+
19
20
  - Disk space for both the LanceDB directory you choose and the embedding model cache
20
21
 
@@ -72,12 +73,32 @@ await tm.importSDLTM('./translations/legacy.sdltm');
72
73
 
73
74
  `semanticTranslationSearch` automatically pairs every source hit with its matching target segment (same `fileId`, `unitId`, and `segmentIndex`), making the output ready for CAT integrations.
74
75
 
76
+ ## Command-line interface
77
+
78
+ Installing HybridTM globally also adds a `hybridtm` command:
79
+
80
+ ```bash
81
+ npm install -g hybridtm
82
+
83
+ hybridtm create -name project -path ./project.lancedb
84
+ hybridtm import -name project -file ./translations/project.xlf
85
+ hybridtm match -name project -file ./new-content.xlf -output ./new-content.matches.xlf
86
+ hybridtm list
87
+ hybridtm remove -name project -yes
88
+ ```
89
+
90
+ `match` never touches `<target>` — it adds spec Translation Candidates
91
+ (`<mtc:matches>`/`<mtc:match>`) entries to a **new** output file, leaving the
92
+ input untouched. Run `hybridtm <command> -help` for the full flag list, or
93
+ see [05 · Command-Line Interface](docs/05-command-line-interface.md).
94
+
75
95
  ## Documentation
76
96
 
77
97
  - [01 · Getting Started](docs/01-getting-started.md)
78
98
  - [02 · Importing Data](docs/02-importing-data.md)
79
99
  - [03 · Search and Filtering](docs/03-search-and-filtering.md)
80
100
  - [04 · Sample Scenarios](docs/04-sample-scenarios.md)
101
+ - [05 · Command-Line Interface](docs/05-command-line-interface.md)
81
102
 
82
103
  Each guide is short and task-oriented, so you can jump directly to the workflow you need.
83
104
 
@@ -100,6 +121,7 @@ If you copy `samples/` elsewhere, update `samples/package.json` so the `hybridtm
100
121
  ## Project layout
101
122
 
102
123
  - `ts/` – source files for the library
124
+ - `ts/cli/` – source for the `hybridtm` command-line tool
103
125
  - `dist/` – compiled JavaScript and declarations (`npm run build`)
104
126
  - `docs/` – task-focused tutorials referenced above
105
127
  - `samples/` – standalone TypeScript project with runnable workflows
@@ -0,0 +1,19 @@
1
+ /*******************************************************************************
2
+ * Copyright (c) 2025-2026 Maxprograms.
3
+ *
4
+ * This program and the accompanying materials
5
+ * are made available under the terms of the Eclipse License 1.0
6
+ * which accompanies this distribution, and is available at
7
+ * https://www.eclipse.org/org/documents/epl-v10.html
8
+ *
9
+ * Contributors:
10
+ * Maxprograms - initial API and implementation
11
+ *******************************************************************************/
12
+ export declare class CliUtils {
13
+ static getFlag(args: string[], flag: string): string | undefined;
14
+ static hasFlag(args: string[], flag: string): boolean;
15
+ static resolvePath(rawPath: string): string;
16
+ static requireExistingFile(rawPath: string, label: string): string;
17
+ static resolveModelName(alias: string | undefined): string;
18
+ static fail(message: string): never;
19
+ }
@@ -0,0 +1,67 @@
1
+ /*******************************************************************************
2
+ * Copyright (c) 2025-2026 Maxprograms.
3
+ *
4
+ * This program and the accompanying materials
5
+ * are made available under the terms of the Eclipse License 1.0
6
+ * which accompanies this distribution, and is available at
7
+ * https://www.eclipse.org/org/documents/epl-v10.html
8
+ *
9
+ * Contributors:
10
+ * Maxprograms - initial API and implementation
11
+ *******************************************************************************/
12
+ import { existsSync } from "node:fs";
13
+ import { homedir } from "node:os";
14
+ import { join, resolve } from "node:path";
15
+ import { HybridTM } from '../hybridtm.js';
16
+ export class CliUtils {
17
+ static getFlag(args, flag) {
18
+ const index = args.indexOf(flag);
19
+ if (index !== -1 && index + 1 < args.length) {
20
+ return args[index + 1];
21
+ }
22
+ return undefined;
23
+ }
24
+ static hasFlag(args, flag) {
25
+ return args.includes(flag);
26
+ }
27
+ static resolvePath(rawPath) {
28
+ let expanded = rawPath;
29
+ if (expanded.startsWith('~')) {
30
+ const home = homedir();
31
+ if (expanded === '~') {
32
+ expanded = home;
33
+ }
34
+ else if (expanded.startsWith('~/')) {
35
+ expanded = join(home, expanded.slice(2));
36
+ }
37
+ }
38
+ return resolve(expanded);
39
+ }
40
+ static requireExistingFile(rawPath, label) {
41
+ const resolved = CliUtils.resolvePath(rawPath);
42
+ if (!existsSync(resolved)) {
43
+ console.error(label + ' "' + resolved + '" does not exist.');
44
+ process.exit(1);
45
+ }
46
+ return resolved;
47
+ }
48
+ static resolveModelName(alias) {
49
+ switch (alias) {
50
+ case 'speed':
51
+ return HybridTM.SPEED_MODEL;
52
+ case 'resource':
53
+ return HybridTM.RESOURCE_MODEL;
54
+ case 'quality':
55
+ case undefined:
56
+ return HybridTM.QUALITY_MODEL;
57
+ default:
58
+ console.error('Unknown -model value "' + alias + '". Expected speed, quality, or resource.');
59
+ process.exit(1);
60
+ }
61
+ }
62
+ static fail(message) {
63
+ console.error(message);
64
+ process.exit(1);
65
+ }
66
+ }
67
+ //# sourceMappingURL=cliUtils.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"cliUtils.js","sourceRoot":"","sources":["../../ts/cli/cliUtils.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;iFAUiF;AAEjF,OAAO,EAAE,UAAU,EAAE,MAAM,SAAS,CAAC;AACrC,OAAO,EAAE,OAAO,EAAE,MAAM,SAAS,CAAC;AAClC,OAAO,EAAE,IAAI,EAAE,OAAO,EAAE,MAAM,WAAW,CAAC;AAC1C,OAAO,EAAE,QAAQ,EAAE,MAAM,gBAAgB,CAAC;AAE1C,MAAM,OAAO,QAAQ;IAEjB,MAAM,CAAC,OAAO,CAAC,IAAc,EAAE,IAAY;QACvC,MAAM,KAAK,GAAW,IAAI,CAAC,OAAO,CAAC,IAAI,CAAC,CAAC;QACzC,IAAI,KAAK,KAAK,CAAC,CAAC,IAAI,KAAK,GAAG,CAAC,GAAG,IAAI,CAAC,MAAM,EAAE,CAAC;YAC1C,OAAO,IAAI,CAAC,KAAK,GAAG,CAAC,CAAC,CAAC;QAC3B,CAAC;QACD,OAAO,SAAS,CAAC;IACrB,CAAC;IAED,MAAM,CAAC,OAAO,CAAC,IAAc,EAAE,IAAY;QACvC,OAAO,IAAI,CAAC,QAAQ,CAAC,IAAI,CAAC,CAAC;IAC/B,CAAC;IAED,MAAM,CAAC,WAAW,CAAC,OAAe;QAC9B,IAAI,QAAQ,GAAW,OAAO,CAAC;QAC/B,IAAI,QAAQ,CAAC,UAAU,CAAC,GAAG,CAAC,EAAE,CAAC;YAC3B,MAAM,IAAI,GAAW,OAAO,EAAE,CAAC;YAC/B,IAAI,QAAQ,KAAK,GAAG,EAAE,CAAC;gBACnB,QAAQ,GAAG,IAAI,CAAC;YACpB,CAAC;iBAAM,IAAI,QAAQ,CAAC,UAAU,CAAC,IAAI,CAAC,EAAE,CAAC;gBACnC,QAAQ,GAAG,IAAI,CAAC,IAAI,EAAE,QAAQ,CAAC,KAAK,CAAC,CAAC,CAAC,CAAC,CAAC;YAC7C,CAAC;QACL,CAAC;QACD,OAAO,OAAO,CAAC,QAAQ,CAAC,CAAC;IAC7B,CAAC;IAED,MAAM,CAAC,mBAAmB,CAAC,OAAe,EAAE,KAAa;QACrD,MAAM,QAAQ,GAAW,QAAQ,CAAC,WAAW,CAAC,OAAO,CAAC,CAAC;QACvD,IAAI,CAAC,UAAU,CAAC,QAAQ,CAAC,EAAE,CAAC;YACxB,OAAO,CAAC,KAAK,CAAC,KAAK,GAAG,IAAI,GAAG,QAAQ,GAAG,mBAAmB,CAAC,CAAC;YAC7D,OAAO,CAAC,IAAI,CAAC,CAAC,CAAC,CAAC;QACpB,CAAC;QACD,OAAO,QAAQ,CAAC;IACpB,CAAC;IAED,MAAM,CAAC,gBAAgB,CAAC,KAAyB;QAC7C,QAAQ,KAAK,EAAE,CAAC;YACZ,KAAK,OAAO;gBACR,OAAO,QAAQ,CAAC,WAAW,CAAC;YAChC,KAAK,UAAU;gBACX,OAAO,QAAQ,CAAC,cAAc,CAAC;YACnC,KAAK,SAAS,CAAC;YACf,KAAK,SAAS;gBACV,OAAO,QAAQ,CAAC,aAAa,CAAC;YAClC;gBACI,OAAO,CAAC,KAAK,CAAC,wBAAwB,GAAG,KAAK,GAAG,0CAA0C,CAAC,CAAC;gBAC7F,OAAO,CAAC,IAAI,CAAC,CAAC,CAAC,CAAC;QACxB,CAAC;IACL,CAAC;IAED,MAAM,CAAC,IAAI,CAAC,OAAe;QACvB,OAAO,CAAC,KAAK,CAAC,OAAO,CAAC,CAAC;QACvB,OAAO,CAAC,IAAI,CAAC,CAAC,CAAC,CAAC;IACpB,CAAC;CACJ"}
@@ -0,0 +1,13 @@
1
+ /*******************************************************************************
2
+ * Copyright (c) 2025-2026 Maxprograms.
3
+ *
4
+ * This program and the accompanying materials
5
+ * are made available under the terms of the Eclipse License 1.0
6
+ * which accompanies this distribution, and is available at
7
+ * https://www.eclipse.org/org/documents/epl-v10.html
8
+ *
9
+ * Contributors:
10
+ * Maxprograms - initial API and implementation
11
+ *******************************************************************************/
12
+ export declare function usage(): void;
13
+ export declare function runCreateCommand(args: string[]): Promise<void>;
@@ -0,0 +1,44 @@
1
+ /*******************************************************************************
2
+ * Copyright (c) 2025-2026 Maxprograms.
3
+ *
4
+ * This program and the accompanying materials
5
+ * are made available under the terms of the Eclipse License 1.0
6
+ * which accompanies this distribution, and is available at
7
+ * https://www.eclipse.org/org/documents/epl-v10.html
8
+ *
9
+ * Contributors:
10
+ * Maxprograms - initial API and implementation
11
+ *******************************************************************************/
12
+ import { HybridTMFactory } from '../index.js';
13
+ import { CliUtils } from './cliUtils.js';
14
+ export function usage() {
15
+ console.log('Usage: hybridtm create -name <name> -path <dir> [-model speed|quality|resource]');
16
+ console.log();
17
+ console.log(' -name Name to register the new instance under (required)');
18
+ console.log(' -path Directory where the instance\'s LanceDB data will live (required)');
19
+ console.log(' -model Embedding model preset: speed, quality (default), or resource');
20
+ }
21
+ export async function runCreateCommand(args) {
22
+ if (CliUtils.hasFlag(args, '-help')) {
23
+ usage();
24
+ return;
25
+ }
26
+ const name = CliUtils.getFlag(args, '-name');
27
+ const rawPath = CliUtils.getFlag(args, '-path');
28
+ if (!name || !rawPath) {
29
+ usage();
30
+ CliUtils.fail('Missing required -name or -path.');
31
+ }
32
+ const modelAlias = CliUtils.getFlag(args, '-model');
33
+ const modelName = CliUtils.resolveModelName(modelAlias);
34
+ const resolvedPath = CliUtils.resolvePath(rawPath);
35
+ try {
36
+ const tm = HybridTMFactory.createInstance(name, resolvedPath, modelName);
37
+ await tm.close();
38
+ console.log('Created instance "' + name + '" at ' + resolvedPath + ' (model: ' + modelName + ')');
39
+ }
40
+ catch (error) {
41
+ CliUtils.fail(error instanceof Error ? error.message : String(error));
42
+ }
43
+ }
44
+ //# sourceMappingURL=createCommand.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"createCommand.js","sourceRoot":"","sources":["../../ts/cli/createCommand.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;iFAUiF;AAEjF,OAAO,EAAY,eAAe,EAAE,MAAM,aAAa,CAAC;AACxD,OAAO,EAAE,QAAQ,EAAE,MAAM,eAAe,CAAC;AAEzC,MAAM,UAAU,KAAK;IACjB,OAAO,CAAC,GAAG,CAAC,iFAAiF,CAAC,CAAC;IAC/F,OAAO,CAAC,GAAG,EAAE,CAAC;IACd,OAAO,CAAC,GAAG,CAAC,+DAA+D,CAAC,CAAC;IAC7E,OAAO,CAAC,GAAG,CAAC,8EAA8E,CAAC,CAAC;IAC5F,OAAO,CAAC,GAAG,CAAC,0EAA0E,CAAC,CAAC;AAC5F,CAAC;AAED,MAAM,CAAC,KAAK,UAAU,gBAAgB,CAAC,IAAc;IACjD,IAAI,QAAQ,CAAC,OAAO,CAAC,IAAI,EAAE,OAAO,CAAC,EAAE,CAAC;QAClC,KAAK,EAAE,CAAC;QACR,OAAO;IACX,CAAC;IAED,MAAM,IAAI,GAAuB,QAAQ,CAAC,OAAO,CAAC,IAAI,EAAE,OAAO,CAAC,CAAC;IACjE,MAAM,OAAO,GAAuB,QAAQ,CAAC,OAAO,CAAC,IAAI,EAAE,OAAO,CAAC,CAAC;IACpE,IAAI,CAAC,IAAI,IAAI,CAAC,OAAO,EAAE,CAAC;QACpB,KAAK,EAAE,CAAC;QACR,QAAQ,CAAC,IAAI,CAAC,kCAAkC,CAAC,CAAC;IACtD,CAAC;IAED,MAAM,UAAU,GAAuB,QAAQ,CAAC,OAAO,CAAC,IAAI,EAAE,QAAQ,CAAC,CAAC;IACxE,MAAM,SAAS,GAAW,QAAQ,CAAC,gBAAgB,CAAC,UAAU,CAAC,CAAC;IAChE,MAAM,YAAY,GAAW,QAAQ,CAAC,WAAW,CAAC,OAAO,CAAC,CAAC;IAE3D,IAAI,CAAC;QACD,MAAM,EAAE,GAAa,eAAe,CAAC,cAAc,CAAC,IAAI,EAAE,YAAY,EAAE,SAAS,CAAC,CAAC;QACnF,MAAM,EAAE,CAAC,KAAK,EAAE,CAAC;QACjB,OAAO,CAAC,GAAG,CAAC,oBAAoB,GAAG,IAAI,GAAG,OAAO,GAAG,YAAY,GAAG,WAAW,GAAG,SAAS,GAAG,GAAG,CAAC,CAAC;IACtG,CAAC;IAAC,OAAO,KAAc,EAAE,CAAC;QACtB,QAAQ,CAAC,IAAI,CAAC,KAAK,YAAY,KAAK,CAAC,CAAC,CAAC,KAAK,CAAC,OAAO,CAAC,CAAC,CAAC,MAAM,CAAC,KAAK,CAAC,CAAC,CAAC;IAC1E,CAAC;AACL,CAAC"}
@@ -0,0 +1,13 @@
1
+ #!/usr/bin/env node
2
+ /*******************************************************************************
3
+ * Copyright (c) 2025-2026 Maxprograms.
4
+ *
5
+ * This program and the accompanying materials
6
+ * are made available under the terms of the Eclipse License 1.0
7
+ * which accompanies this distribution, and is available at
8
+ * https://www.eclipse.org/org/documents/epl-v10.html
9
+ *
10
+ * Contributors:
11
+ * Maxprograms - initial API and implementation
12
+ *******************************************************************************/
13
+ export {};
@@ -0,0 +1,73 @@
1
+ #!/usr/bin/env node
2
+ /*******************************************************************************
3
+ * Copyright (c) 2025-2026 Maxprograms.
4
+ *
5
+ * This program and the accompanying materials
6
+ * are made available under the terms of the Eclipse License 1.0
7
+ * which accompanies this distribution, and is available at
8
+ * https://www.eclipse.org/org/documents/epl-v10.html
9
+ *
10
+ * Contributors:
11
+ * Maxprograms - initial API and implementation
12
+ *******************************************************************************/
13
+ import { runCreateCommand } from './createCommand.js';
14
+ import { runImportCommand } from './importCommand.js';
15
+ import { runMatchCommand } from './matchCommand.js';
16
+ import { runListCommand, runRemoveCommand } from './registryCommands.js';
17
+ class HybridTMCli {
18
+ constructor() {
19
+ this.run(process.argv.slice(2));
20
+ }
21
+ async run(argv) {
22
+ const command = argv[0];
23
+ const rest = argv.slice(1);
24
+ try {
25
+ if (command === 'create') {
26
+ await runCreateCommand(rest);
27
+ }
28
+ else if (command === 'import') {
29
+ await runImportCommand(rest);
30
+ }
31
+ else if (command === 'match') {
32
+ await runMatchCommand(rest);
33
+ }
34
+ else if (command === 'list') {
35
+ await runListCommand(rest);
36
+ }
37
+ else if (command === 'remove') {
38
+ await runRemoveCommand(rest);
39
+ }
40
+ else if (command === '-help' || command === '--help') {
41
+ this.usage();
42
+ process.exit(0);
43
+ }
44
+ else if (command === undefined) {
45
+ this.usage();
46
+ process.exit(1);
47
+ }
48
+ else {
49
+ console.error('Unknown command "' + command + '".');
50
+ this.usage();
51
+ process.exit(1);
52
+ }
53
+ }
54
+ catch (error) {
55
+ console.error(error instanceof Error ? error.message : String(error));
56
+ process.exit(1);
57
+ }
58
+ }
59
+ usage() {
60
+ console.log('Usage: hybridtm <command> [options]');
61
+ console.log();
62
+ console.log('Commands:');
63
+ console.log(' create Create a new named TM instance');
64
+ console.log(' import Import an XLIFF/TMX/SDLTM file into an instance');
65
+ console.log(' match Enrich an XLIFF file with TM match candidates');
66
+ console.log(' list List registered instances');
67
+ console.log(' remove Delete a registered instance and its data');
68
+ console.log();
69
+ console.log('Run "hybridtm <command> -help" for command-specific options.');
70
+ }
71
+ }
72
+ new HybridTMCli();
73
+ //# sourceMappingURL=hybridtmCli.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"hybridtmCli.js","sourceRoot":"","sources":["../../ts/cli/hybridtmCli.ts"],"names":[],"mappings":";AACA;;;;;;;;;;iFAUiF;AAEjF,OAAO,EAAE,gBAAgB,EAAE,MAAM,oBAAoB,CAAC;AACtD,OAAO,EAAE,gBAAgB,EAAE,MAAM,oBAAoB,CAAC;AACtD,OAAO,EAAE,eAAe,EAAE,MAAM,mBAAmB,CAAC;AACpD,OAAO,EAAE,cAAc,EAAE,gBAAgB,EAAE,MAAM,uBAAuB,CAAC;AAEzE,MAAM,WAAW;IAEb;QACI,IAAI,CAAC,GAAG,CAAC,OAAO,CAAC,IAAI,CAAC,KAAK,CAAC,CAAC,CAAC,CAAC,CAAC;IACpC,CAAC;IAEO,KAAK,CAAC,GAAG,CAAC,IAAc;QAC5B,MAAM,OAAO,GAAuB,IAAI,CAAC,CAAC,CAAC,CAAC;QAC5C,MAAM,IAAI,GAAa,IAAI,CAAC,KAAK,CAAC,CAAC,CAAC,CAAC;QACrC,IAAI,CAAC;YACD,IAAI,OAAO,KAAK,QAAQ,EAAE,CAAC;gBACvB,MAAM,gBAAgB,CAAC,IAAI,CAAC,CAAC;YACjC,CAAC;iBAAM,IAAI,OAAO,KAAK,QAAQ,EAAE,CAAC;gBAC9B,MAAM,gBAAgB,CAAC,IAAI,CAAC,CAAC;YACjC,CAAC;iBAAM,IAAI,OAAO,KAAK,OAAO,EAAE,CAAC;gBAC7B,MAAM,eAAe,CAAC,IAAI,CAAC,CAAC;YAChC,CAAC;iBAAM,IAAI,OAAO,KAAK,MAAM,EAAE,CAAC;gBAC5B,MAAM,cAAc,CAAC,IAAI,CAAC,CAAC;YAC/B,CAAC;iBAAM,IAAI,OAAO,KAAK,QAAQ,EAAE,CAAC;gBAC9B,MAAM,gBAAgB,CAAC,IAAI,CAAC,CAAC;YACjC,CAAC;iBAAM,IAAI,OAAO,KAAK,OAAO,IAAI,OAAO,KAAK,QAAQ,EAAE,CAAC;gBACrD,IAAI,CAAC,KAAK,EAAE,CAAC;gBACb,OAAO,CAAC,IAAI,CAAC,CAAC,CAAC,CAAC;YACpB,CAAC;iBAAM,IAAI,OAAO,KAAK,SAAS,EAAE,CAAC;gBAC/B,IAAI,CAAC,KAAK,EAAE,CAAC;gBACb,OAAO,CAAC,IAAI,CAAC,CAAC,CAAC,CAAC;YACpB,CAAC;iBAAM,CAAC;gBACJ,OAAO,CAAC,KAAK,CAAC,mBAAmB,GAAG,OAAO,GAAG,IAAI,CAAC,CAAC;gBACpD,IAAI,CAAC,KAAK,EAAE,CAAC;gBACb,OAAO,CAAC,IAAI,CAAC,CAAC,CAAC,CAAC;YACpB,CAAC;QACL,CAAC;QAAC,OAAO,KAAc,EAAE,CAAC;YACtB,OAAO,CAAC,KAAK,CAAC,KAAK,YAAY,KAAK,CAAC,CAAC,CAAC,KAAK,CAAC,OAAO,CAAC,CAAC,CAAC,MAAM,CAAC,KAAK,CAAC,CAAC,CAAC;YACtE,OAAO,CAAC,IAAI,CAAC,CAAC,CAAC,CAAC;QACpB,CAAC;IACL,CAAC;IAEO,KAAK;QACT,OAAO,CAAC,GAAG,CAAC,qCAAqC,CAAC,CAAC;QACnD,OAAO,CAAC,GAAG,EAAE,CAAC;QACd,OAAO,CAAC,GAAG,CAAC,WAAW,CAAC,CAAC;QACzB,OAAO,CAAC,GAAG,CAAC,2CAA2C,CAAC,CAAC;QACzD,OAAO,CAAC,GAAG,CAAC,4DAA4D,CAAC,CAAC;QAC1E,OAAO,CAAC,GAAG,CAAC,0DAA0D,CAAC,CAAC;QACxE,OAAO,CAAC,GAAG,CAAC,sCAAsC,CAAC,CAAC;QACpD,OAAO,CAAC,GAAG,CAAC,sDAAsD,CAAC,CAAC;QACpE,OAAO,CAAC,GAAG,EAAE,CAAC;QACd,OAAO,CAAC,GAAG,CAAC,8DAA8D,CAAC,CAAC;IAChF,CAAC;CACJ;AAED,IAAI,WAAW,EAAE,CAAC"}
@@ -0,0 +1,13 @@
1
+ /*******************************************************************************
2
+ * Copyright (c) 2025-2026 Maxprograms.
3
+ *
4
+ * This program and the accompanying materials
5
+ * are made available under the terms of the Eclipse License 1.0
6
+ * which accompanies this distribution, and is available at
7
+ * https://www.eclipse.org/org/documents/epl-v10.html
8
+ *
9
+ * Contributors:
10
+ * Maxprograms - initial API and implementation
11
+ *******************************************************************************/
12
+ export declare function usage(): void;
13
+ export declare function runImportCommand(args: string[]): Promise<void>;
@@ -0,0 +1,105 @@
1
+ /*******************************************************************************
2
+ * Copyright (c) 2025-2026 Maxprograms.
3
+ *
4
+ * This program and the accompanying materials
5
+ * are made available under the terms of the Eclipse License 1.0
6
+ * which accompanies this distribution, and is available at
7
+ * https://www.eclipse.org/org/documents/epl-v10.html
8
+ *
9
+ * Contributors:
10
+ * Maxprograms - initial API and implementation
11
+ *******************************************************************************/
12
+ import { extname } from "node:path";
13
+ import { HybridTMFactory } from '../index.js';
14
+ import { CliUtils } from './cliUtils.js';
15
+ export function usage() {
16
+ console.log('Usage: hybridtm import -name <name> -file <path> [-type xliff|tmx|sdltm]');
17
+ console.log(' [-minState initial|translated|reviewed|final]');
18
+ console.log(' [-keepEmpty] [-keepUnconfirmed] [-noMetadata]');
19
+ console.log();
20
+ console.log(' -name Instance to import into (required)');
21
+ console.log(' -file File to import (required)');
22
+ console.log(' -type Import format; inferred from the file extension when omitted');
23
+ console.log(' -minState Minimum segment state to import (default: translated)');
24
+ console.log(' -keepEmpty Import segments with an empty target (default: skipped)');
25
+ console.log(' -keepUnconfirmed Import segments with no recognized state (default: skipped)');
26
+ console.log(' -noMetadata Skip extracting notes/metadata/extension attributes');
27
+ }
28
+ export async function runImportCommand(args) {
29
+ if (CliUtils.hasFlag(args, '-help')) {
30
+ usage();
31
+ return;
32
+ }
33
+ const name = CliUtils.getFlag(args, '-name');
34
+ const rawFile = CliUtils.getFlag(args, '-file');
35
+ if (!name || !rawFile) {
36
+ usage();
37
+ CliUtils.fail('Missing required -name or -file.');
38
+ }
39
+ const filePath = CliUtils.requireExistingFile(rawFile, 'Import file');
40
+ const type = resolveImportType(CliUtils.getFlag(args, '-type'), filePath);
41
+ const options = buildImportOptions(args);
42
+ const tm = HybridTMFactory.getInstance(name);
43
+ if (!tm) {
44
+ CliUtils.fail('No instance named "' + name + '". Run "hybridtm create" or "hybridtm list" first.');
45
+ }
46
+ try {
47
+ let count;
48
+ switch (type) {
49
+ case 'xliff':
50
+ count = await tm.importXLIFF(filePath, options);
51
+ break;
52
+ case 'tmx':
53
+ count = await tm.importTMX(filePath, options);
54
+ break;
55
+ case 'sdltm':
56
+ count = await tm.importSDLTM(filePath, options);
57
+ break;
58
+ }
59
+ console.log('Imported ' + count + ' entries from ' + filePath + ' into "' + name + '".');
60
+ }
61
+ catch (error) {
62
+ CliUtils.fail(error instanceof Error ? error.message : String(error));
63
+ }
64
+ finally {
65
+ await tm.close();
66
+ }
67
+ }
68
+ function resolveImportType(explicit, filePath) {
69
+ if (explicit === 'xliff' || explicit === 'tmx' || explicit === 'sdltm') {
70
+ return explicit;
71
+ }
72
+ if (explicit !== undefined) {
73
+ CliUtils.fail('Unknown -type value "' + explicit + '". Expected xliff, tmx, or sdltm.');
74
+ }
75
+ const ext = extname(filePath).toLowerCase();
76
+ if (ext === '.xlf' || ext === '.xliff') {
77
+ return 'xliff';
78
+ }
79
+ if (ext === '.tmx') {
80
+ return 'tmx';
81
+ }
82
+ if (ext === '.sdltm') {
83
+ return 'sdltm';
84
+ }
85
+ CliUtils.fail('Could not infer import type from "' + filePath + '". Pass -type explicitly.');
86
+ }
87
+ function buildImportOptions(args) {
88
+ const options = {
89
+ skipEmpty: !CliUtils.hasFlag(args, '-keepEmpty'),
90
+ skipUnconfirmed: !CliUtils.hasFlag(args, '-keepUnconfirmed'),
91
+ extractMetadata: !CliUtils.hasFlag(args, '-noMetadata')
92
+ };
93
+ const minState = CliUtils.getFlag(args, '-minState');
94
+ if (minState !== undefined) {
95
+ if (!isTranslationState(minState)) {
96
+ CliUtils.fail('Unknown -minState value "' + minState + '". Expected initial, translated, reviewed, or final.');
97
+ }
98
+ options.minState = minState;
99
+ }
100
+ return options;
101
+ }
102
+ function isTranslationState(value) {
103
+ return value === 'initial' || value === 'translated' || value === 'reviewed' || value === 'final';
104
+ }
105
+ //# sourceMappingURL=importCommand.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"importCommand.js","sourceRoot":"","sources":["../../ts/cli/importCommand.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,13 @@
1
+ /*******************************************************************************
2
+ * Copyright (c) 2025-2026 Maxprograms.
3
+ *
4
+ * This program and the accompanying materials
5
+ * are made available under the terms of the Eclipse License 1.0
6
+ * which accompanies this distribution, and is available at
7
+ * https://www.eclipse.org/org/documents/epl-v10.html
8
+ *
9
+ * Contributors:
10
+ * Maxprograms - initial API and implementation
11
+ *******************************************************************************/
12
+ export declare function usage(): void;
13
+ export declare function runMatchCommand(args: string[]): Promise<void>;
@@ -0,0 +1,195 @@
1
+ /*******************************************************************************
2
+ * Copyright (c) 2025-2026 Maxprograms.
3
+ *
4
+ * This program and the accompanying materials
5
+ * are made available under the terms of the Eclipse License 1.0
6
+ * which accompanies this distribution, and is available at
7
+ * https://www.eclipse.org/org/documents/epl-v10.html
8
+ *
9
+ * Contributors:
10
+ * Maxprograms - initial API and implementation
11
+ *******************************************************************************/
12
+ import { basename, dirname, extname, join } from "node:path";
13
+ import { XliffGroup, XliffMatch, XliffMatches, XliffMrk, XliffParser, XliffPc, XliffSegment, XliffSource, XliffTarget } from 'typesxliff';
14
+ import { HybridTMFactory, Utils } from '../index.js';
15
+ import { CliUtils } from './cliUtils.js';
16
+ const MATCHES_NAMESPACE = 'urn:oasis:names:tc:xliff:matches:2.0';
17
+ const DEFAULT_PREFIX = 'mtc';
18
+ const DEFAULT_LIMIT = 5;
19
+ const DEFAULT_SIMILARITY = 60;
20
+ export function usage() {
21
+ console.log('Usage: hybridtm match -name <name> -file <path> [-output <path>]');
22
+ console.log(' [-limit N] [-similarity N] [-all]');
23
+ console.log();
24
+ console.log(' -name TM instance to search against (required)');
25
+ console.log(' -file XLIFF file to enrich with match candidates (required)');
26
+ console.log(' -output Output path (default: <file-without-ext>.matches.xlf)');
27
+ console.log(' -limit Max candidates per segment (default: ' + DEFAULT_LIMIT + ')');
28
+ console.log(' -similarity Minimum hybrid match score 0-100 (default: ' + DEFAULT_SIMILARITY + ')');
29
+ console.log(' -all Consider every segment, not just untranslated ones');
30
+ console.log();
31
+ console.log('Never modifies <target>. Adds spec Translation Candidates module');
32
+ console.log('(<mtc:matches>/<mtc:match>) entries and writes the result to a new file.');
33
+ }
34
+ export async function runMatchCommand(args) {
35
+ if (CliUtils.hasFlag(args, '-help')) {
36
+ usage();
37
+ return;
38
+ }
39
+ const name = CliUtils.getFlag(args, '-name');
40
+ const rawFile = CliUtils.getFlag(args, '-file');
41
+ if (!name || !rawFile) {
42
+ usage();
43
+ CliUtils.fail('Missing required -name or -file.');
44
+ }
45
+ const filePath = CliUtils.requireExistingFile(rawFile, 'XLIFF file');
46
+ const limit = parsePositiveInt(CliUtils.getFlag(args, '-limit'), DEFAULT_LIMIT, '-limit');
47
+ const similarity = parsePositiveInt(CliUtils.getFlag(args, '-similarity'), DEFAULT_SIMILARITY, '-similarity');
48
+ const processAll = CliUtils.hasFlag(args, '-all');
49
+ const outputPath = resolveOutputPath(CliUtils.getFlag(args, '-output'), filePath);
50
+ const tm = HybridTMFactory.getInstance(name);
51
+ if (!tm) {
52
+ CliUtils.fail('No instance named "' + name + '". Run "hybridtm create" or "hybridtm list" first.');
53
+ }
54
+ try {
55
+ const parser = new XliffParser();
56
+ parser.parseFile(filePath);
57
+ const document = parser.getXliffDocument();
58
+ if (!document) {
59
+ CliUtils.fail('Unable to parse "' + filePath + '".');
60
+ }
61
+ const srcLang = document.getSrcLang();
62
+ const tgtLang = document.getTrgLang();
63
+ if (!tgtLang) {
64
+ CliUtils.fail('"' + filePath + '" is missing @trgLang; nothing to match against.');
65
+ }
66
+ let segmentsProcessed = 0;
67
+ let segmentsWithMatches = 0;
68
+ let totalMatches = 0;
69
+ let usedModule = false;
70
+ for (const file of document.getFiles()) {
71
+ const fileId = file.getId();
72
+ for (const unit of collectUnits(file.getEntries())) {
73
+ const unitMatches = [];
74
+ const unitId = unit.getId();
75
+ for (const item of unit.getItems()) {
76
+ if (!(item instanceof XliffSegment)) {
77
+ continue;
78
+ }
79
+ if (!processAll && !needsMatches(item)) {
80
+ continue;
81
+ }
82
+ const source = item.getSource();
83
+ const pureSource = source ? getPureText(source.getContent()) : '';
84
+ if (pureSource.trim().length === 0) {
85
+ continue;
86
+ }
87
+ segmentsProcessed++;
88
+ const results = await tm.semanticTranslationSearch(pureSource, srcLang, tgtLang, similarity, limit);
89
+ if (results.length === 0) {
90
+ continue;
91
+ }
92
+ segmentsWithMatches++;
93
+ totalMatches += results.length;
94
+ const ref = '#/f=' + fileId + '/u=' + unitId + (item.getId() ? '/' + item.getId() : '');
95
+ results.forEach((match) => unitMatches.push(buildXliffMatch(ref, match)));
96
+ }
97
+ if (unitMatches.length > 0) {
98
+ const matches = new XliffMatches();
99
+ matches.setNamespacePrefix(DEFAULT_PREFIX);
100
+ unitMatches.forEach((match) => matches.addMatch(match));
101
+ unit.setMatches(matches);
102
+ usedModule = true;
103
+ }
104
+ }
105
+ }
106
+ if (usedModule) {
107
+ declareMatchesNamespace(document);
108
+ }
109
+ document.writeDocument(outputPath, true);
110
+ console.log('Segments processed: ' + segmentsProcessed);
111
+ console.log('Segments with matches: ' + segmentsWithMatches);
112
+ console.log('Total match candidates: ' + totalMatches);
113
+ console.log('Output: ' + outputPath);
114
+ }
115
+ catch (error) {
116
+ CliUtils.fail(error instanceof Error ? error.message : String(error));
117
+ }
118
+ finally {
119
+ await tm.close();
120
+ }
121
+ }
122
+ function collectUnits(entries) {
123
+ const units = [];
124
+ entries.forEach((entry) => {
125
+ if (entry instanceof XliffGroup) {
126
+ units.push(...collectUnits(entry.getEntries()));
127
+ }
128
+ else {
129
+ units.push(entry);
130
+ }
131
+ });
132
+ return units;
133
+ }
134
+ function needsMatches(segment) {
135
+ const state = segment.getState();
136
+ if (state === undefined || state === 'initial') {
137
+ return true;
138
+ }
139
+ const target = segment.getTarget();
140
+ if (!target) {
141
+ return true;
142
+ }
143
+ return getPureText(target.getContent()).trim().length === 0;
144
+ }
145
+ function buildXliffMatch(ref, match) {
146
+ const xliffMatch = new XliffMatch(ref);
147
+ xliffMatch.setNamespacePrefix(DEFAULT_PREFIX);
148
+ xliffMatch.setType('tm');
149
+ xliffMatch.setOrigin(match.origin);
150
+ xliffMatch.setSimilarity(String(match.similarity));
151
+ const source = new XliffSource();
152
+ source.addText(Utils.getPureText(match.source));
153
+ xliffMatch.setSource(source);
154
+ const target = new XliffTarget();
155
+ target.addText(Utils.getPureText(match.target));
156
+ xliffMatch.setTarget(target);
157
+ return xliffMatch;
158
+ }
159
+ function declareMatchesNamespace(document) {
160
+ const alreadyDeclared = document.getOtherAttributes().some((attribute) => attribute.getName().startsWith('xmlns:') && attribute.getValue() === MATCHES_NAMESPACE);
161
+ if (!alreadyDeclared) {
162
+ document.setOtherAttribute('xmlns:' + DEFAULT_PREFIX, MATCHES_NAMESPACE);
163
+ }
164
+ }
165
+ function resolveOutputPath(explicit, inputPath) {
166
+ if (explicit) {
167
+ return CliUtils.resolvePath(explicit);
168
+ }
169
+ const ext = extname(inputPath);
170
+ const stem = basename(inputPath, ext);
171
+ return join(dirname(inputPath), stem + '.matches.xlf');
172
+ }
173
+ function parsePositiveInt(raw, fallback, flag) {
174
+ if (raw === undefined) {
175
+ return fallback;
176
+ }
177
+ const parsed = Number(raw);
178
+ if (!Number.isInteger(parsed) || parsed <= 0) {
179
+ CliUtils.fail('Invalid ' + flag + ' value "' + raw + '"; expected a positive integer.');
180
+ }
181
+ return parsed;
182
+ }
183
+ function getPureText(content) {
184
+ let text = '';
185
+ content.forEach((item) => {
186
+ if (typeof item === 'string') {
187
+ text += item;
188
+ }
189
+ else if (item instanceof XliffPc || item instanceof XliffMrk) {
190
+ text += getPureText(item.getContent());
191
+ }
192
+ });
193
+ return text;
194
+ }
195
+ //# sourceMappingURL=matchCommand.js.map