locize-cli 12.3.1 → 12.5.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 +29 -0
- package/README.md +16 -3
- package/dist/cjs/add.js +32 -20
- package/dist/cjs/cli.js +12 -9
- 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 +1 -1
- package/dist/cjs/sync.js +8 -4
- package/dist/esm/add.js +32 -20
- package/dist/esm/cli.js +12 -9
- 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 +1 -1
- package/dist/esm/sync.js +8 -4
- package/package.json +12 -12
package/CHANGELOG.md
CHANGED
|
@@ -5,6 +5,35 @@ 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.5.0](https://github.com/locize/locize-cli/compare/v12.4.0...v12.5.0) - 2026-07-15
|
|
9
|
+
|
|
10
|
+
- sync: removed keys are now listed by name in real runs too (previously the
|
|
11
|
+
names were only shown with `--dry`), capped at 50 keys per namespace, so a
|
|
12
|
+
sync always shows exactly what it deleted. When keys are removed without
|
|
13
|
+
`--backup-deleted-path`, a one-line tip now points to `-B` to keep a local
|
|
14
|
+
backup of the removed segments ([98](https://github.com/locize/locize-cli/issues/98)).
|
|
15
|
+
- remove: accepts multiple keys (`locize remove common title subtitle ...`),
|
|
16
|
+
batched into a single update request per language ([53](https://github.com/locize/locize-cli/issues/53)).
|
|
17
|
+
- docs: README notes for pnpm >= 11 (`blockExoticSubdeps` and why `xlsx` is
|
|
18
|
+
pinned to the SheetJS CDN tarball) and for the xcstrings format (file-level
|
|
19
|
+
conversion; i18next plural suffixes / `{{...}}` interpolation are not
|
|
20
|
+
rewritten to String Catalog plural variations / format specifiers).
|
|
21
|
+
|
|
22
|
+
## [12.4.0](https://github.com/locize/locize-cli/compare/v12.3.1...v12.4.0) - 2026-07-06
|
|
23
|
+
|
|
24
|
+
- xliff: proper support for inline elements (e.g. Angular's
|
|
25
|
+
`<x id="INTERPOLATION" equiv-text="{{ count }}"/>` placeholders, or
|
|
26
|
+
`<ph>`/`<pc>`/`<g>`/`<bpt>` tags from other TMS exports). On import they are
|
|
27
|
+
flattened into stable placeholder tokens inside the translation value
|
|
28
|
+
(previously such values were silently corrupted into arrays); on export the
|
|
29
|
+
tokens are restored as real XLIFF inline elements, so Angular `.xlf` files
|
|
30
|
+
round-trip cleanly. Requires `xliff` >= 6.4.0 (the encoding is implemented
|
|
31
|
+
in the library and shared with the locize CAT editor).
|
|
32
|
+
- xliff: new formats `xliff21`, `xliff22`, `xlf21`, `xlf22` for XLIFF 2.1 and
|
|
33
|
+
2.2 (import of 2.1/2.2 files already worked via `xliff2`; exports now emit
|
|
34
|
+
the matching `version` attribute). XLIFF 2.1 exports carry the
|
|
35
|
+
OASIS-correct `urn:oasis:names:tc:xliff:document:2.0` core namespace.
|
|
36
|
+
|
|
8
37
|
## [12.3.1](https://github.com/locize/locize-cli/compare/v12.3.0...v12.3.1) - 2026-06-18
|
|
9
38
|
|
|
10
39
|
- request: retry transient network failures with exponential backoff and jitter
|
package/README.md
CHANGED
|
@@ -12,6 +12,11 @@
|
|
|
12
12
|
npm install -g locize-cli
|
|
13
13
|
```
|
|
14
14
|
|
|
15
|
+
> ℹ️ **pnpm >= 11:** locize-cli depends on `xlsx` pinned to the official SheetJS CDN tarball, because the npm registry version is outdated and has known vulnerabilities. pnpm 11 blocks such non-registry transitive dependencies by default (`ERR_PNPM_EXOTIC_SUBDEP`). Install with npm, or opt out in your `pnpm-workspace.yaml`:
|
|
16
|
+
> ```yaml
|
|
17
|
+
> blockExoticSubdeps: false
|
|
18
|
+
> ```
|
|
19
|
+
|
|
15
20
|
### with Shell: (downloads [released](https://github.com/locize/locize-cli/releases) (linux or macos) binaries)
|
|
16
21
|
|
|
17
22
|
```sh
|
|
@@ -67,6 +72,12 @@ or
|
|
|
67
72
|
locize remove common title
|
|
68
73
|
```
|
|
69
74
|
|
|
75
|
+
or remove multiple keys at once (batched into one request per language):
|
|
76
|
+
|
|
77
|
+
```sh
|
|
78
|
+
locize remove common title subtitle description
|
|
79
|
+
```
|
|
80
|
+
|
|
70
81
|
|
|
71
82
|
## Get keys
|
|
72
83
|
### Step 1: execute
|
|
@@ -105,11 +116,12 @@ locize download --cdn-type standard
|
|
|
105
116
|
locize download --cdn-type pro
|
|
106
117
|
```
|
|
107
118
|
|
|
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)
|
|
119
|
+
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
120
|
|
|
110
121
|
*Special formats:*
|
|
111
122
|
- use yaml-rails to have the language code in the resulting yaml as root object
|
|
112
123
|
- use yaml-rails-ns to have the namespace in the resulting yaml as first key scope
|
|
124
|
+
- xcstrings (Apple String Catalog): conversion happens at the file level, not at the in-string syntax level — i18next-style plural suffixes (`key_one`/`key_other`) stay separate entries (they are not merged into `variations.plural`) and interpolation placeholders like `{{name}}` are not rewritten to `%@`/`%lld` format specifiers. For a clean String Catalog export, the content should already use xcstrings-native plural variations and format specifiers.
|
|
113
125
|
|
|
114
126
|
```sh
|
|
115
127
|
locize download --project-id my-project-id-93e1-442a-ab35-24331fa294ba --ver latest --language en --namespace namespace1 --path ./backup --format android
|
|
@@ -160,11 +172,12 @@ locize sync --cdn-type standard --api-key my-api-key-d9de-4f55-9855-a9ef0ed44672
|
|
|
160
172
|
locize sync --cdn-type pro --api-key my-api-key-d9de-4f55-9855-a9ef0ed44672 --project-id my-project-id-93e1-442a-ab35-24331fa294ba
|
|
161
173
|
```
|
|
162
174
|
|
|
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)
|
|
175
|
+
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
176
|
|
|
165
177
|
*Special formats:*
|
|
166
178
|
- use yaml-rails to have the language code in the resulting yaml as root object
|
|
167
179
|
- use yaml-rails-ns to have the namespace in the resulting yaml as first key scope
|
|
180
|
+
- xcstrings (Apple String Catalog): conversion happens at the file level, not at the in-string syntax level — i18next-style plural suffixes (`key_one`/`key_other`) stay separate entries (they are not merged into `variations.plural`) and interpolation placeholders like `{{name}}` are not rewritten to `%@`/`%lld` format specifiers. For a clean String Catalog export, the content should already use xcstrings-native plural variations and format specifiers.
|
|
168
181
|
|
|
169
182
|
```sh
|
|
170
183
|
locize sync --api-key my-api-key-d9de-4f55-9855-a9ef0ed44672 --project-id my-project-id-93e1-442a-ab35-24331fa294ba --format android
|
|
@@ -227,7 +240,7 @@ locize save-missing --cdn-type standard --api-key my-api-key-d9de-4f55-9855-a9ef
|
|
|
227
240
|
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
241
|
```
|
|
229
242
|
|
|
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)
|
|
243
|
+
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
244
|
|
|
232
245
|
```sh
|
|
233
246
|
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/add.js
CHANGED
|
@@ -5,6 +5,18 @@ var flatten = require('flat');
|
|
|
5
5
|
var getRemoteLanguages = require('./getRemoteLanguages.js');
|
|
6
6
|
var request = require('./request.js');
|
|
7
7
|
|
|
8
|
+
const payloadOf = (opt) => {
|
|
9
|
+
const data = flatten(opt.data || {});
|
|
10
|
+
if (!opt.data) {
|
|
11
|
+
data[opt.key] = opt.value || null; // null will remove the key
|
|
12
|
+
}
|
|
13
|
+
return data
|
|
14
|
+
};
|
|
15
|
+
|
|
16
|
+
const isRemoval = (data) => Object.keys(data).length > 0 && Object.values(data).every((v) => v === undefined || v === null);
|
|
17
|
+
|
|
18
|
+
const keyLabel = (opt, data) => opt.key || Object.keys(data).join(', ');
|
|
19
|
+
|
|
8
20
|
const _add = async (opt) => {
|
|
9
21
|
const url = `${opt.apiEndpoint}/update/{{projectId}}/{{version}}/{{lng}}/{{ns}}`
|
|
10
22
|
.replace('{{projectId}}', opt.projectId)
|
|
@@ -15,15 +27,14 @@ const _add = async (opt) => {
|
|
|
15
27
|
.replace('{{ns}}', opt.namespace)
|
|
16
28
|
.replace('{{namespace}}', opt.namespace);
|
|
17
29
|
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
console.log(colors.yellow(`adding ${opt.key} to ${opt.version}/${opt.language}/${opt.namespace}...`));
|
|
22
|
-
}
|
|
30
|
+
const data = payloadOf(opt);
|
|
31
|
+
const removing = isRemoval(data);
|
|
32
|
+
const label = keyLabel(opt, data);
|
|
23
33
|
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
34
|
+
if (removing) {
|
|
35
|
+
console.log(colors.yellow(`removing ${label} from ${opt.version}/${opt.language}/${opt.namespace}...`));
|
|
36
|
+
} else {
|
|
37
|
+
console.log(colors.yellow(`adding ${label} to ${opt.version}/${opt.language}/${opt.namespace}...`));
|
|
27
38
|
}
|
|
28
39
|
|
|
29
40
|
try {
|
|
@@ -35,10 +46,10 @@ const _add = async (opt) => {
|
|
|
35
46
|
body: data
|
|
36
47
|
});
|
|
37
48
|
if (res.status >= 300 && res.status !== 412) {
|
|
38
|
-
if (
|
|
39
|
-
console.log(colors.red(`remove failed for ${
|
|
49
|
+
if (removing) {
|
|
50
|
+
console.log(colors.red(`remove failed for ${label} from ${opt.version}/${opt.language}/${opt.namespace}...`));
|
|
40
51
|
} else {
|
|
41
|
-
console.log(colors.red(`add failed for ${
|
|
52
|
+
console.log(colors.red(`add failed for ${label} to ${opt.version}/${opt.language}/${opt.namespace}...`));
|
|
42
53
|
}
|
|
43
54
|
if (obj && (obj.errorMessage || obj.message)) {
|
|
44
55
|
console.error(colors.red((obj.errorMessage || obj.message)));
|
|
@@ -49,16 +60,16 @@ const _add = async (opt) => {
|
|
|
49
60
|
}
|
|
50
61
|
return
|
|
51
62
|
}
|
|
52
|
-
if (
|
|
53
|
-
console.log(colors.green(`removed ${
|
|
63
|
+
if (removing) {
|
|
64
|
+
console.log(colors.green(`removed ${label} from ${opt.version}/${opt.language}/${opt.namespace}...`));
|
|
54
65
|
} else {
|
|
55
|
-
console.log(colors.green(`added ${
|
|
66
|
+
console.log(colors.green(`added ${label} to ${opt.version}/${opt.language}/${opt.namespace}...`));
|
|
56
67
|
}
|
|
57
68
|
} catch (err) {
|
|
58
|
-
if (
|
|
59
|
-
console.log(colors.red(`remove failed for ${
|
|
69
|
+
if (removing) {
|
|
70
|
+
console.log(colors.red(`remove failed for ${label} from ${opt.version}/${opt.language}/${opt.namespace}...`));
|
|
60
71
|
} else {
|
|
61
|
-
console.log(colors.red(`add failed for ${
|
|
72
|
+
console.log(colors.red(`add failed for ${label} to ${opt.version}/${opt.language}/${opt.namespace}...`));
|
|
62
73
|
}
|
|
63
74
|
console.error(colors.red(err.message));
|
|
64
75
|
process.exit(1);
|
|
@@ -80,10 +91,11 @@ const add = async (opt) => {
|
|
|
80
91
|
opt.language = lng;
|
|
81
92
|
await _add(opt);
|
|
82
93
|
}
|
|
83
|
-
|
|
84
|
-
|
|
94
|
+
const data = payloadOf(opt);
|
|
95
|
+
if (isRemoval(data)) {
|
|
96
|
+
console.log(colors.green(`removed ${opt.namespace}/${keyLabel(opt, data)} (${opt.version}) from all languages...`));
|
|
85
97
|
} else {
|
|
86
|
-
console.log(colors.green(`added ${opt.namespace}/${opt
|
|
98
|
+
console.log(colors.green(`added ${opt.namespace}/${keyLabel(opt, data)} (${opt.version}) in all languages...`));
|
|
87
99
|
}
|
|
88
100
|
};
|
|
89
101
|
|
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.5.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
|
|
|
@@ -189,9 +189,9 @@ program
|
|
|
189
189
|
});
|
|
190
190
|
|
|
191
191
|
program
|
|
192
|
-
.command('remove <namespace> <key
|
|
192
|
+
.command('remove <namespace> <key...>')
|
|
193
193
|
.alias('rm')
|
|
194
|
-
.description('remove
|
|
194
|
+
.description('remove one or more keys')
|
|
195
195
|
.option('-k, --api-key <apiKey>', 'The api-key that should be used')
|
|
196
196
|
.option('-i, --project-id <projectId>', 'The project-id that should be used')
|
|
197
197
|
.option('-l, --language <lng>', 'The language that should be targeted (omitting this attribute will result in removing the key from all languages)')
|
|
@@ -199,7 +199,7 @@ program
|
|
|
199
199
|
.option('-a, --api-endpoint <url>', `Specify the api-endpoint url that should be used (default: ${defaultApiEndpoint})`)
|
|
200
200
|
.option('-C, --config-path <configPath>', `Specify the path to the optional locize config file (default: ${configInWorkingDirectory} or ${configInHome})`)
|
|
201
201
|
.option('--ct, --cdn-type <standard|pro>', `Specify the cdn endpoint that should be used (depends on which cdn type you've in your Locize project) (default: ${defaultCdnType})`)
|
|
202
|
-
.action((namespace,
|
|
202
|
+
.action((namespace, keys, options) => {
|
|
203
203
|
try {
|
|
204
204
|
config = ini.parse(fs.readFileSync(options.configPath, 'utf-8')) || config;
|
|
205
205
|
} catch (e) {}
|
|
@@ -240,13 +240,16 @@ program
|
|
|
240
240
|
language,
|
|
241
241
|
version,
|
|
242
242
|
namespace,
|
|
243
|
-
key
|
|
243
|
+
key: keys.length === 1 ? keys[0] : undefined,
|
|
244
|
+
// multiple keys are batched into one update request per language
|
|
245
|
+
data: keys.length > 1 ? keys.reduce((m, k) => { m[k] = null; return m }, {}) : undefined
|
|
244
246
|
});
|
|
245
247
|
})
|
|
246
248
|
.on('--help', () => {
|
|
247
249
|
console.log(' Examples:');
|
|
248
250
|
console.log();
|
|
249
251
|
console.log(' $ locize remove common title');
|
|
252
|
+
console.log(' $ locize remove common title subtitle description');
|
|
250
253
|
console.log(' $ locize remove common title --language en');
|
|
251
254
|
console.log(' $ locize remove common title --api-key <apiKey> --project-id <projectId> --language en');
|
|
252
255
|
console.log();
|
|
@@ -263,7 +266,7 @@ program
|
|
|
263
266
|
.option('-n, --namespace <ns>', 'The namespace that should be targeted')
|
|
264
267
|
.option('-p, --path <path>', `Specify the path that should be used (default: ${process.cwd()})`, process.cwd())
|
|
265
268
|
.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')
|
|
269
|
+
.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
270
|
.option('-s, --skip-empty <true|false>', 'Skips to download empty files (default: true)', 'true')
|
|
268
271
|
.option('-P, --language-folder-prefix <prefix>', 'This will be added as a local folder name prefix in front of the language.', '')
|
|
269
272
|
.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 +415,7 @@ program
|
|
|
412
415
|
.option('-p, --path <path>', `Specify the path that should be used (default: ${process.cwd()})`, process.cwd())
|
|
413
416
|
.option('-B, --backup-deleted-path <path>', 'Saves the segments that will be deleted in this path')
|
|
414
417
|
.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')
|
|
418
|
+
.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
419
|
.option('-s, --skip-empty <true|false>', 'Skips to download empty files (default: false)', 'false')
|
|
417
420
|
.option('-c, --clean <true|false>', 'Removes all local files by removing the whole folder (default: false)', 'false')
|
|
418
421
|
.option('--cf, --clean-local-files <true|false>', 'Removes all local files without removing any folder (default: false)', 'false')
|
|
@@ -540,7 +543,7 @@ program
|
|
|
540
543
|
.option('-i, --project-id <projectId>', 'The project-id that should be used')
|
|
541
544
|
.option('-v, --ver <version>', 'Found namespaces will be matched to this version (default: latest)')
|
|
542
545
|
.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')
|
|
546
|
+
.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
547
|
.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
548
|
.option('-P, --language-folder-prefix <prefix>', 'This will be added as a local folder name prefix in front of the language.', '')
|
|
546
549
|
.option('-d, --dry <true|false>', 'Dry run (default: false)', 'false')
|
|
@@ -835,7 +838,7 @@ program
|
|
|
835
838
|
.command('format [fileOrDirectory]')
|
|
836
839
|
.alias('ft')
|
|
837
840
|
.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')
|
|
841
|
+
.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
842
|
.option('-l, --reference-language <lng>', 'Some format conversions need to know the reference language.', 'en')
|
|
840
843
|
.option('-d, --dry <true|false>', 'Dry run (default: false)', 'false')
|
|
841
844
|
.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
|
@@ -43,7 +43,7 @@ async function request (url, options) {
|
|
|
43
43
|
}
|
|
44
44
|
|
|
45
45
|
options.headers = options.headers || {};
|
|
46
|
-
options.headers['User-Agent'] = `locize-cli/v12.
|
|
46
|
+
options.headers['User-Agent'] = `locize-cli/v12.5.0 (node/${process.version}; ${process.platform} ${process.arch})`; // This string is replaced with the actual version at build time by rollup
|
|
47
47
|
options.headers['X-User-Agent'] = options.headers['User-Agent'];
|
|
48
48
|
if (options.body || options.method !== 'get') options.headers['Content-Type'] = 'application/json';
|
|
49
49
|
if (options.body) {
|
package/dist/cjs/sync.js
CHANGED
|
@@ -195,6 +195,8 @@ const cleanupLanguages = (opt, remoteLanguages) => {
|
|
|
195
195
|
});
|
|
196
196
|
};
|
|
197
197
|
|
|
198
|
+
const listRemovedKeys = (keys) => keys.length <= 50 ? keys.join(', ') : `${keys.slice(0, 50).join(', ')} ... and ${keys.length - 50} more`;
|
|
199
|
+
|
|
198
200
|
const backupDeleted = (opt, ns, now) => {
|
|
199
201
|
if (opt.dry || ns.diff.toRemove.length === 0) return
|
|
200
202
|
let m = now.getMonth() + 1;
|
|
@@ -529,6 +531,7 @@ async function handleSync (opt, remoteLanguages, localNamespaces) {
|
|
|
529
531
|
const shouldOmit = lngsInReqs.length > 5 || nsInReqs.length > 5;
|
|
530
532
|
|
|
531
533
|
let wasThereSomethingToUpdate = opt.autoTranslate || false;
|
|
534
|
+
let suggestBackupTip = false;
|
|
532
535
|
|
|
533
536
|
async function updateComparedNamespaces () {
|
|
534
537
|
const now = new Date();
|
|
@@ -539,14 +542,13 @@ async function handleSync (opt, remoteLanguages, localNamespaces) {
|
|
|
539
542
|
console.log(colors.bgRed(`skipping the removal of ${ns.diff.toRemove.length} keys in ${ns.language}/${ns.namespace}...`));
|
|
540
543
|
if (opt.dry) console.log(colors.bgRed(`skipped to remove ${ns.diff.toRemove.join(', ')} in ${ns.language}/${ns.namespace}...`));
|
|
541
544
|
} else {
|
|
542
|
-
console.log(colors.red(
|
|
543
|
-
if (opt.dry) console.log(colors.red(`would remove ${ns.diff.toRemove.join(', ')} in ${ns.language}/${ns.namespace}...`));
|
|
545
|
+
console.log(colors.red(`${opt.dry ? 'would remove' : 'removing'} ${ns.diff.toRemove.length} keys in ${ns.language}/${ns.namespace}: ${listRemovedKeys(ns.diff.toRemove)}`));
|
|
544
546
|
if (!opt.dry && opt.backupDeletedPath) backupDeleted(opt, ns, now);
|
|
547
|
+
if (!opt.dry && !opt.backupDeletedPath) suggestBackupTip = true;
|
|
545
548
|
}
|
|
546
549
|
}
|
|
547
550
|
if (ns.diff.toRemoveLocally.length > 0) {
|
|
548
|
-
console.log(colors.red(
|
|
549
|
-
if (opt.dry) console.log(colors.red(`would remove ${ns.diff.toRemoveLocally.join(', ')} in ${ns.language}/${ns.namespace} locally...`));
|
|
551
|
+
console.log(colors.red(`${opt.dry ? 'would remove' : 'removing'} ${ns.diff.toRemoveLocally.length} keys in ${ns.language}/${ns.namespace} locally: ${listRemovedKeys(ns.diff.toRemoveLocally)}`));
|
|
550
552
|
}
|
|
551
553
|
if (ns.diff.toAdd.length > 0) {
|
|
552
554
|
console.log(colors.green(`adding ${ns.diff.toAdd.length} keys in ${ns.language}/${ns.namespace}...`));
|
|
@@ -585,6 +587,8 @@ async function handleSync (opt, remoteLanguages, localNamespaces) {
|
|
|
585
587
|
});
|
|
586
588
|
});
|
|
587
589
|
|
|
590
|
+
if (suggestBackupTip) console.log(colors.yellow('tip: use -B <path> (--backup-deleted-path) to keep a local backup of removed segments'));
|
|
591
|
+
|
|
588
592
|
console.log(colors.grey('syncing...'));
|
|
589
593
|
|
|
590
594
|
async function down () {
|
package/dist/esm/add.js
CHANGED
|
@@ -3,6 +3,18 @@ import flatten from 'flat';
|
|
|
3
3
|
import getRemoteLanguages from './getRemoteLanguages.js';
|
|
4
4
|
import request from './request.js';
|
|
5
5
|
|
|
6
|
+
const payloadOf = (opt) => {
|
|
7
|
+
const data = flatten(opt.data || {});
|
|
8
|
+
if (!opt.data) {
|
|
9
|
+
data[opt.key] = opt.value || null; // null will remove the key
|
|
10
|
+
}
|
|
11
|
+
return data
|
|
12
|
+
};
|
|
13
|
+
|
|
14
|
+
const isRemoval = (data) => Object.keys(data).length > 0 && Object.values(data).every((v) => v === undefined || v === null);
|
|
15
|
+
|
|
16
|
+
const keyLabel = (opt, data) => opt.key || Object.keys(data).join(', ');
|
|
17
|
+
|
|
6
18
|
const _add = async (opt) => {
|
|
7
19
|
const url = `${opt.apiEndpoint}/update/{{projectId}}/{{version}}/{{lng}}/{{ns}}`
|
|
8
20
|
.replace('{{projectId}}', opt.projectId)
|
|
@@ -13,15 +25,14 @@ const _add = async (opt) => {
|
|
|
13
25
|
.replace('{{ns}}', opt.namespace)
|
|
14
26
|
.replace('{{namespace}}', opt.namespace);
|
|
15
27
|
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
console.log(colors.yellow(`adding ${opt.key} to ${opt.version}/${opt.language}/${opt.namespace}...`));
|
|
20
|
-
}
|
|
28
|
+
const data = payloadOf(opt);
|
|
29
|
+
const removing = isRemoval(data);
|
|
30
|
+
const label = keyLabel(opt, data);
|
|
21
31
|
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
32
|
+
if (removing) {
|
|
33
|
+
console.log(colors.yellow(`removing ${label} from ${opt.version}/${opt.language}/${opt.namespace}...`));
|
|
34
|
+
} else {
|
|
35
|
+
console.log(colors.yellow(`adding ${label} to ${opt.version}/${opt.language}/${opt.namespace}...`));
|
|
25
36
|
}
|
|
26
37
|
|
|
27
38
|
try {
|
|
@@ -33,10 +44,10 @@ const _add = async (opt) => {
|
|
|
33
44
|
body: data
|
|
34
45
|
});
|
|
35
46
|
if (res.status >= 300 && res.status !== 412) {
|
|
36
|
-
if (
|
|
37
|
-
console.log(colors.red(`remove failed for ${
|
|
47
|
+
if (removing) {
|
|
48
|
+
console.log(colors.red(`remove failed for ${label} from ${opt.version}/${opt.language}/${opt.namespace}...`));
|
|
38
49
|
} else {
|
|
39
|
-
console.log(colors.red(`add failed for ${
|
|
50
|
+
console.log(colors.red(`add failed for ${label} to ${opt.version}/${opt.language}/${opt.namespace}...`));
|
|
40
51
|
}
|
|
41
52
|
if (obj && (obj.errorMessage || obj.message)) {
|
|
42
53
|
console.error(colors.red((obj.errorMessage || obj.message)));
|
|
@@ -47,16 +58,16 @@ const _add = async (opt) => {
|
|
|
47
58
|
}
|
|
48
59
|
return
|
|
49
60
|
}
|
|
50
|
-
if (
|
|
51
|
-
console.log(colors.green(`removed ${
|
|
61
|
+
if (removing) {
|
|
62
|
+
console.log(colors.green(`removed ${label} from ${opt.version}/${opt.language}/${opt.namespace}...`));
|
|
52
63
|
} else {
|
|
53
|
-
console.log(colors.green(`added ${
|
|
64
|
+
console.log(colors.green(`added ${label} to ${opt.version}/${opt.language}/${opt.namespace}...`));
|
|
54
65
|
}
|
|
55
66
|
} catch (err) {
|
|
56
|
-
if (
|
|
57
|
-
console.log(colors.red(`remove failed for ${
|
|
67
|
+
if (removing) {
|
|
68
|
+
console.log(colors.red(`remove failed for ${label} from ${opt.version}/${opt.language}/${opt.namespace}...`));
|
|
58
69
|
} else {
|
|
59
|
-
console.log(colors.red(`add failed for ${
|
|
70
|
+
console.log(colors.red(`add failed for ${label} to ${opt.version}/${opt.language}/${opt.namespace}...`));
|
|
60
71
|
}
|
|
61
72
|
console.error(colors.red(err.message));
|
|
62
73
|
process.exit(1);
|
|
@@ -78,10 +89,11 @@ const add = async (opt) => {
|
|
|
78
89
|
opt.language = lng;
|
|
79
90
|
await _add(opt);
|
|
80
91
|
}
|
|
81
|
-
|
|
82
|
-
|
|
92
|
+
const data = payloadOf(opt);
|
|
93
|
+
if (isRemoval(data)) {
|
|
94
|
+
console.log(colors.green(`removed ${opt.namespace}/${keyLabel(opt, data)} (${opt.version}) from all languages...`));
|
|
83
95
|
} else {
|
|
84
|
-
console.log(colors.green(`added ${opt.namespace}/${opt
|
|
96
|
+
console.log(colors.green(`added ${opt.namespace}/${keyLabel(opt, data)} (${opt.version}) in all languages...`));
|
|
85
97
|
}
|
|
86
98
|
};
|
|
87
99
|
|
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.5.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
|
|
|
@@ -187,9 +187,9 @@ program
|
|
|
187
187
|
});
|
|
188
188
|
|
|
189
189
|
program
|
|
190
|
-
.command('remove <namespace> <key
|
|
190
|
+
.command('remove <namespace> <key...>')
|
|
191
191
|
.alias('rm')
|
|
192
|
-
.description('remove
|
|
192
|
+
.description('remove one or more keys')
|
|
193
193
|
.option('-k, --api-key <apiKey>', 'The api-key that should be used')
|
|
194
194
|
.option('-i, --project-id <projectId>', 'The project-id that should be used')
|
|
195
195
|
.option('-l, --language <lng>', 'The language that should be targeted (omitting this attribute will result in removing the key from all languages)')
|
|
@@ -197,7 +197,7 @@ program
|
|
|
197
197
|
.option('-a, --api-endpoint <url>', `Specify the api-endpoint url that should be used (default: ${defaultApiEndpoint})`)
|
|
198
198
|
.option('-C, --config-path <configPath>', `Specify the path to the optional locize config file (default: ${configInWorkingDirectory} or ${configInHome})`)
|
|
199
199
|
.option('--ct, --cdn-type <standard|pro>', `Specify the cdn endpoint that should be used (depends on which cdn type you've in your Locize project) (default: ${defaultCdnType})`)
|
|
200
|
-
.action((namespace,
|
|
200
|
+
.action((namespace, keys, options) => {
|
|
201
201
|
try {
|
|
202
202
|
config = ini.parse(fs.readFileSync(options.configPath, 'utf-8')) || config;
|
|
203
203
|
} catch (e) {}
|
|
@@ -238,13 +238,16 @@ program
|
|
|
238
238
|
language,
|
|
239
239
|
version,
|
|
240
240
|
namespace,
|
|
241
|
-
key
|
|
241
|
+
key: keys.length === 1 ? keys[0] : undefined,
|
|
242
|
+
// multiple keys are batched into one update request per language
|
|
243
|
+
data: keys.length > 1 ? keys.reduce((m, k) => { m[k] = null; return m }, {}) : undefined
|
|
242
244
|
});
|
|
243
245
|
})
|
|
244
246
|
.on('--help', () => {
|
|
245
247
|
console.log(' Examples:');
|
|
246
248
|
console.log();
|
|
247
249
|
console.log(' $ locize remove common title');
|
|
250
|
+
console.log(' $ locize remove common title subtitle description');
|
|
248
251
|
console.log(' $ locize remove common title --language en');
|
|
249
252
|
console.log(' $ locize remove common title --api-key <apiKey> --project-id <projectId> --language en');
|
|
250
253
|
console.log();
|
|
@@ -261,7 +264,7 @@ program
|
|
|
261
264
|
.option('-n, --namespace <ns>', 'The namespace that should be targeted')
|
|
262
265
|
.option('-p, --path <path>', `Specify the path that should be used (default: ${process.cwd()})`, process.cwd())
|
|
263
266
|
.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')
|
|
267
|
+
.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
268
|
.option('-s, --skip-empty <true|false>', 'Skips to download empty files (default: true)', 'true')
|
|
266
269
|
.option('-P, --language-folder-prefix <prefix>', 'This will be added as a local folder name prefix in front of the language.', '')
|
|
267
270
|
.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 +413,7 @@ program
|
|
|
410
413
|
.option('-p, --path <path>', `Specify the path that should be used (default: ${process.cwd()})`, process.cwd())
|
|
411
414
|
.option('-B, --backup-deleted-path <path>', 'Saves the segments that will be deleted in this path')
|
|
412
415
|
.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')
|
|
416
|
+
.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
417
|
.option('-s, --skip-empty <true|false>', 'Skips to download empty files (default: false)', 'false')
|
|
415
418
|
.option('-c, --clean <true|false>', 'Removes all local files by removing the whole folder (default: false)', 'false')
|
|
416
419
|
.option('--cf, --clean-local-files <true|false>', 'Removes all local files without removing any folder (default: false)', 'false')
|
|
@@ -538,7 +541,7 @@ program
|
|
|
538
541
|
.option('-i, --project-id <projectId>', 'The project-id that should be used')
|
|
539
542
|
.option('-v, --ver <version>', 'Found namespaces will be matched to this version (default: latest)')
|
|
540
543
|
.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')
|
|
544
|
+
.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
545
|
.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
546
|
.option('-P, --language-folder-prefix <prefix>', 'This will be added as a local folder name prefix in front of the language.', '')
|
|
544
547
|
.option('-d, --dry <true|false>', 'Dry run (default: false)', 'false')
|
|
@@ -833,7 +836,7 @@ program
|
|
|
833
836
|
.command('format [fileOrDirectory]')
|
|
834
837
|
.alias('ft')
|
|
835
838
|
.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')
|
|
839
|
+
.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
840
|
.option('-l, --reference-language <lng>', 'Some format conversions need to know the reference language.', 'en')
|
|
838
841
|
.option('-d, --dry <true|false>', 'Dry run (default: false)', 'false')
|
|
839
842
|
.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
|
@@ -41,7 +41,7 @@ async function request (url, options) {
|
|
|
41
41
|
}
|
|
42
42
|
|
|
43
43
|
options.headers = options.headers || {};
|
|
44
|
-
options.headers['User-Agent'] = `locize-cli/v12.
|
|
44
|
+
options.headers['User-Agent'] = `locize-cli/v12.5.0 (node/${process.version}; ${process.platform} ${process.arch})`; // This string is replaced with the actual version at build time by rollup
|
|
45
45
|
options.headers['X-User-Agent'] = options.headers['User-Agent'];
|
|
46
46
|
if (options.body || options.method !== 'get') options.headers['Content-Type'] = 'application/json';
|
|
47
47
|
if (options.body) {
|
package/dist/esm/sync.js
CHANGED
|
@@ -193,6 +193,8 @@ const cleanupLanguages = (opt, remoteLanguages) => {
|
|
|
193
193
|
});
|
|
194
194
|
};
|
|
195
195
|
|
|
196
|
+
const listRemovedKeys = (keys) => keys.length <= 50 ? keys.join(', ') : `${keys.slice(0, 50).join(', ')} ... and ${keys.length - 50} more`;
|
|
197
|
+
|
|
196
198
|
const backupDeleted = (opt, ns, now) => {
|
|
197
199
|
if (opt.dry || ns.diff.toRemove.length === 0) return
|
|
198
200
|
let m = now.getMonth() + 1;
|
|
@@ -527,6 +529,7 @@ async function handleSync (opt, remoteLanguages, localNamespaces) {
|
|
|
527
529
|
const shouldOmit = lngsInReqs.length > 5 || nsInReqs.length > 5;
|
|
528
530
|
|
|
529
531
|
let wasThereSomethingToUpdate = opt.autoTranslate || false;
|
|
532
|
+
let suggestBackupTip = false;
|
|
530
533
|
|
|
531
534
|
async function updateComparedNamespaces () {
|
|
532
535
|
const now = new Date();
|
|
@@ -537,14 +540,13 @@ async function handleSync (opt, remoteLanguages, localNamespaces) {
|
|
|
537
540
|
console.log(colors.bgRed(`skipping the removal of ${ns.diff.toRemove.length} keys in ${ns.language}/${ns.namespace}...`));
|
|
538
541
|
if (opt.dry) console.log(colors.bgRed(`skipped to remove ${ns.diff.toRemove.join(', ')} in ${ns.language}/${ns.namespace}...`));
|
|
539
542
|
} else {
|
|
540
|
-
console.log(colors.red(
|
|
541
|
-
if (opt.dry) console.log(colors.red(`would remove ${ns.diff.toRemove.join(', ')} in ${ns.language}/${ns.namespace}...`));
|
|
543
|
+
console.log(colors.red(`${opt.dry ? 'would remove' : 'removing'} ${ns.diff.toRemove.length} keys in ${ns.language}/${ns.namespace}: ${listRemovedKeys(ns.diff.toRemove)}`));
|
|
542
544
|
if (!opt.dry && opt.backupDeletedPath) backupDeleted(opt, ns, now);
|
|
545
|
+
if (!opt.dry && !opt.backupDeletedPath) suggestBackupTip = true;
|
|
543
546
|
}
|
|
544
547
|
}
|
|
545
548
|
if (ns.diff.toRemoveLocally.length > 0) {
|
|
546
|
-
console.log(colors.red(
|
|
547
|
-
if (opt.dry) console.log(colors.red(`would remove ${ns.diff.toRemoveLocally.join(', ')} in ${ns.language}/${ns.namespace} locally...`));
|
|
549
|
+
console.log(colors.red(`${opt.dry ? 'would remove' : 'removing'} ${ns.diff.toRemoveLocally.length} keys in ${ns.language}/${ns.namespace} locally: ${listRemovedKeys(ns.diff.toRemoveLocally)}`));
|
|
548
550
|
}
|
|
549
551
|
if (ns.diff.toAdd.length > 0) {
|
|
550
552
|
console.log(colors.green(`adding ${ns.diff.toAdd.length} keys in ${ns.language}/${ns.namespace}...`));
|
|
@@ -583,6 +585,8 @@ async function handleSync (opt, remoteLanguages, localNamespaces) {
|
|
|
583
585
|
});
|
|
584
586
|
});
|
|
585
587
|
|
|
588
|
+
if (suggestBackupTip) console.log(colors.yellow('tip: use -B <path> (--backup-deleted-path) to keep a local backup of removed segments'));
|
|
589
|
+
|
|
586
590
|
console.log(colors.grey('syncing...'));
|
|
587
591
|
|
|
588
592
|
async function down () {
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "locize-cli",
|
|
3
|
-
"version": "12.
|
|
3
|
+
"version": "12.5.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",
|