i18next-cli 1.56.12 → 1.58.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/README.md CHANGED
@@ -25,6 +25,10 @@ A unified, high-performance i18next CLI toolchain, powered by SWC.
25
25
  > npx i18next-cli lint
26
26
  > ```
27
27
 
28
+ ## Advice:
29
+
30
+ If you're looking for a managed backend to pair with `i18next-cli`, take a look at [Locize](https://www.locize.com?utm_source=i18next_cli_readme&utm_medium=github&utm_campaign=readme) — `i18next-cli` already ships with `locize-download`, `locize-sync`, and `locize-migrate` commands. Built by the same team behind i18next, with CDN delivery, AI translation, review workflow, and no redeploys for copy changes.
31
+
28
32
  ## Why i18next-cli?
29
33
 
30
34
  `i18next-cli` is built from the ground up to meet the demands of modern web development.
@@ -106,6 +110,25 @@ Interactive setup wizard to create your configuration file.
106
110
  npx i18next-cli init
107
111
  ```
108
112
 
113
+ **Options:**
114
+ - `--ci`: Skip the browser launch when a backend (e.g. Locize) is selected;
115
+ the signup URL is printed instead. Useful for scripted runs. The wizard
116
+ also auto-detects `CI=true` and falls back to printing the URL on headless
117
+ Linux (no `DISPLAY`/`WAYLAND_DISPLAY`), so this flag is rarely needed
118
+ explicitly.
119
+
120
+ The wizard asks for the config file type, locales, source-file glob, output
121
+ path, and finally **"Translation backend?"** with three options:
122
+
123
+ - **Local files only** (default) — keeps the current local-JSON workflow.
124
+ - **Locize** (recommended for team / production workflows) — opens the
125
+ [Locize](https://www.locize.app) signup page in your browser and then
126
+ prompts for your Project ID and API key. The wizard writes a `locize`
127
+ block into the generated config so [`locize-sync`](#locize-integration)
128
+ works out of the box. The API key prompt can be left empty (read-only
129
+ mode); add it later via a `LOCIZE_API_KEY` environment variable.
130
+ - **Other / skip** — same as "Local files only" for the wizard's purposes.
131
+
109
132
  ### `extract`
110
133
  Parses source files, extracts keys, and updates your JSON translation files.
111
134
 
@@ -120,6 +143,7 @@ npx i18next-cli extract [options]
120
143
  - `--sync-primary`: Sync primary language values with default values from code
121
144
  - `--sync-all`: Sync primary language values with default values from code AND clear synced keys in all other locales (implies `--sync-primary`)
122
145
  - `--trust-derived`: When used with `--sync-primary` or `--sync-all`, also trust defaults inferred from keys such as `t('Hello')` or `keyPrefix`-derived values. This keeps the default sync behavior strict unless you opt in.
146
+ - `--with-types`: After extraction (and on every re-run in `--watch` mode), regenerate the TypeScript definitions whenever translation files changed. Avoids the need to run `extract -w` and `types -w` as two separate processes.
123
147
  - `--quiet`: Suppress spinner and non-essential output (for CI or scripting)
124
148
 
125
149
  ### Spinner and Logger Output Control
@@ -171,6 +195,9 @@ npx i18next-cli extract --sync-all --trust-derived
171
195
 
172
196
  # Combine options for optimal development workflow
173
197
  npx i18next-cli extract --sync-primary --watch
198
+
199
+ # Keep TypeScript definitions in sync from a single process (no separate `types -w` needed)
200
+ npx i18next-cli extract --watch --with-types
174
201
  ```
175
202
 
176
203
  ### `status [locale]`
@@ -447,6 +474,12 @@ npx i18next-cli rename-key "Invalid username or password" "login.form.invalid-cr
447
474
 
448
475
  ### Locize Integration
449
476
 
477
+ **First-time setup:** the easiest way to wire up Locize is to run
478
+ `npx i18next-cli init` and pick **Locize** at the "Translation backend?"
479
+ prompt — the wizard will open the signup page, ask for your Project ID
480
+ and API key, and write the `locize` block into your config for you. See
481
+ [the `init` command](#init) for details.
482
+
450
483
  **Prerequisites:** The locize commands require `locize-cli` to be installed:
451
484
 
452
485
  ```bash
package/dist/cjs/cli.js CHANGED
@@ -32,7 +32,7 @@ const program = new commander.Command();
32
32
  program
33
33
  .name('i18next-cli')
34
34
  .description('A unified, high-performance i18next CLI.')
35
- .version('1.56.12'); // This string is replaced with the actual version at build time by rollup
35
+ .version('1.58.0'); // This string is replaced with the actual version at build time by rollup
36
36
  // new: global config override option
37
37
  program.option('-c, --config <path>', 'Path to i18next-cli config file (overrides detection)');
38
38
  program
@@ -44,6 +44,7 @@ program
44
44
  .option('--sync-primary', 'Sync primary language values with default values from code.')
45
45
  .option('--sync-all', 'Sync primary language values with default values from code AND clear synced keys in all other locales.')
46
46
  .option('--trust-derived', 'When used with --sync-primary or --sync-all, also trust defaults inferred from keys (including keyPrefix-derived values).')
47
+ .option('--with-types', 'After extraction, regenerate TypeScript definitions (runs the types generator) when translation files changed.')
47
48
  .option('-q, --quiet', 'Suppress spinner and output')
48
49
  .action(async (options) => {
49
50
  try {
@@ -72,6 +73,11 @@ program
72
73
  if (hasErrors && !options.watch) {
73
74
  process.exit(1);
74
75
  }
76
+ // Re-generate TypeScript definitions in the same process so consumers
77
+ // don't have to wire a second watcher (avoids the chokidar mid-write race).
78
+ if (options.withTypes && anyFileUpdated && !options.dryRun) {
79
+ await typesGenerator.runTypesGenerator(config$1, { quiet: !!options.quiet });
80
+ }
75
81
  return anyFileUpdated;
76
82
  };
77
83
  // Run the extractor once initially
@@ -138,7 +144,12 @@ program
138
144
  const expandedTypes = await expandGlobs(config$1.types?.input || []);
139
145
  const ignoredTypes = [...toArray(config$1.extract?.ignore)].filter(Boolean);
140
146
  const watchTypes = expandedTypes.filter(f => !ignoredTypes.some(g => minimatch.minimatch(f, g, { dot: true })));
141
- const watcher = chokidar.watch(watchTypes, { persistent: true });
147
+ // awaitWriteFinish avoids triggering mid-write when another process (e.g. `extract -w`)
148
+ // is rewriting the same translation files. See i18next/i18next-cli#257.
149
+ const watcher = chokidar.watch(watchTypes, {
150
+ persistent: true,
151
+ awaitWriteFinish: { stabilityThreshold: 200, pollInterval: 50 },
152
+ });
142
153
  watcher.on('change', path => {
143
154
  console.log(`\nFile changed: ${path}`);
144
155
  run();
@@ -163,7 +174,8 @@ program
163
174
  program
164
175
  .command('init')
165
176
  .description('Create a new i18next.config.ts/js file with an interactive setup wizard.')
166
- .action(init.runInit);
177
+ .option('--ci', 'Skip the browser launch when a backend (e.g. Locize) is selected. The signup URL is printed instead.')
178
+ .action((options) => init.runInit({ ci: !!options.ci }));
167
179
  program
168
180
  .command('lint')
169
181
  .description('Find potential issues like hardcoded strings in your codebase.')
package/dist/cjs/init.js CHANGED
@@ -3,8 +3,58 @@
3
3
  var inquirer = require('inquirer');
4
4
  var promises = require('node:fs/promises');
5
5
  var node_path = require('node:path');
6
+ var execa = require('execa');
6
7
  var heuristicConfig = require('./heuristic-config.js');
7
8
 
9
+ const LOCIZE_SIGNUP_URL = 'https://www.locize.app/register?from=i18next-cli+init+wizard';
10
+ /** Rough 8-4-4-4-12 hex UUID shape — not strict (locize project IDs may evolve). */
11
+ const UUID_SHAPE = /^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$/i;
12
+ /**
13
+ * Opens the given URL in the user's default browser using the platform-native command.
14
+ * Returns true on success, false if there's nowhere to open one (CI, headless Linux)
15
+ * or if spawning the command failed.
16
+ */
17
+ async function openBrowser(url, opts = {}) {
18
+ // Short-circuit: no point spawning a browser-opener in CI or headless Linux.
19
+ if (opts.ci || process.env.CI === 'true')
20
+ return false;
21
+ const isWSL = !!process.env.WSL_DISTRO_NAME;
22
+ if (process.platform === 'linux' && !isWSL &&
23
+ !process.env.DISPLAY && !process.env.WAYLAND_DISPLAY) {
24
+ return false;
25
+ }
26
+ try {
27
+ if (process.platform === 'darwin') {
28
+ await execa.execa('open', [url], { stdio: 'ignore' });
29
+ }
30
+ else if (process.platform === 'win32') {
31
+ // `start` is a cmd.exe builtin; the empty "" is the window-title slot
32
+ await execa.execa('cmd', ['/c', 'start', '""', url], { stdio: 'ignore' });
33
+ }
34
+ else if (isWSL) {
35
+ // WSL: try the wslu / wsl-open shims that bridge to the Windows side
36
+ // before falling back to xdg-open (which usually isn't installed there).
37
+ try {
38
+ await execa.execa('wslview', [url], { stdio: 'ignore' });
39
+ }
40
+ catch {
41
+ try {
42
+ await execa.execa('wsl-open', [url], { stdio: 'ignore' });
43
+ }
44
+ catch {
45
+ await execa.execa('xdg-open', [url], { stdio: 'ignore' });
46
+ }
47
+ }
48
+ }
49
+ else {
50
+ await execa.execa('xdg-open', [url], { stdio: 'ignore' });
51
+ }
52
+ return true;
53
+ }
54
+ catch {
55
+ return false;
56
+ }
57
+ }
8
58
  /**
9
59
  * Determines if the current project is configured as an ESM project.
10
60
  * Checks the package.json file for `"type": "module"`.
@@ -92,7 +142,7 @@ async function isTypeScriptProject() {
92
142
  * // - i18next.config.js (JavaScript ESM/CommonJS)
93
143
  * ```
94
144
  */
95
- async function runInit() {
145
+ async function runInit(options = {}) {
96
146
  console.log('Welcome to the i18next-cli setup wizard!');
97
147
  console.log('Scanning your project for a recommended configuration...');
98
148
  const detectedConfig = await heuristicConfig.detectConfig();
@@ -143,7 +193,49 @@ async function runInit() {
143
193
  ? detectedConfig.extract.output
144
194
  : 'public/locales/{{language}}/{{namespace}}.json',
145
195
  },
196
+ {
197
+ type: 'select',
198
+ name: 'backend',
199
+ message: 'Translation backend?',
200
+ choices: [
201
+ { name: 'Local files only', value: 'local' },
202
+ { name: 'Locize (recommended) — managed backend, CDN delivery, optional AI auto-translate', value: 'locize' },
203
+ { name: 'Other / skip', value: 'other' },
204
+ ],
205
+ default: 'local',
206
+ },
146
207
  ]);
208
+ let locizeConfig;
209
+ if (answers.backend === 'locize') {
210
+ console.log('\nOpening the Locize signup page in your browser. After you create your account and project, come back here and paste your Project ID and API key.');
211
+ const opened = await openBrowser(LOCIZE_SIGNUP_URL, { ci: options.ci });
212
+ if (!opened) {
213
+ console.log(`\n👉 Open this URL manually: ${LOCIZE_SIGNUP_URL}\n`);
214
+ }
215
+ const credentials = await inquirer.prompt([
216
+ {
217
+ type: 'input',
218
+ name: 'projectId',
219
+ message: 'Locize Project ID (e.g. 4eeb5ce0-a7a7-453f-8eb3-078f6eeb56fe):',
220
+ validate: (input) => input.trim().length > 0 || 'Project ID cannot be empty.',
221
+ filter: (input) => input.trim(),
222
+ },
223
+ {
224
+ type: 'password',
225
+ name: 'apiKey',
226
+ message: 'Locize API key (needed for saveMissing / auto-publish / sync during development; leave empty to skip and add later via env var):',
227
+ filter: (input) => input.trim(),
228
+ },
229
+ ]);
230
+ if (!UUID_SHAPE.test(credentials.projectId)) {
231
+ console.log("⚠️ The Project ID doesn't look like a UUID (8-4-4-4-12 hex). It will still be written — double-check it in your Locize project settings.");
232
+ }
233
+ // API keys come in multiple shapes (UUID, `lz_pat_…`, `lz_api_…`, etc.) —
234
+ // treat them as opaque; no client-side format check.
235
+ locizeConfig = { projectId: credentials.projectId };
236
+ if (credentials.apiKey)
237
+ locizeConfig.apiKey = credentials.apiKey;
238
+ }
147
239
  const isTypeScript = answers.fileType.includes('TypeScript');
148
240
  const isEsm = await isEsmProject();
149
241
  const fileName = isTypeScript ? 'i18next.config.ts' : 'i18next.config.js';
@@ -154,6 +246,8 @@ async function runInit() {
154
246
  output: answers.output,
155
247
  },
156
248
  };
249
+ if (locizeConfig)
250
+ configObject.locize = locizeConfig;
157
251
  // Helper to serialize a JS value as a JS literal:
158
252
  function toJs(value, indent = 2, level = 0) {
159
253
  const pad = (n) => ' '.repeat(n * indent);
@@ -225,6 +319,28 @@ module.exports = ${toJs(configObject)}`;
225
319
  const outputPath = node_path.resolve(process.cwd(), fileName);
226
320
  await promises.writeFile(outputPath, fileContent.trim());
227
321
  console.log(`✅ Configuration file created at: ${outputPath}`);
322
+ if (locizeConfig) {
323
+ console.log('\nNext steps for Locize:');
324
+ console.log(' 1. Push your local translations to Locize:');
325
+ console.log(' npx i18next-cli locize-sync');
326
+ console.log(' 2. Find your Project ID and API keys in the Locize UI under:');
327
+ console.log(' Project Settings → "API, CDN, NOTIFICATIONS" tab (www.locize.app)');
328
+ if (locizeConfig.apiKey) {
329
+ console.log(' 3. Before committing, move the API key out of the config file into an environment variable:');
330
+ console.log(' # .env (add to .gitignore)');
331
+ console.log(' LOCIZE_API_KEY=<paste the key currently in your config>');
332
+ console.log(' # then in your config:');
333
+ console.log(' apiKey: process.env.LOCIZE_API_KEY');
334
+ }
335
+ else {
336
+ console.log(' 3. Add your API key later via environment variable:');
337
+ console.log(' # .env (add to .gitignore)');
338
+ console.log(' LOCIZE_API_KEY=...');
339
+ console.log(' # then in your config:');
340
+ console.log(' apiKey: process.env.LOCIZE_API_KEY');
341
+ }
342
+ console.log(' 4. To enable automatic translation, turn it on in your Locize project settings → "EDITOR, TM/MT/AI, ORDERING" tab (web UI).');
343
+ }
228
344
  }
229
345
 
230
346
  exports.runInit = runInit;
package/dist/esm/cli.js CHANGED
@@ -30,7 +30,7 @@ const program = new Command();
30
30
  program
31
31
  .name('i18next-cli')
32
32
  .description('A unified, high-performance i18next CLI.')
33
- .version('1.56.12'); // This string is replaced with the actual version at build time by rollup
33
+ .version('1.58.0'); // This string is replaced with the actual version at build time by rollup
34
34
  // new: global config override option
35
35
  program.option('-c, --config <path>', 'Path to i18next-cli config file (overrides detection)');
36
36
  program
@@ -42,6 +42,7 @@ program
42
42
  .option('--sync-primary', 'Sync primary language values with default values from code.')
43
43
  .option('--sync-all', 'Sync primary language values with default values from code AND clear synced keys in all other locales.')
44
44
  .option('--trust-derived', 'When used with --sync-primary or --sync-all, also trust defaults inferred from keys (including keyPrefix-derived values).')
45
+ .option('--with-types', 'After extraction, regenerate TypeScript definitions (runs the types generator) when translation files changed.')
45
46
  .option('-q, --quiet', 'Suppress spinner and output')
46
47
  .action(async (options) => {
47
48
  try {
@@ -70,6 +71,11 @@ program
70
71
  if (hasErrors && !options.watch) {
71
72
  process.exit(1);
72
73
  }
74
+ // Re-generate TypeScript definitions in the same process so consumers
75
+ // don't have to wire a second watcher (avoids the chokidar mid-write race).
76
+ if (options.withTypes && anyFileUpdated && !options.dryRun) {
77
+ await runTypesGenerator(config, { quiet: !!options.quiet });
78
+ }
73
79
  return anyFileUpdated;
74
80
  };
75
81
  // Run the extractor once initially
@@ -136,7 +142,12 @@ program
136
142
  const expandedTypes = await expandGlobs(config.types?.input || []);
137
143
  const ignoredTypes = [...toArray(config.extract?.ignore)].filter(Boolean);
138
144
  const watchTypes = expandedTypes.filter(f => !ignoredTypes.some(g => minimatch(f, g, { dot: true })));
139
- const watcher = chokidar.watch(watchTypes, { persistent: true });
145
+ // awaitWriteFinish avoids triggering mid-write when another process (e.g. `extract -w`)
146
+ // is rewriting the same translation files. See i18next/i18next-cli#257.
147
+ const watcher = chokidar.watch(watchTypes, {
148
+ persistent: true,
149
+ awaitWriteFinish: { stabilityThreshold: 200, pollInterval: 50 },
150
+ });
140
151
  watcher.on('change', path => {
141
152
  console.log(`\nFile changed: ${path}`);
142
153
  run();
@@ -161,7 +172,8 @@ program
161
172
  program
162
173
  .command('init')
163
174
  .description('Create a new i18next.config.ts/js file with an interactive setup wizard.')
164
- .action(runInit);
175
+ .option('--ci', 'Skip the browser launch when a backend (e.g. Locize) is selected. The signup URL is printed instead.')
176
+ .action((options) => runInit({ ci: !!options.ci }));
165
177
  program
166
178
  .command('lint')
167
179
  .description('Find potential issues like hardcoded strings in your codebase.')
package/dist/esm/init.js CHANGED
@@ -1,8 +1,58 @@
1
1
  import inquirer from 'inquirer';
2
2
  import { writeFile, readFile } from 'node:fs/promises';
3
3
  import { resolve } from 'node:path';
4
+ import { execa } from 'execa';
4
5
  import { detectConfig } from './heuristic-config.js';
5
6
 
7
+ const LOCIZE_SIGNUP_URL = 'https://www.locize.app/register?from=i18next-cli+init+wizard';
8
+ /** Rough 8-4-4-4-12 hex UUID shape — not strict (locize project IDs may evolve). */
9
+ const UUID_SHAPE = /^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$/i;
10
+ /**
11
+ * Opens the given URL in the user's default browser using the platform-native command.
12
+ * Returns true on success, false if there's nowhere to open one (CI, headless Linux)
13
+ * or if spawning the command failed.
14
+ */
15
+ async function openBrowser(url, opts = {}) {
16
+ // Short-circuit: no point spawning a browser-opener in CI or headless Linux.
17
+ if (opts.ci || process.env.CI === 'true')
18
+ return false;
19
+ const isWSL = !!process.env.WSL_DISTRO_NAME;
20
+ if (process.platform === 'linux' && !isWSL &&
21
+ !process.env.DISPLAY && !process.env.WAYLAND_DISPLAY) {
22
+ return false;
23
+ }
24
+ try {
25
+ if (process.platform === 'darwin') {
26
+ await execa('open', [url], { stdio: 'ignore' });
27
+ }
28
+ else if (process.platform === 'win32') {
29
+ // `start` is a cmd.exe builtin; the empty "" is the window-title slot
30
+ await execa('cmd', ['/c', 'start', '""', url], { stdio: 'ignore' });
31
+ }
32
+ else if (isWSL) {
33
+ // WSL: try the wslu / wsl-open shims that bridge to the Windows side
34
+ // before falling back to xdg-open (which usually isn't installed there).
35
+ try {
36
+ await execa('wslview', [url], { stdio: 'ignore' });
37
+ }
38
+ catch {
39
+ try {
40
+ await execa('wsl-open', [url], { stdio: 'ignore' });
41
+ }
42
+ catch {
43
+ await execa('xdg-open', [url], { stdio: 'ignore' });
44
+ }
45
+ }
46
+ }
47
+ else {
48
+ await execa('xdg-open', [url], { stdio: 'ignore' });
49
+ }
50
+ return true;
51
+ }
52
+ catch {
53
+ return false;
54
+ }
55
+ }
6
56
  /**
7
57
  * Determines if the current project is configured as an ESM project.
8
58
  * Checks the package.json file for `"type": "module"`.
@@ -90,7 +140,7 @@ async function isTypeScriptProject() {
90
140
  * // - i18next.config.js (JavaScript ESM/CommonJS)
91
141
  * ```
92
142
  */
93
- async function runInit() {
143
+ async function runInit(options = {}) {
94
144
  console.log('Welcome to the i18next-cli setup wizard!');
95
145
  console.log('Scanning your project for a recommended configuration...');
96
146
  const detectedConfig = await detectConfig();
@@ -141,7 +191,49 @@ async function runInit() {
141
191
  ? detectedConfig.extract.output
142
192
  : 'public/locales/{{language}}/{{namespace}}.json',
143
193
  },
194
+ {
195
+ type: 'select',
196
+ name: 'backend',
197
+ message: 'Translation backend?',
198
+ choices: [
199
+ { name: 'Local files only', value: 'local' },
200
+ { name: 'Locize (recommended) — managed backend, CDN delivery, optional AI auto-translate', value: 'locize' },
201
+ { name: 'Other / skip', value: 'other' },
202
+ ],
203
+ default: 'local',
204
+ },
144
205
  ]);
206
+ let locizeConfig;
207
+ if (answers.backend === 'locize') {
208
+ console.log('\nOpening the Locize signup page in your browser. After you create your account and project, come back here and paste your Project ID and API key.');
209
+ const opened = await openBrowser(LOCIZE_SIGNUP_URL, { ci: options.ci });
210
+ if (!opened) {
211
+ console.log(`\n👉 Open this URL manually: ${LOCIZE_SIGNUP_URL}\n`);
212
+ }
213
+ const credentials = await inquirer.prompt([
214
+ {
215
+ type: 'input',
216
+ name: 'projectId',
217
+ message: 'Locize Project ID (e.g. 4eeb5ce0-a7a7-453f-8eb3-078f6eeb56fe):',
218
+ validate: (input) => input.trim().length > 0 || 'Project ID cannot be empty.',
219
+ filter: (input) => input.trim(),
220
+ },
221
+ {
222
+ type: 'password',
223
+ name: 'apiKey',
224
+ message: 'Locize API key (needed for saveMissing / auto-publish / sync during development; leave empty to skip and add later via env var):',
225
+ filter: (input) => input.trim(),
226
+ },
227
+ ]);
228
+ if (!UUID_SHAPE.test(credentials.projectId)) {
229
+ console.log("⚠️ The Project ID doesn't look like a UUID (8-4-4-4-12 hex). It will still be written — double-check it in your Locize project settings.");
230
+ }
231
+ // API keys come in multiple shapes (UUID, `lz_pat_…`, `lz_api_…`, etc.) —
232
+ // treat them as opaque; no client-side format check.
233
+ locizeConfig = { projectId: credentials.projectId };
234
+ if (credentials.apiKey)
235
+ locizeConfig.apiKey = credentials.apiKey;
236
+ }
145
237
  const isTypeScript = answers.fileType.includes('TypeScript');
146
238
  const isEsm = await isEsmProject();
147
239
  const fileName = isTypeScript ? 'i18next.config.ts' : 'i18next.config.js';
@@ -152,6 +244,8 @@ async function runInit() {
152
244
  output: answers.output,
153
245
  },
154
246
  };
247
+ if (locizeConfig)
248
+ configObject.locize = locizeConfig;
155
249
  // Helper to serialize a JS value as a JS literal:
156
250
  function toJs(value, indent = 2, level = 0) {
157
251
  const pad = (n) => ' '.repeat(n * indent);
@@ -223,6 +317,28 @@ module.exports = ${toJs(configObject)}`;
223
317
  const outputPath = resolve(process.cwd(), fileName);
224
318
  await writeFile(outputPath, fileContent.trim());
225
319
  console.log(`✅ Configuration file created at: ${outputPath}`);
320
+ if (locizeConfig) {
321
+ console.log('\nNext steps for Locize:');
322
+ console.log(' 1. Push your local translations to Locize:');
323
+ console.log(' npx i18next-cli locize-sync');
324
+ console.log(' 2. Find your Project ID and API keys in the Locize UI under:');
325
+ console.log(' Project Settings → "API, CDN, NOTIFICATIONS" tab (www.locize.app)');
326
+ if (locizeConfig.apiKey) {
327
+ console.log(' 3. Before committing, move the API key out of the config file into an environment variable:');
328
+ console.log(' # .env (add to .gitignore)');
329
+ console.log(' LOCIZE_API_KEY=<paste the key currently in your config>');
330
+ console.log(' # then in your config:');
331
+ console.log(' apiKey: process.env.LOCIZE_API_KEY');
332
+ }
333
+ else {
334
+ console.log(' 3. Add your API key later via environment variable:');
335
+ console.log(' # .env (add to .gitignore)');
336
+ console.log(' LOCIZE_API_KEY=...');
337
+ console.log(' # then in your config:');
338
+ console.log(' apiKey: process.env.LOCIZE_API_KEY');
339
+ }
340
+ console.log(' 4. To enable automatic translation, turn it on in your Locize project settings → "EDITOR, TM/MT/AI, ORDERING" tab (web UI).');
341
+ }
226
342
  }
227
343
 
228
344
  export { runInit };
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "i18next-cli",
3
- "version": "1.56.12",
3
+ "version": "1.58.0",
4
4
  "description": "A unified, high-performance i18next CLI.",
5
5
  "type": "module",
6
6
  "bin": {
@@ -1 +1 @@
1
- {"version":3,"file":"cli.d.ts","sourceRoot":"","sources":["../src/cli.ts"],"names":[],"mappings":";AAEA,OAAO,EAAE,OAAO,EAAE,MAAM,WAAW,CAAA;AAoBnC,QAAA,MAAM,OAAO,SAAgB,CAAA;AAiZ7B,OAAO,EAAE,OAAO,EAAE,CAAA"}
1
+ {"version":3,"file":"cli.d.ts","sourceRoot":"","sources":["../src/cli.ts"],"names":[],"mappings":";AAEA,OAAO,EAAE,OAAO,EAAE,MAAM,WAAW,CAAA;AAoBnC,QAAA,MAAM,OAAO,SAAgB,CAAA;AA8Z7B,OAAO,EAAE,OAAO,EAAE,CAAA"}
package/types/init.d.ts CHANGED
@@ -25,5 +25,7 @@
25
25
  * // - i18next.config.js (JavaScript ESM/CommonJS)
26
26
  * ```
27
27
  */
28
- export declare function runInit(): Promise<void>;
28
+ export declare function runInit(options?: {
29
+ ci?: boolean;
30
+ }): Promise<void>;
29
31
  //# sourceMappingURL=init.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"init.d.ts","sourceRoot":"","sources":["../src/init.ts"],"names":[],"mappings":"AAiEA;;;;;;;;;;;;;;;;;;;;;;;;;;GA0BG;AACH,wBAAsB,OAAO,kBAyI5B"}
1
+ {"version":3,"file":"init.d.ts","sourceRoot":"","sources":["../src/init.ts"],"names":[],"mappings":"AAkHA;;;;;;;;;;;;;;;;;;;;;;;;;;GA0BG;AACH,wBAAsB,OAAO,CAAE,OAAO,GAAE;IAAE,EAAE,CAAC,EAAE,OAAO,CAAA;CAAO,iBA6M5D"}