@l10nmonster/cli 1.0.5 → 3.0.0-alpha.10

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.
@@ -0,0 +1,31 @@
1
+ {
2
+ "branches": [
3
+ "main",
4
+ {
5
+ "name": "next",
6
+ "prerelease": "alpha"
7
+ },
8
+ {
9
+ "name": "beta",
10
+ "prerelease": "beta"
11
+ }
12
+ ],
13
+ "tagFormat": "@l10nmonster/cli@${version}",
14
+ "plugins": [
15
+ "@semantic-release/commit-analyzer",
16
+ "@semantic-release/release-notes-generator",
17
+ {
18
+ "path": "@semantic-release/changelog",
19
+ "changelogFile": "CHANGELOG.md"
20
+ },
21
+ {
22
+ "path": "@semantic-release/npm",
23
+ "npmPublish": true
24
+ },
25
+ {
26
+ "path": "@semantic-release/git",
27
+ "assets": ["CHANGELOG.md", "package.json"],
28
+ "message": "chore(release): @l10nmonster/cli@${nextRelease.version} [skip ci]\n\n${nextRelease.notes}"
29
+ }
30
+ ]
31
+ }
package/CHANGELOG.md ADDED
@@ -0,0 +1,6 @@
1
+ # Changelog
2
+
3
+ All notable changes to this project will be documented in this file.
4
+
5
+ The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/),
6
+ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
package/README.md CHANGED
@@ -1,103 +1,421 @@
1
- ## Getting started
1
+ # @l10nmonster/cli
2
2
 
3
- ### Installation
3
+ Command-line interface for L10n Monster v3 - continuous localization for the rest of us.
4
4
 
5
- ```sh
5
+ ## Installation
6
+
7
+ ### From npm
8
+
9
+ ```bash
10
+ npm install -g @l10nmonster/cli
11
+ ```
12
+
13
+ ### From source
14
+
15
+ ```bash
6
16
  git clone git@github.com:l10nmonster/l10nmonster.git
7
17
  cd l10nmonster
8
- npm i
9
- npm link
18
+ npm install
19
+ npm run build
20
+ npm link --global @l10nmonster/cli
10
21
  ```
11
22
 
12
- Eventually there will be a binary for each platform, but this is still under heavy development.
23
+ ## Getting Started
13
24
 
14
- ## Basic Operation
25
+ Create a configuration file `l10nmonster.config.mjs` at your project root, then use the CLI commands to manage translations.
15
26
 
16
- ```sh
17
- l10n push
18
- ```
19
- It will re-read all your source content, figure out what needs translation, and send it to your translator.
27
+ ### Basic Configuration
20
28
 
21
- ```sh
22
- l10n status
29
+ ```javascript
30
+ // l10nmonster.config.mjs
31
+ import { FsSource, FsTarget } from '@l10nmonster/core';
32
+
33
+ export default {
34
+ channels: [{
35
+ source: new FsSource({ globs: ['src/**/*.json'] }),
36
+ target: new FsTarget({
37
+ targetPath: (lang, resourceId) => resourceId.replace('/en/', `/${lang}/`)
38
+ })
39
+ }],
40
+
41
+ providers: [{
42
+ id: 'internal',
43
+ provider: 'InternalLeverage'
44
+ }]
45
+ };
23
46
  ```
24
- It will give you an overview of the state of translation of your project.
25
47
 
26
- ```sh
48
+ ## v3 Commands
49
+
50
+ ### Core Operations
51
+
52
+ ```bash
53
+ # Analyze source content
27
54
  l10n analyze
