locize-cli 12.4.0 → 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 CHANGED
@@ -5,6 +5,20 @@ 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
+
8
22
  ## [12.4.0](https://github.com/locize/locize-cli/compare/v12.3.1...v12.4.0) - 2026-07-06
9
23
 
10
24
  - xliff: proper support for inline elements (e.g. Angular's
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
@@ -110,6 +121,7 @@ or add a format like (json, nested, flat, xliff2, xliff21, xliff22, xliff12, xlf
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
@@ -165,6 +177,7 @@ or add a format like (json, nested, flat, xliff2, xliff21, xliff22, xliff12, xlf
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
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
- if (!opt.data && (opt.value === undefined || opt.value === null)) {
19
- console.log(colors.yellow(`removing ${opt.key} from ${opt.version}/${opt.language}/${opt.namespace}...`));
20
- } else {
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
- const data = flatten(opt.data || {});
25
- if (!opt.data) {
26
- data[opt.key] = opt.value || null; // null will remove the key
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 (!opt.data && (opt.value === undefined || opt.value === null)) {
39
- console.log(colors.red(`remove failed for ${opt.key} from ${opt.version}/${opt.language}/${opt.namespace}...`));
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 ${opt.key} to ${opt.version}/${opt.language}/${opt.namespace}...`));
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 (!opt.data && (opt.value === undefined || opt.value === null)) {
53
- console.log(colors.green(`removed ${opt.key} from ${opt.version}/${opt.language}/${opt.namespace}...`));
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 ${opt.key} to ${opt.version}/${opt.language}/${opt.namespace}...`));
66
+ console.log(colors.green(`added ${label} to ${opt.version}/${opt.language}/${opt.namespace}...`));
56
67
  }
57
68
  } catch (err) {
58
- if (!opt.data && (opt.value === undefined || opt.value === null)) {
59
- console.log(colors.red(`remove failed for ${opt.key} from ${opt.version}/${opt.language}/${opt.namespace}...`));
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 ${opt.key} to ${opt.version}/${opt.language}/${opt.namespace}...`));
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
- if (!opt.data && (opt.value === undefined || opt.value === null)) {
84
- console.log(colors.green(`removed ${opt.namespace}/${opt.key} (${opt.version}) from all languages...`));
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.key} (${opt.version}) in all languages...`));
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.4.0'); // This string is replaced with the actual version at build time by rollup
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 a key')
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, key, options) => {
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();
@@ -1,5 +1,5 @@
1
1
  {
2
2
  "name": "locize-cli",
3
- "version": "12.4.0",
3
+ "version": "12.5.0",
4
4
  "type": "commonjs"
5
5
  }
@@ -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.4.0 (node/${process.version}; ${process.platform} ${process.arch})`; // This string is replaced with the actual version at build time by rollup
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(`removing ${ns.diff.toRemove.length} keys in ${ns.language}/${ns.namespace}...`));
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(`removing ${ns.diff.toRemoveLocally.length} keys in ${ns.language}/${ns.namespace} locally...`));
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
- if (!opt.data && (opt.value === undefined || opt.value === null)) {
17
- console.log(colors.yellow(`removing ${opt.key} from ${opt.version}/${opt.language}/${opt.namespace}...`));
18
- } else {
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
- const data = flatten(opt.data || {});
23
- if (!opt.data) {
24
- data[opt.key] = opt.value || null; // null will remove the key
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 (!opt.data && (opt.value === undefined || opt.value === null)) {
37
- console.log(colors.red(`remove failed for ${opt.key} from ${opt.version}/${opt.language}/${opt.namespace}...`));
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 ${opt.key} to ${opt.version}/${opt.language}/${opt.namespace}...`));
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 (!opt.data && (opt.value === undefined || opt.value === null)) {
51
- console.log(colors.green(`removed ${opt.key} from ${opt.version}/${opt.language}/${opt.namespace}...`));
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 ${opt.key} to ${opt.version}/${opt.language}/${opt.namespace}...`));
64
+ console.log(colors.green(`added ${label} to ${opt.version}/${opt.language}/${opt.namespace}...`));
54
65
  }
55
66
  } catch (err) {
56
- if (!opt.data && (opt.value === undefined || opt.value === null)) {
57
- console.log(colors.red(`remove failed for ${opt.key} from ${opt.version}/${opt.language}/${opt.namespace}...`));
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 ${opt.key} to ${opt.version}/${opt.language}/${opt.namespace}...`));
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
- if (!opt.data && (opt.value === undefined || opt.value === null)) {
82
- console.log(colors.green(`removed ${opt.namespace}/${opt.key} (${opt.version}) from all languages...`));
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.key} (${opt.version}) in all languages...`));
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.4.0'); // This string is replaced with the actual version at build time by rollup
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 a key')
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, key, options) => {
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();
@@ -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.4.0 (node/${process.version}; ${process.platform} ${process.arch})`; // This string is replaced with the actual version at build time by rollup
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(`removing ${ns.diff.toRemove.length} keys in ${ns.language}/${ns.namespace}...`));
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(`removing ${ns.diff.toRemoveLocally.length} keys in ${ns.language}/${ns.namespace} locally...`));
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.4.0",
3
+ "version": "12.5.0",
4
4
  "description": "locize cli to import locales",
5
5
  "type": "module",
6
6
  "main": "./dist/cjs/index.js",