locize-cli 12.3.0 → 12.4.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/CHANGELOG.md +22 -0
- package/README.md +3 -3
- package/dist/cjs/cli.js +5 -5
- package/dist/cjs/convertToDesiredFormat.js +9 -11
- package/dist/cjs/convertToFlatFormat.js +3 -7
- package/dist/cjs/formats.js +2 -2
- package/dist/cjs/package.json +1 -1
- package/dist/cjs/request.js +15 -6
- package/dist/esm/cli.js +5 -5
- package/dist/esm/convertToDesiredFormat.js +9 -11
- package/dist/esm/convertToFlatFormat.js +3 -7
- package/dist/esm/formats.js +2 -2
- package/dist/esm/request.js +15 -6
- package/package.json +12 -12
package/CHANGELOG.md
CHANGED
|
@@ -5,6 +5,28 @@ All notable changes to this project will be documented in this file.
|
|
|
5
5
|
Project versioning adheres to [Semantic Versioning](http://semver.org/).
|
|
6
6
|
Change log format is based on [Keep a Changelog](http://keepachangelog.com/).
|
|
7
7
|
|
|
8
|
+
## [12.4.0](https://github.com/locize/locize-cli/compare/v12.3.1...v12.4.0) - 2026-07-06
|
|
9
|
+
|
|
10
|
+
- xliff: proper support for inline elements (e.g. Angular's
|
|
11
|
+
`<x id="INTERPOLATION" equiv-text="{{ count }}"/>` placeholders, or
|
|
12
|
+
`<ph>`/`<pc>`/`<g>`/`<bpt>` tags from other TMS exports). On import they are
|
|
13
|
+
flattened into stable placeholder tokens inside the translation value
|
|
14
|
+
(previously such values were silently corrupted into arrays); on export the
|
|
15
|
+
tokens are restored as real XLIFF inline elements, so Angular `.xlf` files
|
|
16
|
+
round-trip cleanly. Requires `xliff` >= 6.4.0 (the encoding is implemented
|
|
17
|
+
in the library and shared with the locize CAT editor).
|
|
18
|
+
- xliff: new formats `xliff21`, `xliff22`, `xlf21`, `xlf22` for XLIFF 2.1 and
|
|
19
|
+
2.2 (import of 2.1/2.2 files already worked via `xliff2`; exports now emit
|
|
20
|
+
the matching `version` attribute). XLIFF 2.1 exports carry the
|
|
21
|
+
OASIS-correct `urn:oasis:names:tc:xliff:document:2.0` core namespace.
|
|
22
|
+
|
|
23
|
+
## [12.3.1](https://github.com/locize/locize-cli/compare/v12.3.0...v12.3.1) - 2026-06-18
|
|
24
|
+
|
|
25
|
+
- request: retry transient network failures with exponential backoff and jitter
|
|
26
|
+
instead of a fixed 5s delay. This spreads retries so that bursts of
|
|
27
|
+
concurrent CLI runs (e.g. in CI) no longer retry in lockstep and hit the API
|
|
28
|
+
at the same instant.
|
|
29
|
+
|
|
8
30
|
## [12.3.0](https://github.com/locize/locize-cli/compare/v12.2.0...v12.3.0) - 2026-06-10
|
|
9
31
|
|
|
10
32
|
- sync: creates missing remote languages on the fly. A project without any
|
package/README.md
CHANGED
|
@@ -105,7 +105,7 @@ locize download --cdn-type standard
|
|
|
105
105
|
locize download --cdn-type pro
|
|
106
106
|
```
|
|
107
107
|
|
|
108
|
-
or add a format like (json, nested, flat, xliff2, xliff12, xlf2, xlf12, android, yaml, yaml-rails, yaml-rails-ns, yaml-nested, yml, yml-rails, yml-nested, csv, xlsx, po, strings, resx, fluent, tmx, laravel, properties, xcstrings)
|
|
108
|
+
or add a format like (json, nested, flat, xliff2, xliff21, xliff22, xliff12, xlf2, xlf21, xlf22, xlf12, android, yaml, yaml-rails, yaml-rails-ns, yaml-nested, yml, yml-rails, yml-nested, csv, xlsx, po, strings, resx, fluent, tmx, laravel, properties, xcstrings)
|
|
109
109
|
|
|
110
110
|
*Special formats:*
|
|
111
111
|
- use yaml-rails to have the language code in the resulting yaml as root object
|
|
@@ -160,7 +160,7 @@ locize sync --cdn-type standard --api-key my-api-key-d9de-4f55-9855-a9ef0ed44672
|
|
|
160
160
|
locize sync --cdn-type pro --api-key my-api-key-d9de-4f55-9855-a9ef0ed44672 --project-id my-project-id-93e1-442a-ab35-24331fa294ba
|
|
161
161
|
```
|
|
162
162
|
|
|
163
|
-
or add a format like (json, nested, flat, xliff2, xliff12, xlf2, xlf12, android, yaml, yaml-rails, yaml-rails-ns, yaml-nested, yml, yml-rails, yml-nested, csv, xlsx, po, strings, resx, fluent, tmx, laravel, properties, xcstrings)
|
|
163
|
+
or add a format like (json, nested, flat, xliff2, xliff21, xliff22, xliff12, xlf2, xlf21, xlf22, xlf12, android, yaml, yaml-rails, yaml-rails-ns, yaml-nested, yml, yml-rails, yml-nested, csv, xlsx, po, strings, resx, fluent, tmx, laravel, properties, xcstrings)
|
|
164
164
|
|
|
165
165
|
*Special formats:*
|
|
166
166
|
- use yaml-rails to have the language code in the resulting yaml as root object
|
|
@@ -227,7 +227,7 @@ locize save-missing --cdn-type standard --api-key my-api-key-d9de-4f55-9855-a9ef
|
|
|
227
227
|
locize save-missing --cdn-type pro --api-key my-api-key-d9de-4f55-9855-a9ef0ed44672 --project-id my-project-id-93e1-442a-ab35-24331fa294ba
|
|
228
228
|
```
|
|
229
229
|
|
|
230
|
-
or add a format like (json, nested, flat, xliff2, xliff12, xlf2, xlf12, android, yaml, yaml-rails, yaml-rails-ns, yaml-nested, yml, yml-rails, yml-nested, csv, xlsx, po, strings, resx, fluent, tmx, laravel, properties, xcstrings)
|
|
230
|
+
or add a format like (json, nested, flat, xliff2, xliff21, xliff22, xliff12, xlf2, xlf21, xlf22, xlf12, android, yaml, yaml-rails, yaml-rails-ns, yaml-nested, yml, yml-rails, yml-nested, csv, xlsx, po, strings, resx, fluent, tmx, laravel, properties, xcstrings)
|
|
231
231
|
|
|
232
232
|
```sh
|
|
233
233
|
locize save-missing --api-key my-api-key-d9de-4f55-9855-a9ef0ed44672 --project-id my-project-id-93e1-442a-ab35-24331fa294ba --format android
|
package/dist/cjs/cli.js
CHANGED
|
@@ -50,7 +50,7 @@ const program = new commander.Command();
|
|
|
50
50
|
|
|
51
51
|
program
|
|
52
52
|
.description('The official locize CLI.')
|
|
53
|
-
.version('12.
|
|
53
|
+
.version('12.4.0'); // This string is replaced with the actual version at build time by rollup
|
|
54
54
|
// .option('-a, --api-endpoint <url>', `Specify the api-endpoint url that should be used (default: ${defaultApiEndpoint})`)
|
|
55
55
|
// .option('-C, --config-path <configPath>', `Specify the path to the optional locize config file (default: ${configInWorkingDirectory} or ${configInHome})`);
|
|
56
56
|
|
|
@@ -263,7 +263,7 @@ program
|
|
|
263
263
|
.option('-n, --namespace <ns>', 'The namespace that should be targeted')
|
|
264
264
|
.option('-p, --path <path>', `Specify the path that should be used (default: ${process.cwd()})`, process.cwd())
|
|
265
265
|
.option('-k, --api-key <apiKey>', 'The api-key that should be used')
|
|
266
|
-
.option('-f, --format <json>', 'File format of namespaces (default: json; [nested, flat, xliff2, xliff12, xlf2, xlf12, android, yaml, yaml-rails, yaml-rails-ns, yaml-nested, yml, yml-rails, yml-nested, csv, xlsx, po, strings, resx, fluent, tmx, laravel, properties, xcstrings])', 'json')
|
|
266
|
+
.option('-f, --format <json>', 'File format of namespaces (default: json; [nested, flat, xliff2, xliff21, xliff22, xliff12, xlf2, xlf21, xlf22, xlf12, android, yaml, yaml-rails, yaml-rails-ns, yaml-nested, yml, yml-rails, yml-nested, csv, xlsx, po, strings, resx, fluent, tmx, laravel, properties, xcstrings])', 'json')
|
|
267
267
|
.option('-s, --skip-empty <true|false>', 'Skips to download empty files (default: true)', 'true')
|
|
268
268
|
.option('-P, --language-folder-prefix <prefix>', 'This will be added as a local folder name prefix in front of the language.', '')
|
|
269
269
|
.option('-m, --path-mask <mask>', 'This will define the folder and file structure; do not add a file extension (default: {{language}}/{{namespace}})', `{{language}}${path.sep}{{namespace}}`)
|
|
@@ -412,7 +412,7 @@ program
|
|
|
412
412
|
.option('-p, --path <path>', `Specify the path that should be used (default: ${process.cwd()})`, process.cwd())
|
|
413
413
|
.option('-B, --backup-deleted-path <path>', 'Saves the segments that will be deleted in this path')
|
|
414
414
|
.option('-A, --auto-create-path <true|false>', 'This will automatically make sure the --path is created. (default: true)', 'true')
|
|
415
|
-
.option('-f, --format <json>', 'File format of namespaces (default: json; [nested, flat, xliff2, xliff12, xlf2, xlf12, android, yaml, yaml-rails, yaml-rails-ns, yaml-nested, yml, yml-rails, yml-nested, csv, xlsx, po, strings, resx, fluent, tmx, laravel, properties, xcstrings])', 'json')
|
|
415
|
+
.option('-f, --format <json>', 'File format of namespaces (default: json; [nested, flat, xliff2, xliff21, xliff22, xliff12, xlf2, xlf21, xlf22, xlf12, android, yaml, yaml-rails, yaml-rails-ns, yaml-nested, yml, yml-rails, yml-nested, csv, xlsx, po, strings, resx, fluent, tmx, laravel, properties, xcstrings])', 'json')
|
|
416
416
|
.option('-s, --skip-empty <true|false>', 'Skips to download empty files (default: false)', 'false')
|
|
417
417
|
.option('-c, --clean <true|false>', 'Removes all local files by removing the whole folder (default: false)', 'false')
|
|
418
418
|
.option('--cf, --clean-local-files <true|false>', 'Removes all local files without removing any folder (default: false)', 'false')
|
|
@@ -540,7 +540,7 @@ program
|
|
|
540
540
|
.option('-i, --project-id <projectId>', 'The project-id that should be used')
|
|
541
541
|
.option('-v, --ver <version>', 'Found namespaces will be matched to this version (default: latest)')
|
|
542
542
|
.option('-p, --path <path>', `Specify the path that should be used (default: ${process.cwd()})`, process.cwd())
|
|
543
|
-
.option('-f, --format <json>', 'File format of namespaces (default: json; [nested, flat, xliff2, xliff12, xlf2, xlf12, android, yaml, yaml-rails, yaml-rails-ns, yaml-nested, yml, yml-rails, yml-nested, csv, xlsx, po, strings, resx, fluent, tmx, laravel, properties, xcstrings])', 'json')
|
|
543
|
+
.option('-f, --format <json>', 'File format of namespaces (default: json; [nested, flat, xliff2, xliff21, xliff22, xliff12, xlf2, xlf21, xlf22, xlf12, android, yaml, yaml-rails, yaml-rails-ns, yaml-nested, yml, yml-rails, yml-nested, csv, xlsx, po, strings, resx, fluent, tmx, laravel, properties, xcstrings])', 'json')
|
|
544
544
|
.option('-m, --path-mask <mask>', 'This will define the folder and file structure; do not add a file extension (default: {{language}}/{{namespace}})', `{{language}}${path.sep}{{namespace}}`)
|
|
545
545
|
.option('-P, --language-folder-prefix <prefix>', 'This will be added as a local folder name prefix in front of the language.', '')
|
|
546
546
|
.option('-d, --dry <true|false>', 'Dry run (default: false)', 'false')
|
|
@@ -835,7 +835,7 @@ program
|
|
|
835
835
|
.command('format [fileOrDirectory]')
|
|
836
836
|
.alias('ft')
|
|
837
837
|
.description('format local files')
|
|
838
|
-
.option('-f, --format <json>', 'File format of namespaces (default: json; [nested, flat, xliff2, xliff12, xlf2, xlf12, android, yaml, yaml-rails, yaml-rails-ns, yaml-nested, yml, yml-rails, yml-nested, csv, xlsx, po, strings, resx, fluent, tmx, laravel, properties, xcstrings])', 'json')
|
|
838
|
+
.option('-f, --format <json>', 'File format of namespaces (default: json; [nested, flat, xliff2, xliff21, xliff22, xliff12, xlf2, xlf21, xlf22, xlf12, android, yaml, yaml-rails, yaml-rails-ns, yaml-nested, yml, yml-rails, yml-nested, csv, xlsx, po, strings, resx, fluent, tmx, laravel, properties, xcstrings])', 'json')
|
|
839
839
|
.option('-l, --reference-language <lng>', 'Some format conversions need to know the reference language.', 'en')
|
|
840
840
|
.option('-d, --dry <true|false>', 'Dry run (default: false)', 'false')
|
|
841
841
|
.action((fileOrDirectory, options) => {
|
|
@@ -143,19 +143,17 @@ const convertToDesiredFormat = async (
|
|
|
143
143
|
});
|
|
144
144
|
return stringsFile.compile(data)
|
|
145
145
|
}
|
|
146
|
-
if (
|
|
147
|
-
opt.format === 'xliff2' ||
|
|
148
|
-
opt.format === 'xliff12' ||
|
|
149
|
-
opt.format === 'xlf2' ||
|
|
150
|
-
opt.format === 'xlf12'
|
|
151
|
-
) {
|
|
152
|
-
const fn =
|
|
153
|
-
opt.format === 'xliff12' || opt.format === 'xlf12'
|
|
154
|
-
? xliff.createxliff12
|
|
155
|
-
: xliff.createxliff;
|
|
146
|
+
if (['xliff2', 'xliff21', 'xliff22', 'xliff12', 'xlf2', 'xlf21', 'xlf22', 'xlf12'].indexOf(opt.format) > -1) {
|
|
156
147
|
const refNs = await opt.getNamespace(opt, opt.referenceLanguage, namespace);
|
|
157
148
|
const prepared = combineSubkeyPreprocessor.prepareExport(refNs, flatten(data));
|
|
158
|
-
|
|
149
|
+
const js = await xliff.createjs(opt.referenceLanguage, lng, prepared.ref, prepared.trg, namespace);
|
|
150
|
+
if (opt.format === 'xliff12' || opt.format === 'xlf12') {
|
|
151
|
+
return await xliff.jsToXliff12(js, { inlineAsString: true })
|
|
152
|
+
}
|
|
153
|
+
let targetXliffVersion; // default 2.0
|
|
154
|
+
if (opt.format === 'xliff21' || opt.format === 'xlf21') targetXliffVersion = '2.1';
|
|
155
|
+
if (opt.format === 'xliff22' || opt.format === 'xlf22') targetXliffVersion = '2.2';
|
|
156
|
+
return await xliff.js2xliff(js, { targetXliffVersion, inlineAsString: true })
|
|
159
157
|
}
|
|
160
158
|
if (opt.format === 'resx') {
|
|
161
159
|
return await resx.js2resx(flatten(data))
|
|
@@ -116,17 +116,13 @@ const convertToFlatFormat = async (opt, data, lng) => {
|
|
|
116
116
|
// CRLF => LF
|
|
117
117
|
return stringsFile.parse(data.toString().replace(/\r\n/g, '\n'), false)
|
|
118
118
|
}
|
|
119
|
-
if (
|
|
120
|
-
|
|
121
|
-
opt.format === 'xliff12' ||
|
|
122
|
-
opt.format === 'xlf2' ||
|
|
123
|
-
opt.format === 'xlf12'
|
|
124
|
-
) {
|
|
119
|
+
if (['xliff2', 'xliff21', 'xliff22', 'xliff12', 'xlf2', 'xlf21', 'xlf22', 'xlf12'].indexOf(opt.format) > -1) {
|
|
120
|
+
// xliff2js parses 2.0, 2.1 and 2.2
|
|
125
121
|
const fn =
|
|
126
122
|
opt.format === 'xliff12' || opt.format === 'xlf12'
|
|
127
123
|
? xliff.xliff12ToJs
|
|
128
124
|
: xliff.xliff2js;
|
|
129
|
-
const res = await fn(data.toString());
|
|
125
|
+
const res = await fn(data.toString(), { inlineAsString: true });
|
|
130
126
|
res.resources = res.resources || {};
|
|
131
127
|
const ns = Object.keys(res.resources)[0];
|
|
132
128
|
const orgRes = res.resources[ns] || res.resources;
|
package/dist/cjs/formats.js
CHANGED
|
@@ -10,8 +10,8 @@ const fileExtensionsMap = {
|
|
|
10
10
|
'.yaml': ['yaml', 'yaml-rails', 'yaml-rails-ns', 'yaml-nested'],
|
|
11
11
|
'.yml': ['yml', 'yml-rails', 'yml-rails-ns', 'yml-nested'],
|
|
12
12
|
'.xlsx': ['xlsx'],
|
|
13
|
-
'.xliff': ['xliff2', 'xliff12'],
|
|
14
|
-
'.xlf': ['xlf2', 'xlf12'],
|
|
13
|
+
'.xliff': ['xliff2', 'xliff21', 'xliff22', 'xliff12'],
|
|
14
|
+
'.xlf': ['xlf2', 'xlf21', 'xlf22', 'xlf12'],
|
|
15
15
|
'.ftl': ['fluent'],
|
|
16
16
|
'.tmx': ['tmx'],
|
|
17
17
|
'.php': ['laravel'],
|
package/dist/cjs/package.json
CHANGED
package/dist/cjs/request.js
CHANGED
|
@@ -7,6 +7,10 @@ var CacheableLookup = require('cacheable-lookup');
|
|
|
7
7
|
const cacheable = new CacheableLookup();
|
|
8
8
|
cacheable.install(https.globalAgent);
|
|
9
9
|
|
|
10
|
+
const RETRY_MAX_ATTEMPTS = 3;
|
|
11
|
+
const RETRY_BASE_DELAY_MS = 2000;
|
|
12
|
+
const RETRY_MAX_DELAY_MS = 30000;
|
|
13
|
+
|
|
10
14
|
const httpProxy = process.env.http_proxy || process.env.HTTP_PROXY || process.env.https_proxy || process.env.HTTPS_PROXY;
|
|
11
15
|
const isRetriableError = (err) => {
|
|
12
16
|
return err && err.message && (
|
|
@@ -39,7 +43,7 @@ async function request (url, options) {
|
|
|
39
43
|
}
|
|
40
44
|
|
|
41
45
|
options.headers = options.headers || {};
|
|
42
|
-
options.headers['User-Agent'] = `locize-cli/v12.
|
|
46
|
+
options.headers['User-Agent'] = `locize-cli/v12.4.0 (node/${process.version}; ${process.platform} ${process.arch})`; // This string is replaced with the actual version at build time by rollup
|
|
43
47
|
options.headers['X-User-Agent'] = options.headers['User-Agent'];
|
|
44
48
|
if (options.body || options.method !== 'get') options.headers['Content-Type'] = 'application/json';
|
|
45
49
|
if (options.body) {
|
|
@@ -49,19 +53,24 @@ async function request (url, options) {
|
|
|
49
53
|
}
|
|
50
54
|
if (options.headers['Authorization'] === undefined) delete options.headers['Authorization'];
|
|
51
55
|
|
|
52
|
-
async function retriableFetch (
|
|
56
|
+
async function retriableFetch (retriesLeft, attempt = 0) {
|
|
53
57
|
try {
|
|
54
58
|
const response = await fetch(url, options);
|
|
55
59
|
const result = await handleResponse(response);
|
|
56
60
|
return result
|
|
57
61
|
} catch (err) {
|
|
58
|
-
if (
|
|
62
|
+
if (retriesLeft < 1) throw err
|
|
59
63
|
if (!isRetriableError(err)) throw err
|
|
60
|
-
|
|
61
|
-
|
|
64
|
+
// Exponential backoff with equal jitter: spreads retries so a burst of
|
|
65
|
+
// concurrent CLI runs (e.g. CI) doesn't all retry in lockstep and
|
|
66
|
+
// hammer the API at the same instant.
|
|
67
|
+
const expDelay = Math.min(RETRY_MAX_DELAY_MS, RETRY_BASE_DELAY_MS * (2 ** attempt));
|
|
68
|
+
const delay = Math.round(expDelay / 2 + Math.random() * (expDelay / 2));
|
|
69
|
+
await new Promise(resolve => setTimeout(resolve, delay));
|
|
70
|
+
return retriableFetch(retriesLeft - 1, attempt + 1)
|
|
62
71
|
}
|
|
63
72
|
}
|
|
64
|
-
return retriableFetch(
|
|
73
|
+
return retriableFetch(RETRY_MAX_ATTEMPTS)
|
|
65
74
|
}
|
|
66
75
|
|
|
67
76
|
module.exports = request;
|
package/dist/esm/cli.js
CHANGED
|
@@ -48,7 +48,7 @@ const program = new Command();
|
|
|
48
48
|
|
|
49
49
|
program
|
|
50
50
|
.description('The official locize CLI.')
|
|
51
|
-
.version('12.
|
|
51
|
+
.version('12.4.0'); // This string is replaced with the actual version at build time by rollup
|
|
52
52
|
// .option('-a, --api-endpoint <url>', `Specify the api-endpoint url that should be used (default: ${defaultApiEndpoint})`)
|
|
53
53
|
// .option('-C, --config-path <configPath>', `Specify the path to the optional locize config file (default: ${configInWorkingDirectory} or ${configInHome})`);
|
|
54
54
|
|
|
@@ -261,7 +261,7 @@ program
|
|
|
261
261
|
.option('-n, --namespace <ns>', 'The namespace that should be targeted')
|
|
262
262
|
.option('-p, --path <path>', `Specify the path that should be used (default: ${process.cwd()})`, process.cwd())
|
|
263
263
|
.option('-k, --api-key <apiKey>', 'The api-key that should be used')
|
|
264
|
-
.option('-f, --format <json>', 'File format of namespaces (default: json; [nested, flat, xliff2, xliff12, xlf2, xlf12, android, yaml, yaml-rails, yaml-rails-ns, yaml-nested, yml, yml-rails, yml-nested, csv, xlsx, po, strings, resx, fluent, tmx, laravel, properties, xcstrings])', 'json')
|
|
264
|
+
.option('-f, --format <json>', 'File format of namespaces (default: json; [nested, flat, xliff2, xliff21, xliff22, xliff12, xlf2, xlf21, xlf22, xlf12, android, yaml, yaml-rails, yaml-rails-ns, yaml-nested, yml, yml-rails, yml-nested, csv, xlsx, po, strings, resx, fluent, tmx, laravel, properties, xcstrings])', 'json')
|
|
265
265
|
.option('-s, --skip-empty <true|false>', 'Skips to download empty files (default: true)', 'true')
|
|
266
266
|
.option('-P, --language-folder-prefix <prefix>', 'This will be added as a local folder name prefix in front of the language.', '')
|
|
267
267
|
.option('-m, --path-mask <mask>', 'This will define the folder and file structure; do not add a file extension (default: {{language}}/{{namespace}})', `{{language}}${path.sep}{{namespace}}`)
|
|
@@ -410,7 +410,7 @@ program
|
|
|
410
410
|
.option('-p, --path <path>', `Specify the path that should be used (default: ${process.cwd()})`, process.cwd())
|
|
411
411
|
.option('-B, --backup-deleted-path <path>', 'Saves the segments that will be deleted in this path')
|
|
412
412
|
.option('-A, --auto-create-path <true|false>', 'This will automatically make sure the --path is created. (default: true)', 'true')
|
|
413
|
-
.option('-f, --format <json>', 'File format of namespaces (default: json; [nested, flat, xliff2, xliff12, xlf2, xlf12, android, yaml, yaml-rails, yaml-rails-ns, yaml-nested, yml, yml-rails, yml-nested, csv, xlsx, po, strings, resx, fluent, tmx, laravel, properties, xcstrings])', 'json')
|
|
413
|
+
.option('-f, --format <json>', 'File format of namespaces (default: json; [nested, flat, xliff2, xliff21, xliff22, xliff12, xlf2, xlf21, xlf22, xlf12, android, yaml, yaml-rails, yaml-rails-ns, yaml-nested, yml, yml-rails, yml-nested, csv, xlsx, po, strings, resx, fluent, tmx, laravel, properties, xcstrings])', 'json')
|
|
414
414
|
.option('-s, --skip-empty <true|false>', 'Skips to download empty files (default: false)', 'false')
|
|
415
415
|
.option('-c, --clean <true|false>', 'Removes all local files by removing the whole folder (default: false)', 'false')
|
|
416
416
|
.option('--cf, --clean-local-files <true|false>', 'Removes all local files without removing any folder (default: false)', 'false')
|
|
@@ -538,7 +538,7 @@ program
|
|
|
538
538
|
.option('-i, --project-id <projectId>', 'The project-id that should be used')
|
|
539
539
|
.option('-v, --ver <version>', 'Found namespaces will be matched to this version (default: latest)')
|
|
540
540
|
.option('-p, --path <path>', `Specify the path that should be used (default: ${process.cwd()})`, process.cwd())
|
|
541
|
-
.option('-f, --format <json>', 'File format of namespaces (default: json; [nested, flat, xliff2, xliff12, xlf2, xlf12, android, yaml, yaml-rails, yaml-rails-ns, yaml-nested, yml, yml-rails, yml-nested, csv, xlsx, po, strings, resx, fluent, tmx, laravel, properties, xcstrings])', 'json')
|
|
541
|
+
.option('-f, --format <json>', 'File format of namespaces (default: json; [nested, flat, xliff2, xliff21, xliff22, xliff12, xlf2, xlf21, xlf22, xlf12, android, yaml, yaml-rails, yaml-rails-ns, yaml-nested, yml, yml-rails, yml-nested, csv, xlsx, po, strings, resx, fluent, tmx, laravel, properties, xcstrings])', 'json')
|
|
542
542
|
.option('-m, --path-mask <mask>', 'This will define the folder and file structure; do not add a file extension (default: {{language}}/{{namespace}})', `{{language}}${path.sep}{{namespace}}`)
|
|
543
543
|
.option('-P, --language-folder-prefix <prefix>', 'This will be added as a local folder name prefix in front of the language.', '')
|
|
544
544
|
.option('-d, --dry <true|false>', 'Dry run (default: false)', 'false')
|
|
@@ -833,7 +833,7 @@ program
|
|
|
833
833
|
.command('format [fileOrDirectory]')
|
|
834
834
|
.alias('ft')
|
|
835
835
|
.description('format local files')
|
|
836
|
-
.option('-f, --format <json>', 'File format of namespaces (default: json; [nested, flat, xliff2, xliff12, xlf2, xlf12, android, yaml, yaml-rails, yaml-rails-ns, yaml-nested, yml, yml-rails, yml-nested, csv, xlsx, po, strings, resx, fluent, tmx, laravel, properties, xcstrings])', 'json')
|
|
836
|
+
.option('-f, --format <json>', 'File format of namespaces (default: json; [nested, flat, xliff2, xliff21, xliff22, xliff12, xlf2, xlf21, xlf22, xlf12, android, yaml, yaml-rails, yaml-rails-ns, yaml-nested, yml, yml-rails, yml-nested, csv, xlsx, po, strings, resx, fluent, tmx, laravel, properties, xcstrings])', 'json')
|
|
837
837
|
.option('-l, --reference-language <lng>', 'Some format conversions need to know the reference language.', 'en')
|
|
838
838
|
.option('-d, --dry <true|false>', 'Dry run (default: false)', 'false')
|
|
839
839
|
.action((fileOrDirectory, options) => {
|
|
@@ -141,19 +141,17 @@ const convertToDesiredFormat = async (
|
|
|
141
141
|
});
|
|
142
142
|
return stringsFile.compile(data)
|
|
143
143
|
}
|
|
144
|
-
if (
|
|
145
|
-
opt.format === 'xliff2' ||
|
|
146
|
-
opt.format === 'xliff12' ||
|
|
147
|
-
opt.format === 'xlf2' ||
|
|
148
|
-
opt.format === 'xlf12'
|
|
149
|
-
) {
|
|
150
|
-
const fn =
|
|
151
|
-
opt.format === 'xliff12' || opt.format === 'xlf12'
|
|
152
|
-
? xliff.createxliff12
|
|
153
|
-
: xliff.createxliff;
|
|
144
|
+
if (['xliff2', 'xliff21', 'xliff22', 'xliff12', 'xlf2', 'xlf21', 'xlf22', 'xlf12'].indexOf(opt.format) > -1) {
|
|
154
145
|
const refNs = await opt.getNamespace(opt, opt.referenceLanguage, namespace);
|
|
155
146
|
const prepared = prepareExport(refNs, flatten(data));
|
|
156
|
-
|
|
147
|
+
const js = await xliff.createjs(opt.referenceLanguage, lng, prepared.ref, prepared.trg, namespace);
|
|
148
|
+
if (opt.format === 'xliff12' || opt.format === 'xlf12') {
|
|
149
|
+
return await xliff.jsToXliff12(js, { inlineAsString: true })
|
|
150
|
+
}
|
|
151
|
+
let targetXliffVersion; // default 2.0
|
|
152
|
+
if (opt.format === 'xliff21' || opt.format === 'xlf21') targetXliffVersion = '2.1';
|
|
153
|
+
if (opt.format === 'xliff22' || opt.format === 'xlf22') targetXliffVersion = '2.2';
|
|
154
|
+
return await xliff.js2xliff(js, { targetXliffVersion, inlineAsString: true })
|
|
157
155
|
}
|
|
158
156
|
if (opt.format === 'resx') {
|
|
159
157
|
return await resx.js2resx(flatten(data))
|
|
@@ -114,17 +114,13 @@ const convertToFlatFormat = async (opt, data, lng) => {
|
|
|
114
114
|
// CRLF => LF
|
|
115
115
|
return stringsFile.parse(data.toString().replace(/\r\n/g, '\n'), false)
|
|
116
116
|
}
|
|
117
|
-
if (
|
|
118
|
-
|
|
119
|
-
opt.format === 'xliff12' ||
|
|
120
|
-
opt.format === 'xlf2' ||
|
|
121
|
-
opt.format === 'xlf12'
|
|
122
|
-
) {
|
|
117
|
+
if (['xliff2', 'xliff21', 'xliff22', 'xliff12', 'xlf2', 'xlf21', 'xlf22', 'xlf12'].indexOf(opt.format) > -1) {
|
|
118
|
+
// xliff2js parses 2.0, 2.1 and 2.2
|
|
123
119
|
const fn =
|
|
124
120
|
opt.format === 'xliff12' || opt.format === 'xlf12'
|
|
125
121
|
? xliff.xliff12ToJs
|
|
126
122
|
: xliff.xliff2js;
|
|
127
|
-
const res = await fn(data.toString());
|
|
123
|
+
const res = await fn(data.toString(), { inlineAsString: true });
|
|
128
124
|
res.resources = res.resources || {};
|
|
129
125
|
const ns = Object.keys(res.resources)[0];
|
|
130
126
|
const orgRes = res.resources[ns] || res.resources;
|
package/dist/esm/formats.js
CHANGED
|
@@ -8,8 +8,8 @@ const fileExtensionsMap = {
|
|
|
8
8
|
'.yaml': ['yaml', 'yaml-rails', 'yaml-rails-ns', 'yaml-nested'],
|
|
9
9
|
'.yml': ['yml', 'yml-rails', 'yml-rails-ns', 'yml-nested'],
|
|
10
10
|
'.xlsx': ['xlsx'],
|
|
11
|
-
'.xliff': ['xliff2', 'xliff12'],
|
|
12
|
-
'.xlf': ['xlf2', 'xlf12'],
|
|
11
|
+
'.xliff': ['xliff2', 'xliff21', 'xliff22', 'xliff12'],
|
|
12
|
+
'.xlf': ['xlf2', 'xlf21', 'xlf22', 'xlf12'],
|
|
13
13
|
'.ftl': ['fluent'],
|
|
14
14
|
'.tmx': ['tmx'],
|
|
15
15
|
'.php': ['laravel'],
|
package/dist/esm/request.js
CHANGED
|
@@ -5,6 +5,10 @@ import CacheableLookup from 'cacheable-lookup';
|
|
|
5
5
|
const cacheable = new CacheableLookup();
|
|
6
6
|
cacheable.install(https.globalAgent);
|
|
7
7
|
|
|
8
|
+
const RETRY_MAX_ATTEMPTS = 3;
|
|
9
|
+
const RETRY_BASE_DELAY_MS = 2000;
|
|
10
|
+
const RETRY_MAX_DELAY_MS = 30000;
|
|
11
|
+
|
|
8
12
|
const httpProxy = process.env.http_proxy || process.env.HTTP_PROXY || process.env.https_proxy || process.env.HTTPS_PROXY;
|
|
9
13
|
const isRetriableError = (err) => {
|
|
10
14
|
return err && err.message && (
|
|
@@ -37,7 +41,7 @@ async function request (url, options) {
|
|
|
37
41
|
}
|
|
38
42
|
|
|
39
43
|
options.headers = options.headers || {};
|
|
40
|
-
options.headers['User-Agent'] = `locize-cli/v12.
|
|
44
|
+
options.headers['User-Agent'] = `locize-cli/v12.4.0 (node/${process.version}; ${process.platform} ${process.arch})`; // This string is replaced with the actual version at build time by rollup
|
|
41
45
|
options.headers['X-User-Agent'] = options.headers['User-Agent'];
|
|
42
46
|
if (options.body || options.method !== 'get') options.headers['Content-Type'] = 'application/json';
|
|
43
47
|
if (options.body) {
|
|
@@ -47,19 +51,24 @@ async function request (url, options) {
|
|
|
47
51
|
}
|
|
48
52
|
if (options.headers['Authorization'] === undefined) delete options.headers['Authorization'];
|
|
49
53
|
|
|
50
|
-
async function retriableFetch (
|
|
54
|
+
async function retriableFetch (retriesLeft, attempt = 0) {
|
|
51
55
|
try {
|
|
52
56
|
const response = await fetch(url, options);
|
|
53
57
|
const result = await handleResponse(response);
|
|
54
58
|
return result
|
|
55
59
|
} catch (err) {
|
|
56
|
-
if (
|
|
60
|
+
if (retriesLeft < 1) throw err
|
|
57
61
|
if (!isRetriableError(err)) throw err
|
|
58
|
-
|
|
59
|
-
|
|
62
|
+
// Exponential backoff with equal jitter: spreads retries so a burst of
|
|
63
|
+
// concurrent CLI runs (e.g. CI) doesn't all retry in lockstep and
|
|
64
|
+
// hammer the API at the same instant.
|
|
65
|
+
const expDelay = Math.min(RETRY_MAX_DELAY_MS, RETRY_BASE_DELAY_MS * (2 ** attempt));
|
|
66
|
+
const delay = Math.round(expDelay / 2 + Math.random() * (expDelay / 2));
|
|
67
|
+
await new Promise(resolve => setTimeout(resolve, delay));
|
|
68
|
+
return retriableFetch(retriesLeft - 1, attempt + 1)
|
|
60
69
|
}
|
|
61
70
|
}
|
|
62
|
-
return retriableFetch(
|
|
71
|
+
return retriableFetch(RETRY_MAX_ATTEMPTS)
|
|
63
72
|
}
|
|
64
73
|
|
|
65
74
|
export { request as default };
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "locize-cli",
|
|
3
|
-
"version": "12.
|
|
3
|
+
"version": "12.4.0",
|
|
4
4
|
"description": "locize cli to import locales",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"main": "./dist/cjs/index.js",
|
|
@@ -32,10 +32,10 @@
|
|
|
32
32
|
"commander": "14.0.3",
|
|
33
33
|
"diff": "8.0.4",
|
|
34
34
|
"dotenv": "17.4.2",
|
|
35
|
-
"fast-csv": "5.0.
|
|
35
|
+
"fast-csv": "5.0.7",
|
|
36
36
|
"flat": "5.0.2",
|
|
37
37
|
"fluent_conv": "3.3.0",
|
|
38
|
-
"gettext-converter": "1.3.
|
|
38
|
+
"gettext-converter": "1.3.3",
|
|
39
39
|
"https-proxy-agent": "7.0.6",
|
|
40
40
|
"ini": "6.0.0",
|
|
41
41
|
"laravelphp": "2.0.4",
|
|
@@ -46,20 +46,20 @@
|
|
|
46
46
|
"rimraf": "6.1.3",
|
|
47
47
|
"strings-file": "0.0.5",
|
|
48
48
|
"tmexchange": "2.0.6",
|
|
49
|
-
"xliff": "6.
|
|
49
|
+
"xliff": "6.4.0",
|
|
50
50
|
"xlsx": "https://cdn.sheetjs.com/xlsx-0.20.3/xlsx-0.20.3.tgz",
|
|
51
|
-
"yaml": "2.
|
|
51
|
+
"yaml": "2.9.0"
|
|
52
52
|
},
|
|
53
53
|
"devDependencies": {
|
|
54
54
|
"@rollup/plugin-replace": "6.0.3",
|
|
55
|
-
"@rollup/plugin-terser": "0.
|
|
56
|
-
"@yao-pkg/pkg": "6.
|
|
55
|
+
"@rollup/plugin-terser": "1.0.0",
|
|
56
|
+
"@yao-pkg/pkg": "6.21.0",
|
|
57
57
|
"eslint": "9.39.2",
|
|
58
58
|
"eslint-plugin-import": "2.32.0",
|
|
59
|
-
"gh-release": "
|
|
59
|
+
"gh-release": "8.1.0",
|
|
60
60
|
"neostandard": "0.13.0",
|
|
61
|
-
"rollup": "4.
|
|
62
|
-
"vitest": "4.1.
|
|
61
|
+
"rollup": "4.62.2",
|
|
62
|
+
"vitest": "4.1.10"
|
|
63
63
|
},
|
|
64
64
|
"scripts": {
|
|
65
65
|
"lint": "eslint .",
|
|
@@ -67,10 +67,10 @@
|
|
|
67
67
|
"test:watch": "vitest",
|
|
68
68
|
"test": "npm run lint && vitest run",
|
|
69
69
|
"pkg": "mkdir -p ./bins && pkg ./dist/cjs/cli.js --output ./bins/locize --targets node24-linux-x64,node24-macos-x64,node24-win-x64,node24-alpine-x64",
|
|
70
|
-
"release": "gh-release --assets ./bins/locize-linux,./bins/locize-macos,./bins/locize-win.exe,./bins/locize-alpine",
|
|
70
|
+
"release": "GH_RELEASE_GITHUB_API_TOKEN=$(gh auth token) gh-release --assets ./bins/locize-linux,./bins/locize-macos,./bins/locize-win.exe,./bins/locize-alpine",
|
|
71
71
|
"preversion": "npm run test",
|
|
72
72
|
"version": "npm run build && npm run pkg",
|
|
73
|
-
"postversion": "git push && npm run release"
|
|
73
|
+
"postversion": "git push --follow-tags && npm run release"
|
|
74
74
|
},
|
|
75
75
|
"keywords": [
|
|
76
76
|
"cli",
|