28
55
  ```
29
- It will analyze your sources and report insights like repeated content in different files and keys.
56
+ Analyzes your sources and reports insights like repeated content, quality metrics, and translation opportunities.
57
+
58
+ ```bash
59
+ # Capture source content snapshot
60
+ l10n source snap
61
+ ```
62
+ Creates a snapshot of your source content for translation workflows.
63
+
64
+ ```bash
65
+ # Generate translations
66
+ l10n translate
67
+ ```
68
+ Processes translation requests through configured providers and generates translated resources.
69
+
70
+ ```bash
71
+ # Update operations and target resources
72
+ l10n ops update
73
+ ```
74
+ Updates target resources with latest translations and manages operation lifecycle.
75
+
76
+ ### Translation Memory
77
+
78
+ ```bash
79
+ # Synchronize TM with providers
80
+ l10n tm syncup
81
+ ```
82
+ Uploads completed translations to translation memory stores.
83
+
84
+ ```bash
85
+ # Download translations from TM
86
+ l10n tm syncdown
87
+ ```
88
+ Downloads latest translations from translation memory to local stores.
30
89
 
31
- ```sh
32
- l10n grandfather -q 80
90
+ ```bash
91
+ # Export TM data
92
+ l10n tm export
33
93
  ```
34
- TODO: update to Grandfather provider. For all missing translations, it will extract translations from the current translated files and, if present, import them at the specified quality level. This assume translations are faithful translations of the current source (i.e. they didn't become outdated if the source has changed). This is probably only used at the beginning, in order to establish a baseline. Afterwards, translated files are always recreated from the TM and overwritten.
94
+ Exports translation memory data for backup or external processing.
35
95
 
36
- ```sh
37
- l10n leverage -q 70 -u 60
96
+ ### Source Management
97
+
98
+ ```bash
99
+ # List source content
100
+ l10n source list
101
+ ```
102
+ Lists all detected source content with metadata and statistics.
103
+
104
+ ```bash
105
+ # Query specific content
106
+ l10n source query --filter "*.json"
107
+ ```
108
+ Queries source content with filters and search criteria.
109
+
110
+ ```bash
111
+ # Show untranslated content
112
+ l10n source untranslated --target es
113
+ ```
114
+ Shows untranslated content for specific target languages.
115
+
116
+ ### Operations Management
117
+
118
+ ```bash
119
+ # View operation details
120
+ l10n ops view
121
+ ```
122
+ Displays detailed information about current operations and their status.
123
+
124
+ ```bash
125
+ # Manage jobs
126
+ l10n ops jobs --status pending
127
+ ```
128
+ Lists and manages translation jobs by status or other criteria.
129
+
130
+ ```bash
131
+ # Provider operations
132
+ l10n ops providers
38
133
  ```
39
- TODO: update to Repetition provider. For all missing translations, it will look into the TM for translations of the exact same source text but in different resources, while matching or not the string id (called respectively qualified and unqualified repetition). Since reusing translations may lead to a loss of quality, you can choose what quality levels to assign to your specific content. Leveraging can be done on a regular basis before pushing content to translation, or never if it's not safe to do so.
134
+ Shows configured providers and their current status.
40
135
 
41
- ```sh
136
+ ```bash
137
+ # Delete operations
138
+ l10n ops delete --job-id abc123
139
+ ```
140
+ Removes specific operations or jobs from the system.
141
+
142
+ ### Legacy Commands (v2 compatibility)
143
+
144
+ ```bash
145
+ # Legacy push operation
146
+ l10n push
147
+ ```
148
+ **Deprecated**: Use `l10n translate` and `l10n ops update` instead.
149
+
150
+ ```bash
151
+ # Legacy pull operation
42
152
  l10n pull
43
153
  ```
44
- If there are pending translations, it will check if they became available and it will fetch them.
154
+ **Deprecated**: Use `l10n tm syncdown` and `l10n ops update` instead.
45
155
 
46
- ```sh
47
- l10n translate
156
+ ```bash
157
+ # Legacy status command
158
+ l10n status
159
+ ```
160
+ **Deprecated**: Use `l10n analyze` for detailed insights.
161
+
162
+ ## Working Files
163
+
164
+ L10n Monster v3 maintains its working files in a `l10nmonster/` directory at the root of the project:
165
+
166
+ - **TM stores**: `l10nmonster/tm/` - Translation memory data
167
+ - **Operations**: `l10nmonster/ops/` - Job and task management
168
+ - **Snapshots**: `l10nmonster/snap/` - Source content snapshots
169
+ - **Providers**: `l10nmonster/providers/` - Provider-specific data
170
+
171
+ Working files are source-control friendly (JSON/JSONL files with consistent formatting) and should be checked in for team collaboration.
172
+
173
+ ## Advanced CLI Options
174
+
175
+ The CLI supports additional options to control behavior:
176
+
177
+ - `-c, --config <path>`: Specify custom configuration file path
178
+ - `-v, --verbose`: Output additional debug information
179
+ - `--dry-run`: Preview operations without making changes
180
+ - `--parallel <number>`: Set parallelism level for operations
181
+ - `--filter <pattern>`: Apply filters to operations
182
+
183
+ ### Example Usage
184
+
185
+ ```bash
186
+ # Run with custom config and high verbosity
187
+ l10n translate -c ./custom.config.mjs -v
188
+
189
+ # Preview operations without executing
190
+ l10n ops update --dry-run
191
+
192
+ # Parallel translation processing
193
+ l10n translate --parallel 4
194
+
195
+ # Filter specific content
196
+ l10n source snap --filter "components/**/*.json"
197
+ ```
198
+
199
+ ## v3 Configuration
200
+
201
+ ### ESM Configuration Format
202
+
203
+ v3 uses ESM-based configuration files (`l10nmonster.config.mjs`):
204
+
205
+ ```javascript
206
+ import { FsSource, FsTarget } from '@l10nmonster/core';
207
+ import { GptAgent } from '@l10nmonster/helpers-openai';
208
+
209
+ export default {
210
+ // Source and target channels
211
+ channels: [{
212
+ source: new FsSource({
213
+ globs: ['src/**/*.json'],
214
+ targetLangs: ['es', 'fr', 'de']
215
+ }),
216
+ target: new FsTarget({
217
+ targetPath: (lang, id) => id.replace('/en/', `/${lang}/`)
218
+ })
219
+ }],
220
+
221
+ // Translation providers
222
+ providers: [{
223
+ id: 'ai-translator',
224
+ provider: new GptAgent({ model: 'gpt-4' })
225
+ }, {
226
+ id: 'internal',
227
+ provider: 'InternalLeverage'
228
+ }],
229
+
230
+ // Content type definitions
231
+ contentTypes: [{
232
+ name: 'json',
233
+ resourceFilter: 'i18next'
234
+ }],
235
+
236
+ // Storage configuration
237
+ stores: {
238
+ tm: 'BaseJsonlTmStore',
239
+ ops: 'FsOpsStore'
240
+ }
241
+ };
242
+ ```
243
+
244
+ ### Multi-Channel Configuration
245
+
246
+ ```javascript
247
+ export default {
248
+ channels: [
249
+ {
250
+ // Web app content
251
+ source: new FsSource({ globs: ['web/src/**/*.json'] }),
252
+ target: new FsTarget({ targetPath: (lang, id) => id.replace('/src/', `/dist/${lang}/`) })
253
+ },
254
+ {
255
+ // Mobile app content
256
+ source: new FsSource({ globs: ['mobile/strings/**/*.xml'] }),
257
+ target: new FsTarget({ targetPath: (lang, id) => id.replace('/strings/', `/strings-${lang}/`) })
258
+ }
259
+ ]
260
+ };
261
+ ```
262
+
263
+ ### Provider Chains
264
+
265
+ ```javascript
266
+ export default {
267
+ providers: [
268
+ { id: 'leverage', provider: 'InternalLeverage' },
269
+ { id: 'repetitions', provider: 'Repetition' },
270
+ { id: 'ai', provider: new GptAgent({ model: 'gpt-4' }) },
271
+ { id: 'fallback', provider: 'Invisicode' }
272
+ ]
273
+ };
48
274
  ```
49
- It will generate translated files based on the latest sources and translations in the TM.
50
275
 
51
- ### Working files
276
+ ## Error Handling
277
+
278
+ The CLI provides comprehensive error handling with actionable messages:
52
279
 
53
- L10n Monster maintains its working files in a hidden `.l10nmonster` directory at the root of the project. Working files are source-control friendly (json files with newlines) and can be checked in. On the other hand, they can also be destroyed and recreated on the fly if all you want to preserve is translations in your current files.
280
+ ```bash
281
+ # Configuration errors
282
+ Error: Configuration file not found: l10nmonster.config.mjs
283
+ Tip: Run 'l10n init' to create a basic configuration
54
284
 
55
- ## Demo
285
+ # Provider errors
286
+ Error: OpenAI API key not configured
287
+ Tip: Set OPENAI_API_KEY environment variable or configure apiKey in provider options
56
288
 
57
- ![Demo screen](tty.gif)
289
+ # Operation errors
290
+ Error: Translation job failed for provider 'gpt-4'
291
+ Tip: Check provider configuration and API limits
292
+ ```
58
293
 
59
- ## Basic Configuration
294
+ ## Performance Optimization
60
295
 
61
- At the root of your project there should be a file named `l10nmonster.cjs`. You can create it by hand, or you can use `l10n init` and use one of the configurators to get up and running in no time. Well, that's the plan, it's not implemented yet!
296
+ ### Parallel Processing
62
297
 
63
- The configuration must export a class that once instantiated provides the following properties:
298
+ ```bash
299
+ # Enable parallel operations
300
+ l10n translate --parallel 4
64
301
 
65
- * `sourceLang`: the default source language
66
- * `minimumQuality`: this is the minimum required quality for a string to be considered translated (anything below triggers a request to translate)
67
- * `source`: a source adapter to read input resources from
68
- * `resourceFilter`: a filter to process the specific resource format
69
- * `translationProvider`: a connector to the translation vendor
70
- * `target`: a target adapter to write translated resources to
71
- * `adapters`, `filters`, `translators`: built-in helpers (see below)
72
- * TODO: add the other properties that can be defined
302
+ # Provider-specific parallelism
303
+ l10n ops update --provider-parallel 2
304
+ ```
73
305
 
74
- ## Advanced CLI
306
+ ### Filtering and Batching
75
307
 
76
- The CLI support additional options to control its behavior:
308
+ ```bash
309
+ # Process specific file patterns
310
+ l10n translate --filter "*.json"
77
311
 
78
- * `-a, --arg <string>`: this is a user-defined argument that allows to customize the user config behavior
79
- * `-v, --verbose`: output additional debug information
312
+ # Batch operations by language
313
+ l10n translate --batch-by-language
80
314
 
81
- Some commands also allow additional options. For more information type `l10n help <command>`.
315
+ # Limit operation scope
316
+ l10n source snap --since "2024-01-01"
317
+ ```
82
318
 
83
- ## Advanced Configuration
319
+ ## Integration Examples
84
320
 
85
- There is also additional functionality in the configuration that can be useful, especially in environments with larger teams.
321
+ ### CI/CD Pipeline
86
322
 
87
- The the following properties can optionally be defined:
323
+ ```yaml
324
+ # GitHub Actions example
325
+ name: Localization
326
+ on: [push, pull_request]
88
327
 
89
- * `jobStore`: a durable persistence adapter to store translations
90
- * `translationProvider`: this can also be a function that given a job request returns the desired vendor (e.g. `(job) => job.targetLang === 'piggy' ? piggyTranslator : xliffTranslator`)
91
- * TODO: add the other properties that can be defined
328
+ jobs:
329
+ l10n:
330
+ runs-on: ubuntu-latest
331
+ steps:
332
+ - uses: actions/checkout@v3
333
+ - uses: actions/setup-node@v3
334
+ with:
335
+ node-version: '20'
336
+ - run: npm install -g @l10nmonster/cli
337
+ - run: l10n analyze
338
+ - run: l10n translate --dry-run
339
+ ```
92
340
 
93
- ### JSON Job Store
341
+ ### NPM Scripts
94
342
 
95
- ```js
96
- this.jobStore = new stores.JsonJobStore({
97
- jobsDir: 'translationJobs',
98
- });
343
+ ```json
344
+ {
345
+ "scripts": {
346
+ "l10n:analyze": "l10n analyze",
347
+ "l10n:translate": "l10n translate",
348
+ "l10n:update": "l10n ops update",
349
+ "l10n:sync": "l10n tm syncup && l10n tm syncdown"
350
+ }
351
+ }
99
352
  ```
100
353
 
101
- The JSON job store is appropriate for small dev teams where all translations are managed by a single person and there little possibility of conflicts among members. Translation jobs are stored locally in JSON file in a specified folder.
354
+ ## Troubleshooting
355
+
356
+ ### Common Issues
357
+
358
+ 1. **Configuration not found**
359
+ ```bash
360
+ # Ensure config file exists and has correct name
361
+ ls l10nmonster.config.mjs
362
+ ```
363
+
364
+ 2. **Module import errors**
365
+ ```bash
366
+ # Verify Node.js version (requires >= 22.11.0)
367
+ node --version
368
+ ```
369
+
370
+ 3. **Provider authentication**
371
+ ```bash
372
+ # Check environment variables
373
+ echo $OPENAI_API_KEY
374
+ ```
375
+
376
+ 4. **Performance issues**
377
+ ```bash
378
+ # Enable debug logging
379
+ l10n translate -v
380
+ ```
381
+
382
+ ## Migration from v2
383
+
384
+ ### Configuration Updates
385
+
386
+ 1. **Rename config file**: `l10nmonster.cjs` → `l10nmonster.config.mjs`
387
+ 2. **Update imports**: Use ESM import syntax
388
+ 3. **Update providers**: Many providers have new names and APIs
389
+ 4. **Update commands**: Some command names have changed
390
+
391
+ ### Command Mapping
392
+
393
+ | v2 Command | v3 Equivalent |
394
+ |------------|---------------|
395
+ | `l10n push` | `l10n translate && l10n ops update` |
396
+ | `l10n pull` | `l10n tm syncdown && l10n ops update` |
397
+ | `l10n status` | `l10n analyze` |
398
+ | `l10n grandfather` | Provider-based (configured in config) |
399
+ | `l10n leverage` | Provider-based (configured in config) |
400
+
401
+ For detailed migration guidance, see the [v3 Migration Guide](../v3.md).
402
+
403
+ ## Help and Support
404
+
405
+ ```bash
406
+ # Get general help
407
+ l10n help
408
+
409
+ # Get command-specific help
410
+ l10n help translate
411
+ l10n help ops
412
+ l10n help source
413
+
414
+ # Get provider information
415
+ l10n ops providers --info
416
+ ```
102
417
 
103
- * `jobsDir` is the directory containing translation jobs. It should be kept (e.g. checked into git) as it is needed to regenerate translated resources in a reliable way.
418
+ For more detailed documentation, see:
419
+ - [Main Documentation](../README.md)
420
+ - [Architecture Guide](../architecture.md)
421
+ - [Core Package Documentation](../core/README.md)
package/index.js ADDED
@@ -0,0 +1,88 @@
1
+ /* eslint-disable no-underscore-dangle */
2
+
3
+ import { Command, Argument, Option, InvalidArgumentError } from 'commander';
4
+ import { getVerbosity } from '@l10nmonster/core';
5
+
6
+ import path from 'path';
7
+ import { readFileSync } from 'fs';
8
+ const cliVersion = JSON.parse(readFileSync(path.join(import.meta.dirname, 'package.json'), 'utf-8')).version;
9
+
10
+ function intOptionParser(value) {
11
+ const parsedValue = parseInt(value, 10);
12
+ if (isNaN(parsedValue)) {
13
+ throw new InvalidArgumentError('Not an integer');
14
+ }
15
+ return parsedValue;
16
+ }
17
+
18
+ function configureCommand(cmd, Action, l10nRunner) {
19
+
20
+ /** @this Command */
21
+ const actionHandler = async function actionHandler() {
22
+ const options = this.opts();
23
+ // Need to hack into the guts of commander as it doesn't seem to expose argument names
24
+ // @ts-ignore
25
+ const args = Object.fromEntries(this._args.map((arg, idx) => [
26
+ arg._name,
27
+ arg.variadic ? this.args.slice(idx) : this.args[idx]
28
+ ]));
29
+ await l10nRunner(async l10n => {
30
+ await l10n[Action.name]({ ...options, ...args })
31
+ });
32
+ };
33
+ const help = Action.help;
34
+ cmd.description(help.description).action(actionHandler);
35
+ help.summary && cmd.summary(help.summary);
36
+ help.options && help.options.forEach(([ arg, desc, choices]) => {
37
+ if (choices) {
38
+ cmd.addOption(new Option(arg, desc).choices(choices));
39
+ } else {
40
+ cmd.option(arg, desc);
41
+ }
42
+ });
43
+ help.requiredOptions && help.requiredOptions.forEach(opt => cmd.requiredOption(...opt));
44
+ help.arguments && help.arguments.forEach(([ arg, desc, choices]) => {
45
+ if (choices) {
46
+ cmd.addArgument(new Argument(arg, desc).choices(choices));
47
+ } else {
48
+ cmd.argument(arg, desc);
49
+ }
50
+ });
51
+ }
52
+
53
+ export default async function runMonsterCLI(monsterConfig, cliCommand) {
54
+ const monsterCLI = new Command();
55
+ monsterCLI
56
+ .name('l10n')
57
+ .version(cliVersion, '--version', 'output the current version number')
58
+ .description('Continuous localization for the rest of us.')
59
+ .option('-v, --verbose [level]', '0=error, 1=warning, 2=info, 3=verbose', intOptionParser)
60
+ .option('--regression', 'keep variables constant during regression testing');
61
+ try {
62
+ const l10nRunner = async (cb) => {
63
+ const { verbose, regression } = monsterCLI.opts();
64
+ await monsterConfig
65
+ .verbose(verbose)
66
+ .regression(regression)
67
+ .run(async mm => await cb(mm.l10n));
68
+ };
69
+ monsterConfig.actions.forEach(Action => {
70
+ const cmd = monsterCLI.command(Action.name);
71
+ if (Action.subActions) {
72
+ Action.help.description && cmd.description(Action.help.description);
73
+ Action.subActions.forEach(subAction => {
74
+ const subName = subAction.name.split('_')[1];
75
+ const subCmd = cmd.command(subName);
76
+ configureCommand(subCmd, subAction, l10nRunner);
77
+ });
78
+ } else {
79
+ configureCommand(cmd, Action, l10nRunner);
80
+ }
81
+ });
82
+ const argv = typeof cliCommand === 'string' ? cliCommand.split(' ') : cliCommand;
83
+ await monsterCLI.parseAsync(argv);
84
+ } catch(e) {
85
+ console.error(`Unable to run CLI: ${(getVerbosity() > 1 ? e.stack : e.message) || e}`);
86
+ process.exit(1);
87
+ }
88
+ }
package/l10n.mjs ADDED
@@ -0,0 +1,49 @@
1
+ #!/usr/bin/env node
2
+
3
+ import runMonsterCLI from './index.js';
4
+ import { resolve, dirname, join } from 'path';
5
+ import { existsSync } from 'fs';
6
+
7
+ function findConfigFile(startDir = process.cwd()) {
8
+ let currentDir = resolve(startDir);
9
+ const configFileName = 'l10nmonster.config.mjs';
10
+
11
+ while (true) {
12
+ const configPath = join(currentDir, configFileName);
13
+ if (existsSync(configPath)) {
14
+ return configPath;
15
+ }
16
+
17
+ const parentDir = dirname(currentDir);
18
+ if (parentDir === currentDir) {
19
+ // We've reached the root directory
20
+ break;
21
+ }
22
+ currentDir = parentDir;
23
+ }
24
+
25
+ return null;
26
+ }
27
+
28
+ try {
29
+ const configPath = findConfigFile();
30
+ if (!configPath) {
31
+ console.error('Error: Could not find l10nmonster.config.mjs in current directory or any parent directory.');
32
+ console.error('Please ensure the config file exists in your project root or current working directory.');
33
+ process.exit(1);
34
+ }
35
+
36
+ const config = await import(configPath);
37
+ await runMonsterCLI(config.default);
38
+ } catch (error) {
39
+ if (error.code === 'ERR_MODULE_NOT_FOUND') {
40
+ console.error('Error: Could not load l10nmonster.config.mjs - the file may have syntax errors or missing dependencies.');
41
+ console.error('Details:', error.message);
42
+ } else if (error.code === 'ENOENT') {
43
+ console.error('Error: Config file was found but could not be accessed. Please check file permissions.');
44
+ console.error('Details:', error.message);
45
+ } else {
46
+ console.error('Error running l10nmonster CLI:', error.message);
47
+ }
48
+ process.exit(1);
49
+ }