i18next-cli 0.9.11 → 0.9.12

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
@@ -9,6 +9,15 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
9
9
 
10
10
  - not yet released
11
11
 
12
+ ## [0.9.12](https://github.com/i18next/i18next-cli/compare/v0.9.11...v0.9.12) - 2025-09-27
13
+
14
+ ### Added
15
+ - **CLI:** The `init` command is now smarter. It uses a heuristic scan of the project to suggest tailored defaults for locales and file paths in the interactive setup wizard.
16
+
17
+ ### Fixed
18
+ - **CLI:** Fixed a critical bug where the `types` command would hang without exiting after generating files if they already existed. [#5](https://github.com/i18next/i18next-cli/issues/5)
19
+ - **Types Generator:** Corrected an issue where default configuration values were not being applied, causing the `types` command to fail if the `types` property was not explicitly defined in the config file.
20
+
12
21
  ## [0.9.11](https://github.com/i18next/i18next-cli/compare/v0.9.10...v0.9.11) - 2025-09-26
13
22
 
14
23
  ### Added
package/dist/cjs/cli.js CHANGED
@@ -1,2 +1,2 @@
1
1
  #!/usr/bin/env node
2
- "use strict";var e=require("commander"),t=require("chokidar"),o=require("glob"),n=require("chalk"),i=require("./config.js"),a=require("./heuristic-config.js"),r=require("./extractor/core/extractor.js");require("node:path"),require("node:fs/promises"),require("jiti");var c=require("./types-generator.js"),s=require("./syncer.js"),l=require("./migrator.js"),u=require("./init.js"),d=require("./linter.js"),g=require("./status.js"),p=require("./locize.js");const f=new e.Command;f.name("i18next-cli").description("A unified, high-performance i18next CLI.").version("0.9.11"),f.command("extract").description("Extract translation keys from source files and update resource files.").option("-w, --watch","Watch for file changes and re-run the extractor.").option("--ci","Exit with a non-zero status code if any files are updated.").action(async e=>{const a=await i.ensureConfig(),c=async()=>{const t=await r.runExtractor(a);e.ci&&t&&(console.error(n.red.bold("\n[CI Mode] Error: Translation files were updated. Please commit the changes.")),console.log(n.yellow("💡 Tip: Tired of committing JSON files? locize syncs your team automatically => https://www.locize.com/docs/getting-started")),console.log(` Learn more: ${n.cyan("npx i18next-cli locize-sync")}`),process.exit(1))};if(await c(),e.watch){console.log("\nWatching for changes...");t.watch(await o.glob(a.extract.input),{ignored:/node_modules/,persistent:!0}).on("change",e=>{console.log(`\nFile changed: ${e}`),c()})}}),f.command("status [locale]").description("Display translation status. Provide a locale for a detailed key-by-key view.").option("-n, --namespace <ns>","Filter the status report by a specific namespace").action(async(e,t)=>{let o=await i.loadConfig();if(!o){console.log(n.blue("No config file found. Attempting to detect project structure..."));const e=await a.detectConfig();e||(console.error(n.red("Could not automatically detect your project structure.")),console.log(`Please create a config file first by running: ${n.cyan("npx i18next-cli init")}`),process.exit(1)),console.log(n.green("Project structure detected successfully!")),o=e}await g.runStatus(o,{detail:e,namespace:t.namespace})}),f.command("types").description("Generate TypeScript definitions from translation resource files.").option("-w, --watch","Watch for file changes and re-run the type generator.").action(async e=>{const n=await i.ensureConfig(),a=()=>c.runTypesGenerator(n);if(await a(),e.watch){console.log("\nWatching for changes...");t.watch(await o.glob(n.types?.input||[]),{persistent:!0}).on("change",e=>{console.log(`\nFile changed: ${e}`),a()})}}),f.command("sync").description("Synchronize secondary language files with the primary language file.").action(async()=>{const e=await i.ensureConfig();await s.runSyncer(e)}),f.command("migrate-config").description("Migrate a legacy i18next-parser.config.js to the new format.").action(async()=>{await l.runMigrator()}),f.command("init").description("Create a new i18next.config.ts/js file with an interactive setup wizard.").action(u.runInit),f.command("lint").description("Find potential issues like hardcoded strings in your codebase.").action(async()=>{let e=await i.loadConfig();if(!e){console.log(n.blue("No config file found. Attempting to detect project structure..."));const t=await a.detectConfig();t||(console.error(n.red("Could not automatically detect your project structure.")),console.log(`Please create a config file first by running: ${n.cyan("npx i1e-toolkit init")}`),process.exit(1)),console.log(n.green("Project structure detected successfully!")),e=t}await d.runLinter(e)}),f.command("locize-sync").description("Synchronize local translations with your locize project.").option("--update-values","Update values of existing translations on locize.").option("--src-lng-only","Check for changes in source language only.").option("--compare-mtime","Compare modification times when syncing.").option("--dry-run","Run the command without making any changes.").action(async e=>{const t=await i.ensureConfig();await p.runLocizeSync(t,e)}),f.command("locize-download").description("Download all translations from your locize project.").action(async e=>{const t=await i.ensureConfig();await p.runLocizeDownload(t,e)}),f.command("locize-migrate").description("Migrate local translation files to a new locize project.").action(async e=>{const t=await i.ensureConfig();await p.runLocizeMigrate(t,e)}),f.parse(process.argv);
2
+ "use strict";var e=require("commander"),t=require("chokidar"),o=require("glob"),n=require("chalk"),i=require("./config.js"),a=require("./heuristic-config.js"),r=require("./extractor/core/extractor.js");require("node:path"),require("node:fs/promises"),require("jiti");var c=require("./types-generator.js"),s=require("./syncer.js"),l=require("./migrator.js"),u=require("./init.js"),d=require("./linter.js"),g=require("./status.js"),p=require("./locize.js");const f=new e.Command;f.name("i18next-cli").description("A unified, high-performance i18next CLI.").version("0.9.12"),f.command("extract").description("Extract translation keys from source files and update resource files.").option("-w, --watch","Watch for file changes and re-run the extractor.").option("--ci","Exit with a non-zero status code if any files are updated.").action(async e=>{const a=await i.ensureConfig(),c=async()=>{const t=await r.runExtractor(a);e.ci&&t&&(console.error(n.red.bold("\n[CI Mode] Error: Translation files were updated. Please commit the changes.")),console.log(n.yellow("💡 Tip: Tired of committing JSON files? locize syncs your team automatically => https://www.locize.com/docs/getting-started")),console.log(` Learn more: ${n.cyan("npx i18next-cli locize-sync")}`),process.exit(1))};if(await c(),e.watch){console.log("\nWatching for changes...");t.watch(await o.glob(a.extract.input),{ignored:/node_modules/,persistent:!0}).on("change",e=>{console.log(`\nFile changed: ${e}`),c()})}}),f.command("status [locale]").description("Display translation status. Provide a locale for a detailed key-by-key view.").option("-n, --namespace <ns>","Filter the status report by a specific namespace").action(async(e,t)=>{let o=await i.loadConfig();if(!o){console.log(n.blue("No config file found. Attempting to detect project structure..."));const e=await a.detectConfig();e||(console.error(n.red("Could not automatically detect your project structure.")),console.log(`Please create a config file first by running: ${n.cyan("npx i18next-cli init")}`),process.exit(1)),console.log(n.green("Project structure detected successfully!")),o=e}await g.runStatus(o,{detail:e,namespace:t.namespace})}),f.command("types").description("Generate TypeScript definitions from translation resource files.").option("-w, --watch","Watch for file changes and re-run the type generator.").action(async e=>{const n=await i.ensureConfig(),a=()=>c.runTypesGenerator(n);if(await a(),e.watch){console.log("\nWatching for changes...");t.watch(await o.glob(n.types?.input||[]),{persistent:!0}).on("change",e=>{console.log(`\nFile changed: ${e}`),a()})}}),f.command("sync").description("Synchronize secondary language files with the primary language file.").action(async()=>{const e=await i.ensureConfig();await s.runSyncer(e)}),f.command("migrate-config").description("Migrate a legacy i18next-parser.config.js to the new format.").action(async()=>{await l.runMigrator()}),f.command("init").description("Create a new i18next.config.ts/js file with an interactive setup wizard.").action(u.runInit),f.command("lint").description("Find potential issues like hardcoded strings in your codebase.").action(async()=>{let e=await i.loadConfig();if(!e){console.log(n.blue("No config file found. Attempting to detect project structure..."));const t=await a.detectConfig();t||(console.error(n.red("Could not automatically detect your project structure.")),console.log(`Please create a config file first by running: ${n.cyan("npx i1e-toolkit init")}`),process.exit(1)),console.log(n.green("Project structure detected successfully!")),e=t}await d.runLinter(e)}),f.command("locize-sync").description("Synchronize local translations with your locize project.").option("--update-values","Update values of existing translations on locize.").option("--src-lng-only","Check for changes in source language only.").option("--compare-mtime","Compare modification times when syncing.").option("--dry-run","Run the command without making any changes.").action(async e=>{const t=await i.ensureConfig();await p.runLocizeSync(t,e)}),f.command("locize-download").description("Download all translations from your locize project.").action(async e=>{const t=await i.ensureConfig();await p.runLocizeDownload(t,e)}),f.command("locize-migrate").description("Migrate local translation files to a new locize project.").action(async e=>{const t=await i.ensureConfig();await p.runLocizeMigrate(t,e)}),f.parse(process.argv);
package/dist/cjs/init.js CHANGED
@@ -1 +1 @@
1
- "use strict";var e=require("inquirer"),t=require("node:fs/promises"),n=require("node:path");exports.runInit=async function(){console.log("Welcome to the i18next-cli setup wizard!");const i=await e.prompt([{type:"list",name:"fileType",message:"What kind of configuration file do you want?",choices:["TypeScript (i18next.config.ts)","JavaScript (i18next.config.js)"]},{type:"input",name:"locales",message:"What locales does your project support? (comma-separated)",default:"en,de,fr",filter:e=>e.split(",").map(e=>e.trim())},{type:"input",name:"input",message:"What is the glob pattern for your source files?",default:"src/**/*.{js,jsx,ts,tsx}"},{type:"input",name:"output",message:"What is the path for your output resource files?",default:"public/locales/{{language}}/{{namespace}}.json"}]),o=i.fileType.includes("TypeScript"),r=await async function(){try{const e=n.resolve(process.cwd(),"package.json"),i=await t.readFile(e,"utf-8");return"module"===JSON.parse(i).type}catch{return!0}}(),s=o?"i18next.config.ts":"i18next.config.js",a={locales:i.locales,extract:{input:i.input,output:i.output}};function p(e,t=2,n=0){const i=e=>" ".repeat(e*t),o=i(n),r=i(n+1);if(null===e||"number"==typeof e||"boolean"==typeof e)return JSON.stringify(e);if("string"==typeof e)return JSON.stringify(e);if(Array.isArray(e)){if(0===e.length)return"[]";return`[\n${e.map(e=>`${r}${p(e,t,n+1)}`).join(",\n")}\n${o}]`}if("object"==typeof e){const i=Object.keys(e);if(0===i.length)return"{}";return`{\n${i.map(i=>{const o=/^[A-Za-z_$][A-Za-z0-9_$]*$/.test(i)?i:JSON.stringify(i);return`${r}${o}: ${p(e[i],t,n+1)}`}).join(",\n")}\n${o}}`}return JSON.stringify(e)}let f="";f=o?`import { defineConfig } from 'i18next-cli';\n\nexport default defineConfig(${p(a)});`:r?`import { defineConfig } from 'i18next-cli';\n\n/** @type {import('i18next-cli').I18nextToolkitConfig} */\nexport default defineConfig(${p(a)});`:`const { defineConfig } = require('i18next-cli');\n\n/** @type {import('i18next-cli').I18nextToolkitConfig} */\nmodule.exports = defineConfig(${p(a)});`;const u=n.resolve(process.cwd(),s);await t.writeFile(u,f.trim()),console.log(`✅ Configuration file created at: ${u}`)};
1
+ "use strict";var e=require("inquirer"),t=require("node:fs/promises"),n=require("node:path"),i=require("./heuristic-config.js");exports.runInit=async function(){console.log("Welcome to the i18next-cli setup wizard!"),console.log("Scanning your project for a recommended configuration...");const o=await i.detectConfig();o?console.log("✅ Found a potential project structure. Using it for suggestions."):console.log("Could not detect a project structure. Using standard defaults."),"string"==typeof o?.extract?.input&&(o.extract.input=[o?.extract?.input]);const r=await e.prompt([{type:"list",name:"fileType",message:"What kind of configuration file do you want?",choices:["TypeScript (i18next.config.ts)","JavaScript (i18next.config.js)"]},{type:"input",name:"locales",message:"What locales does your project support? (comma-separated)",default:o?.locales?.join(",")||"en,de,fr",filter:e=>e.split(",").map(e=>e.trim())},{type:"input",name:"input",message:"What is the glob pattern for your source files?",default:o?.extract?.input?(o.extract.input||[])[0]:"src/**/*.{js,jsx,ts,tsx}"},{type:"input",name:"output",message:"What is the path for your output resource files?",default:o?.extract?.output||"public/locales/{{language}}/{{namespace}}.json"}]),s=r.fileType.includes("TypeScript"),a=await async function(){try{const e=n.resolve(process.cwd(),"package.json"),i=await t.readFile(e,"utf-8");return"module"===JSON.parse(i).type}catch{return!0}}(),c=s?"i18next.config.ts":"i18next.config.js",u={locales:r.locales,extract:{input:r.input,output:r.output}};function p(e,t=2,n=0){const i=e=>" ".repeat(e*t),o=i(n),r=i(n+1);if(null===e||"number"==typeof e||"boolean"==typeof e)return JSON.stringify(e);if("string"==typeof e)return JSON.stringify(e);if(Array.isArray(e)){if(0===e.length)return"[]";return`[\n${e.map(e=>`${r}${p(e,t,n+1)}`).join(",\n")}\n${o}]`}if("object"==typeof e){const i=Object.keys(e);if(0===i.length)return"{}";return`{\n${i.map(i=>{const o=/^[A-Za-z_$][A-Za-z0-9_$]*$/.test(i)?i:JSON.stringify(i);return`${r}${o}: ${p(e[i],t,n+1)}`}).join(",\n")}\n${o}}`}return JSON.stringify(e)}let f="";f=s?`import { defineConfig } from 'i18next-cli';\n\nexport default defineConfig(${p(u)});`:a?`import { defineConfig } from 'i18next-cli';\n\n/** @type {import('i18next-cli').I18nextToolkitConfig} */\nexport default defineConfig(${p(u)});`:`const { defineConfig } = require('i18next-cli');\n\n/** @type {import('i18next-cli').I18nextToolkitConfig} */\nmodule.exports = defineConfig(${p(u)});`;const l=n.resolve(process.cwd(),c);await t.writeFile(l,f.trim()),console.log(`✅ Configuration file created at: ${l}`)};
@@ -1 +1 @@
1
- "use strict";var e=require("i18next-resources-for-ts"),t=require("glob"),s=require("ora"),r=require("chalk"),i=require("node:fs/promises"),n=require("node:path");exports.runTypesGenerator=async function(o){const a=s("Generating TypeScript types for translations...\n").start();try{if(o.types||(o.types={input:["locales/en/*.json"],output:"src/types/i18next.d.ts"}),void 0===o.types.input&&(o.types.input=["locales/en/*.json"]),o.types.output||(o.types.output="src/types/i18next.d.ts"),o.types.resourcesFile||(o.types.resourcesFile=n.join(n.dirname(o.types?.output),"resources.d.ts")),!o.types?.input||o.types?.input.length<0)return void console.log("No input defined!");const s=await t.glob(o.types?.input||[],{cwd:process.cwd()}),c=[];for(const e of s){const t=n.basename(e,n.extname(e)),s=await i.readFile(e,"utf-8"),r=JSON.parse(s);c.push({name:t,resources:r})}const p=[],u=o.types?.enableSelector||!1,l=e.mergeResourcesAsInterface(c,{optimize:!!u}),d=n.resolve(process.cwd(),o.types?.output||""),y=n.resolve(process.cwd(),o.types.resourcesFile);let f;await i.mkdir(n.dirname(y),{recursive:!0}),await i.writeFile(y,l),p.push(` ${r.green("✓")} Resources interface written to ${o.types.resourcesFile}`);try{await i.access(d),f=!0}catch(e){f=!1}if(!f){const e=`// This file is automatically generated by i18next-cli. Do not edit manually.\nimport Resources from './${n.relative(n.dirname(d),y).replace(/\\/g,"/").replace(/\.d\.ts$/,"")}';\n\ndeclare module 'i18next' {\n interface CustomTypeOptions {\n enableSelector: ${"string"==typeof u?`"${u}"`:u};\n defaultNS: '${o.extract.defaultNS||"translation"}';\n resources: Resources;\n }\n}`;await i.mkdir(n.dirname(d),{recursive:!0}),await i.writeFile(d,e),p.push(` ${r.green("✓")} TypeScript definitions written to ${o.types.output||""}`),a.succeed(r.bold("TypeScript definitions generated successfully.")),p.forEach(e=>console.log(e))}}catch(e){a.fail(r.red("Failed to generate TypeScript definitions.")),console.error(e)}};
1
+ "use strict";var e=require("i18next-resources-for-ts"),t=require("glob"),r=require("ora"),s=require("chalk"),i=require("node:fs/promises"),n=require("node:path"),o=require("./utils/file-utils.js");exports.runTypesGenerator=async function(a){const c=r("Generating TypeScript types for translations...\n").start();try{a.extract.primaryLanguage||=a.locales[0]||"en";let r=a.extract.output||`locales/${a.extract.primaryLanguage}/*.json`;if(r=o.getOutputPath(r,a.extract.primaryLanguage||"en","*"),a.types||(a.types={input:r,output:"src/@types/i18next.d.ts"}),void 0===a.types.input&&(a.types.input=r),a.types.output||(a.types.output="src/@types/i18next.d.ts"),a.types.resourcesFile||(a.types.resourcesFile=n.join(n.dirname(a.types?.output),"resources.d.ts")),!a.types?.input||a.types?.input.length<0)return void console.log("No input defined!");const u=await t.glob(a.types?.input||[],{cwd:process.cwd()}),p=[];for(const e of u){const t=n.basename(e,n.extname(e)),r=await i.readFile(e,"utf-8"),s=JSON.parse(r);p.push({name:t,resources:s})}const l=[],y=a.types?.enableSelector||!1,d=e.mergeResourcesAsInterface(p,{optimize:!!y}),f=n.resolve(process.cwd(),a.types?.output||""),g=n.resolve(process.cwd(),a.types.resourcesFile);let m;await i.mkdir(n.dirname(g),{recursive:!0}),await i.writeFile(g,d),l.push(` ${s.green("✓")} Resources interface written to ${a.types.resourcesFile}`);try{await i.access(f),m=!0}catch(e){m=!1}if(!m){const e=`// This file is automatically generated by i18next-cli. Do not edit manually.\nimport Resources from './${n.relative(n.dirname(f),g).replace(/\\/g,"/").replace(/\.d\.ts$/,"")}';\n\ndeclare module 'i18next' {\n interface CustomTypeOptions {\n enableSelector: ${"string"==typeof y?`"${y}"`:y};\n defaultNS: '${a.extract.defaultNS||"translation"}';\n resources: Resources;\n }\n}`;await i.mkdir(n.dirname(f),{recursive:!0}),await i.writeFile(f,e),l.push(` ${s.green("✓")} TypeScript definitions written to ${a.types.output||""}`)}c.succeed(s.bold("TypeScript definitions generated successfully.")),l.forEach(e=>console.log(e))}catch(e){c.fail(s.red("Failed to generate TypeScript definitions.")),console.error(e)}};
package/dist/esm/cli.js CHANGED
@@ -1,2 +1,2 @@
1
1
  #!/usr/bin/env node
2
- import{Command as o}from"commander";import t from"chokidar";import{glob as e}from"glob";import i from"chalk";import{ensureConfig as n,loadConfig as a}from"./config.js";import{detectConfig as c}from"./heuristic-config.js";import{runExtractor as r}from"./extractor/core/extractor.js";import"node:path";import"node:fs/promises";import"jiti";import{runTypesGenerator as s}from"./types-generator.js";import{runSyncer as l}from"./syncer.js";import{runMigrator as m}from"./migrator.js";import{runInit as p}from"./init.js";import{runLinter as d}from"./linter.js";import{runStatus as f}from"./status.js";import{runLocizeSync as g,runLocizeDownload as u,runLocizeMigrate as y}from"./locize.js";const w=new o;w.name("i18next-cli").description("A unified, high-performance i18next CLI.").version("0.9.11"),w.command("extract").description("Extract translation keys from source files and update resource files.").option("-w, --watch","Watch for file changes and re-run the extractor.").option("--ci","Exit with a non-zero status code if any files are updated.").action(async o=>{const a=await n(),c=async()=>{const t=await r(a);o.ci&&t&&(console.error(i.red.bold("\n[CI Mode] Error: Translation files were updated. Please commit the changes.")),console.log(i.yellow("💡 Tip: Tired of committing JSON files? locize syncs your team automatically => https://www.locize.com/docs/getting-started")),console.log(` Learn more: ${i.cyan("npx i18next-cli locize-sync")}`),process.exit(1))};if(await c(),o.watch){console.log("\nWatching for changes...");t.watch(await e(a.extract.input),{ignored:/node_modules/,persistent:!0}).on("change",o=>{console.log(`\nFile changed: ${o}`),c()})}}),w.command("status [locale]").description("Display translation status. Provide a locale for a detailed key-by-key view.").option("-n, --namespace <ns>","Filter the status report by a specific namespace").action(async(o,t)=>{let e=await a();if(!e){console.log(i.blue("No config file found. Attempting to detect project structure..."));const o=await c();o||(console.error(i.red("Could not automatically detect your project structure.")),console.log(`Please create a config file first by running: ${i.cyan("npx i18next-cli init")}`),process.exit(1)),console.log(i.green("Project structure detected successfully!")),e=o}await f(e,{detail:o,namespace:t.namespace})}),w.command("types").description("Generate TypeScript definitions from translation resource files.").option("-w, --watch","Watch for file changes and re-run the type generator.").action(async o=>{const i=await n(),a=()=>s(i);if(await a(),o.watch){console.log("\nWatching for changes...");t.watch(await e(i.types?.input||[]),{persistent:!0}).on("change",o=>{console.log(`\nFile changed: ${o}`),a()})}}),w.command("sync").description("Synchronize secondary language files with the primary language file.").action(async()=>{const o=await n();await l(o)}),w.command("migrate-config").description("Migrate a legacy i18next-parser.config.js to the new format.").action(async()=>{await m()}),w.command("init").description("Create a new i18next.config.ts/js file with an interactive setup wizard.").action(p),w.command("lint").description("Find potential issues like hardcoded strings in your codebase.").action(async()=>{let o=await a();if(!o){console.log(i.blue("No config file found. Attempting to detect project structure..."));const t=await c();t||(console.error(i.red("Could not automatically detect your project structure.")),console.log(`Please create a config file first by running: ${i.cyan("npx i1e-toolkit init")}`),process.exit(1)),console.log(i.green("Project structure detected successfully!")),o=t}await d(o)}),w.command("locize-sync").description("Synchronize local translations with your locize project.").option("--update-values","Update values of existing translations on locize.").option("--src-lng-only","Check for changes in source language only.").option("--compare-mtime","Compare modification times when syncing.").option("--dry-run","Run the command without making any changes.").action(async o=>{const t=await n();await g(t,o)}),w.command("locize-download").description("Download all translations from your locize project.").action(async o=>{const t=await n();await u(t,o)}),w.command("locize-migrate").description("Migrate local translation files to a new locize project.").action(async o=>{const t=await n();await y(t,o)}),w.parse(process.argv);
2
+ import{Command as o}from"commander";import t from"chokidar";import{glob as e}from"glob";import i from"chalk";import{ensureConfig as n,loadConfig as a}from"./config.js";import{detectConfig as c}from"./heuristic-config.js";import{runExtractor as r}from"./extractor/core/extractor.js";import"node:path";import"node:fs/promises";import"jiti";import{runTypesGenerator as s}from"./types-generator.js";import{runSyncer as l}from"./syncer.js";import{runMigrator as m}from"./migrator.js";import{runInit as p}from"./init.js";import{runLinter as d}from"./linter.js";import{runStatus as f}from"./status.js";import{runLocizeSync as g,runLocizeDownload as u,runLocizeMigrate as y}from"./locize.js";const w=new o;w.name("i18next-cli").description("A unified, high-performance i18next CLI.").version("0.9.12"),w.command("extract").description("Extract translation keys from source files and update resource files.").option("-w, --watch","Watch for file changes and re-run the extractor.").option("--ci","Exit with a non-zero status code if any files are updated.").action(async o=>{const a=await n(),c=async()=>{const t=await r(a);o.ci&&t&&(console.error(i.red.bold("\n[CI Mode] Error: Translation files were updated. Please commit the changes.")),console.log(i.yellow("💡 Tip: Tired of committing JSON files? locize syncs your team automatically => https://www.locize.com/docs/getting-started")),console.log(` Learn more: ${i.cyan("npx i18next-cli locize-sync")}`),process.exit(1))};if(await c(),o.watch){console.log("\nWatching for changes...");t.watch(await e(a.extract.input),{ignored:/node_modules/,persistent:!0}).on("change",o=>{console.log(`\nFile changed: ${o}`),c()})}}),w.command("status [locale]").description("Display translation status. Provide a locale for a detailed key-by-key view.").option("-n, --namespace <ns>","Filter the status report by a specific namespace").action(async(o,t)=>{let e=await a();if(!e){console.log(i.blue("No config file found. Attempting to detect project structure..."));const o=await c();o||(console.error(i.red("Could not automatically detect your project structure.")),console.log(`Please create a config file first by running: ${i.cyan("npx i18next-cli init")}`),process.exit(1)),console.log(i.green("Project structure detected successfully!")),e=o}await f(e,{detail:o,namespace:t.namespace})}),w.command("types").description("Generate TypeScript definitions from translation resource files.").option("-w, --watch","Watch for file changes and re-run the type generator.").action(async o=>{const i=await n(),a=()=>s(i);if(await a(),o.watch){console.log("\nWatching for changes...");t.watch(await e(i.types?.input||[]),{persistent:!0}).on("change",o=>{console.log(`\nFile changed: ${o}`),a()})}}),w.command("sync").description("Synchronize secondary language files with the primary language file.").action(async()=>{const o=await n();await l(o)}),w.command("migrate-config").description("Migrate a legacy i18next-parser.config.js to the new format.").action(async()=>{await m()}),w.command("init").description("Create a new i18next.config.ts/js file with an interactive setup wizard.").action(p),w.command("lint").description("Find potential issues like hardcoded strings in your codebase.").action(async()=>{let o=await a();if(!o){console.log(i.blue("No config file found. Attempting to detect project structure..."));const t=await c();t||(console.error(i.red("Could not automatically detect your project structure.")),console.log(`Please create a config file first by running: ${i.cyan("npx i1e-toolkit init")}`),process.exit(1)),console.log(i.green("Project structure detected successfully!")),o=t}await d(o)}),w.command("locize-sync").description("Synchronize local translations with your locize project.").option("--update-values","Update values of existing translations on locize.").option("--src-lng-only","Check for changes in source language only.").option("--compare-mtime","Compare modification times when syncing.").option("--dry-run","Run the command without making any changes.").action(async o=>{const t=await n();await g(t,o)}),w.command("locize-download").description("Download all translations from your locize project.").action(async o=>{const t=await n();await u(t,o)}),w.command("locize-migrate").description("Migrate local translation files to a new locize project.").action(async o=>{const t=await n();await y(t,o)}),w.parse(process.argv);
package/dist/esm/init.js CHANGED
@@ -1 +1 @@
1
- import e from"inquirer";import{writeFile as t,readFile as n}from"node:fs/promises";import{resolve as i}from"node:path";async function o(){console.log("Welcome to the i18next-cli setup wizard!");const o=await e.prompt([{type:"list",name:"fileType",message:"What kind of configuration file do you want?",choices:["TypeScript (i18next.config.ts)","JavaScript (i18next.config.js)"]},{type:"input",name:"locales",message:"What locales does your project support? (comma-separated)",default:"en,de,fr",filter:e=>e.split(",").map(e=>e.trim())},{type:"input",name:"input",message:"What is the glob pattern for your source files?",default:"src/**/*.{js,jsx,ts,tsx}"},{type:"input",name:"output",message:"What is the path for your output resource files?",default:"public/locales/{{language}}/{{namespace}}.json"}]),r=o.fileType.includes("TypeScript"),s=await async function(){try{const e=i(process.cwd(),"package.json"),t=await n(e,"utf-8");return"module"===JSON.parse(t).type}catch{return!0}}(),a=r?"i18next.config.ts":"i18next.config.js",p={locales:o.locales,extract:{input:o.input,output:o.output}};function f(e,t=2,n=0){const i=e=>" ".repeat(e*t),o=i(n),r=i(n+1);if(null===e||"number"==typeof e||"boolean"==typeof e)return JSON.stringify(e);if("string"==typeof e)return JSON.stringify(e);if(Array.isArray(e)){if(0===e.length)return"[]";return`[\n${e.map(e=>`${r}${f(e,t,n+1)}`).join(",\n")}\n${o}]`}if("object"==typeof e){const i=Object.keys(e);if(0===i.length)return"{}";return`{\n${i.map(i=>{const o=/^[A-Za-z_$][A-Za-z0-9_$]*$/.test(i)?i:JSON.stringify(i);return`${r}${o}: ${f(e[i],t,n+1)}`}).join(",\n")}\n${o}}`}return JSON.stringify(e)}let c="";c=r?`import { defineConfig } from 'i18next-cli';\n\nexport default defineConfig(${f(p)});`:s?`import { defineConfig } from 'i18next-cli';\n\n/** @type {import('i18next-cli').I18nextToolkitConfig} */\nexport default defineConfig(${f(p)});`:`const { defineConfig } = require('i18next-cli');\n\n/** @type {import('i18next-cli').I18nextToolkitConfig} */\nmodule.exports = defineConfig(${f(p)});`;const u=i(process.cwd(),a);await t(u,c.trim()),console.log(`✅ Configuration file created at: ${u}`)}export{o as runInit};
1
+ import t from"inquirer";import{writeFile as e,readFile as n}from"node:fs/promises";import{resolve as o}from"node:path";import{detectConfig as i}from"./heuristic-config.js";async function r(){console.log("Welcome to the i18next-cli setup wizard!"),console.log("Scanning your project for a recommended configuration...");const r=await i();r?console.log("✅ Found a potential project structure. Using it for suggestions."):console.log("Could not detect a project structure. Using standard defaults."),"string"==typeof r?.extract?.input&&(r.extract.input=[r?.extract?.input]);const s=await t.prompt([{type:"list",name:"fileType",message:"What kind of configuration file do you want?",choices:["TypeScript (i18next.config.ts)","JavaScript (i18next.config.js)"]},{type:"input",name:"locales",message:"What locales does your project support? (comma-separated)",default:r?.locales?.join(",")||"en,de,fr",filter:t=>t.split(",").map(t=>t.trim())},{type:"input",name:"input",message:"What is the glob pattern for your source files?",default:r?.extract?.input?(r.extract.input||[])[0]:"src/**/*.{js,jsx,ts,tsx}"},{type:"input",name:"output",message:"What is the path for your output resource files?",default:r?.extract?.output||"public/locales/{{language}}/{{namespace}}.json"}]),a=s.fileType.includes("TypeScript"),c=await async function(){try{const t=o(process.cwd(),"package.json"),e=await n(t,"utf-8");return"module"===JSON.parse(e).type}catch{return!0}}(),p=a?"i18next.config.ts":"i18next.config.js",u={locales:s.locales,extract:{input:s.input,output:s.output}};function f(t,e=2,n=0){const o=t=>" ".repeat(t*e),i=o(n),r=o(n+1);if(null===t||"number"==typeof t||"boolean"==typeof t)return JSON.stringify(t);if("string"==typeof t)return JSON.stringify(t);if(Array.isArray(t)){if(0===t.length)return"[]";return`[\n${t.map(t=>`${r}${f(t,e,n+1)}`).join(",\n")}\n${i}]`}if("object"==typeof t){const o=Object.keys(t);if(0===o.length)return"{}";return`{\n${o.map(o=>{const i=/^[A-Za-z_$][A-Za-z0-9_$]*$/.test(o)?o:JSON.stringify(o);return`${r}${i}: ${f(t[o],e,n+1)}`}).join(",\n")}\n${i}}`}return JSON.stringify(t)}let l="";l=a?`import { defineConfig } from 'i18next-cli';\n\nexport default defineConfig(${f(u)});`:c?`import { defineConfig } from 'i18next-cli';\n\n/** @type {import('i18next-cli').I18nextToolkitConfig} */\nexport default defineConfig(${f(u)});`:`const { defineConfig } = require('i18next-cli');\n\n/** @type {import('i18next-cli').I18nextToolkitConfig} */\nmodule.exports = defineConfig(${f(u)});`;const g=o(process.cwd(),p);await e(g,l.trim()),console.log(`✅ Configuration file created at: ${g}`)}export{r as runInit};
@@ -1 +1 @@
1
- import{mergeResourcesAsInterface as e}from"i18next-resources-for-ts";import{glob as t}from"glob";import s from"ora";import o from"chalk";import{readFile as r,mkdir as i,writeFile as n,access as p}from"node:fs/promises";import{join as c,dirname as a,basename as u,extname as l,resolve as y,relative as f}from"node:path";async function d(d){const m=s("Generating TypeScript types for translations...\n").start();try{if(d.types||(d.types={input:["locales/en/*.json"],output:"src/types/i18next.d.ts"}),void 0===d.types.input&&(d.types.input=["locales/en/*.json"]),d.types.output||(d.types.output="src/types/i18next.d.ts"),d.types.resourcesFile||(d.types.resourcesFile=c(a(d.types?.output),"resources.d.ts")),!d.types?.input||d.types?.input.length<0)return void console.log("No input defined!");const s=await t(d.types?.input||[],{cwd:process.cwd()}),w=[];for(const e of s){const t=u(e,l(e)),s=await r(e,"utf-8"),o=JSON.parse(s);w.push({name:t,resources:o})}const g=[],h=d.types?.enableSelector||!1,S=e(w,{optimize:!!h}),$=y(process.cwd(),d.types?.output||""),x=y(process.cwd(),d.types.resourcesFile);let T;await i(a(x),{recursive:!0}),await n(x,S),g.push(` ${o.green("✓")} Resources interface written to ${d.types.resourcesFile}`);try{await p($),T=!0}catch(e){T=!1}if(!T){const e=`// This file is automatically generated by i18next-cli. Do not edit manually.\nimport Resources from './${f(a($),x).replace(/\\/g,"/").replace(/\.d\.ts$/,"")}';\n\ndeclare module 'i18next' {\n interface CustomTypeOptions {\n enableSelector: ${"string"==typeof h?`"${h}"`:h};\n defaultNS: '${d.extract.defaultNS||"translation"}';\n resources: Resources;\n }\n}`;await i(a($),{recursive:!0}),await n($,e),g.push(` ${o.green("✓")} TypeScript definitions written to ${d.types.output||""}`),m.succeed(o.bold("TypeScript definitions generated successfully.")),g.forEach(e=>console.log(e))}}catch(e){m.fail(o.red("Failed to generate TypeScript definitions.")),console.error(e)}}export{d as runTypesGenerator};
1
+ import{mergeResourcesAsInterface as e}from"i18next-resources-for-ts";import{glob as t}from"glob";import s from"ora";import r from"chalk";import{readFile as o,mkdir as i,writeFile as n,access as p}from"node:fs/promises";import{join as a,dirname as c,basename as u,extname as l,resolve as y,relative as f}from"node:path";import{getOutputPath as d}from"./utils/file-utils.js";async function m(m){const g=s("Generating TypeScript types for translations...\n").start();try{m.extract.primaryLanguage||=m.locales[0]||"en";let s=m.extract.output||`locales/${m.extract.primaryLanguage}/*.json`;if(s=d(s,m.extract.primaryLanguage||"en","*"),m.types||(m.types={input:s,output:"src/@types/i18next.d.ts"}),void 0===m.types.input&&(m.types.input=s),m.types.output||(m.types.output="src/@types/i18next.d.ts"),m.types.resourcesFile||(m.types.resourcesFile=a(c(m.types?.output),"resources.d.ts")),!m.types?.input||m.types?.input.length<0)return void console.log("No input defined!");const w=await t(m.types?.input||[],{cwd:process.cwd()}),x=[];for(const e of w){const t=u(e,l(e)),s=await o(e,"utf-8"),r=JSON.parse(s);x.push({name:t,resources:r})}const h=[],$=m.types?.enableSelector||!1,S=e(x,{optimize:!!$}),T=y(process.cwd(),m.types?.output||""),b=y(process.cwd(),m.types.resourcesFile);let F;await i(c(b),{recursive:!0}),await n(b,S),h.push(` ${r.green("✓")} Resources interface written to ${m.types.resourcesFile}`);try{await p(T),F=!0}catch(e){F=!1}if(!F){const e=`// This file is automatically generated by i18next-cli. Do not edit manually.\nimport Resources from './${f(c(T),b).replace(/\\/g,"/").replace(/\.d\.ts$/,"")}';\n\ndeclare module 'i18next' {\n interface CustomTypeOptions {\n enableSelector: ${"string"==typeof $?`"${$}"`:$};\n defaultNS: '${m.extract.defaultNS||"translation"}';\n resources: Resources;\n }\n}`;await i(c(T),{recursive:!0}),await n(T,e),h.push(` ${r.green("✓")} TypeScript definitions written to ${m.types.output||""}`)}g.succeed(r.bold("TypeScript definitions generated successfully.")),h.forEach(e=>console.log(e))}catch(e){g.fail(r.red("Failed to generate TypeScript definitions.")),console.error(e)}}export{m as runTypesGenerator};
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "i18next-cli",
3
- "version": "0.9.11",
3
+ "version": "0.9.12",
4
4
  "description": "A unified, high-performance i18next CLI.",
5
5
  "type": "module",
6
6
  "bin": {
package/src/cli.ts CHANGED
@@ -21,7 +21,7 @@ const program = new Command()
21
21
  program
22
22
  .name('i18next-cli')
23
23
  .description('A unified, high-performance i18next CLI.')
24
- .version('0.9.11')
24
+ .version('0.9.12')
25
25
 
26
26
  program
27
27
  .command('extract')
package/src/init.ts CHANGED
@@ -1,6 +1,7 @@
1
1
  import inquirer from 'inquirer'
2
2
  import { writeFile, readFile } from 'node:fs/promises'
3
3
  import { resolve } from 'node:path'
4
+ import { detectConfig } from './heuristic-config'
4
5
 
5
6
  /**
6
7
  * Determines if the current project is configured as an ESM project.
@@ -58,6 +59,15 @@ async function isEsmProject (): Promise<boolean> {
58
59
  */
59
60
  export async function runInit () {
60
61
  console.log('Welcome to the i18next-cli setup wizard!')
62
+ console.log('Scanning your project for a recommended configuration...')
63
+
64
+ const detectedConfig = await detectConfig()
65
+ if (detectedConfig) {
66
+ console.log('✅ Found a potential project structure. Using it for suggestions.')
67
+ } else {
68
+ console.log('Could not detect a project structure. Using standard defaults.')
69
+ }
70
+ if (typeof detectedConfig?.extract?.input === 'string') detectedConfig.extract.input = [detectedConfig?.extract?.input]
61
71
 
62
72
  const answers = await inquirer.prompt([
63
73
  {
@@ -70,20 +80,20 @@ export async function runInit () {
70
80
  type: 'input',
71
81
  name: 'locales',
72
82
  message: 'What locales does your project support? (comma-separated)',
73
- default: 'en,de,fr',
83
+ default: detectedConfig?.locales?.join(',') || 'en,de,fr',
74
84
  filter: (input: string) => input.split(',').map(s => s.trim()),
75
85
  },
76
86
  {
77
87
  type: 'input',
78
88
  name: 'input',
79
89
  message: 'What is the glob pattern for your source files?',
80
- default: 'src/**/*.{js,jsx,ts,tsx}',
90
+ default: detectedConfig?.extract?.input ? (detectedConfig.extract.input || [])[0] : 'src/**/*.{js,jsx,ts,tsx}',
81
91
  },
82
92
  {
83
93
  type: 'input',
84
94
  name: 'output',
85
95
  message: 'What is the path for your output resource files?',
86
- default: 'public/locales/{{language}}/{{namespace}}.json',
96
+ default: detectedConfig?.extract?.output || 'public/locales/{{language}}/{{namespace}}.json',
87
97
  },
88
98
  ])
89
99
 
@@ -5,6 +5,7 @@ import chalk from 'chalk'
5
5
  import { mkdir, readFile, writeFile, access } from 'node:fs/promises'
6
6
  import { basename, extname, resolve, dirname, join, relative } from 'node:path'
7
7
  import type { I18nextToolkitConfig } from './types'
8
+ import { getOutputPath } from './utils/file-utils'
8
9
 
9
10
  /**
10
11
  * Represents a translation resource with its namespace name and content
@@ -46,9 +47,13 @@ export async function runTypesGenerator (config: I18nextToolkitConfig) {
46
47
  const spinner = ora('Generating TypeScript types for translations...\n').start()
47
48
 
48
49
  try {
49
- if (!config.types) config.types = { input: ['locales/en/*.json'], output: 'src/types/i18next.d.ts' }
50
- if (config.types.input === undefined) config.types.input = ['locales/en/*.json']
51
- if (!config.types.output) config.types.output = 'src/types/i18next.d.ts'
50
+ config.extract.primaryLanguage ||= config.locales[0] || 'en'
51
+ let defaultTypesInputPath = config.extract.output || `locales/${config.extract.primaryLanguage}/*.json`
52
+ defaultTypesInputPath = getOutputPath(defaultTypesInputPath, config.extract.primaryLanguage || 'en', '*')
53
+
54
+ if (!config.types) config.types = { input: defaultTypesInputPath, output: 'src/@types/i18next.d.ts' }
55
+ if (config.types.input === undefined) config.types.input = defaultTypesInputPath
56
+ if (!config.types.output) config.types.output = 'src/@types/i18next.d.ts'
52
57
  if (!config.types.resourcesFile) config.types.resourcesFile = join(dirname(config.types?.output), 'resources.d.ts')
53
58
 
54
59
  if (!config.types?.input || config.types?.input.length < 0) {
@@ -105,10 +110,9 @@ declare module 'i18next' {
105
110
  await mkdir(dirname(outputPath), { recursive: true })
106
111
  await writeFile(outputPath, fileContent)
107
112
  logMessages.push(` ${chalk.green('✓')} TypeScript definitions written to ${config.types.output || ''}`)
108
-
109
- spinner.succeed(chalk.bold('TypeScript definitions generated successfully.'))
110
- logMessages.forEach(msg => console.log(msg))
111
113
  }
114
+ spinner.succeed(chalk.bold('TypeScript definitions generated successfully.'))
115
+ logMessages.forEach(msg => console.log(msg))
112
116
  } catch (error) {
113
117
  spinner.fail(chalk.red('Failed to generate TypeScript definitions.'))
114
118
  console.error(error)
@@ -1 +1 @@
1
- {"version":3,"file":"init.d.ts","sourceRoot":"","sources":["../src/init.ts"],"names":[],"mappings":"AA+BA;;;;;;;;;;;;;;;;;;;;;;;;;;GA0BG;AACH,wBAAsB,OAAO,kBAiG5B"}
1
+ {"version":3,"file":"init.d.ts","sourceRoot":"","sources":["../src/init.ts"],"names":[],"mappings":"AAgCA;;;;;;;;;;;;;;;;;;;;;;;;;;GA0BG;AACH,wBAAsB,OAAO,kBA0G5B"}
@@ -1 +1 @@
1
- {"version":3,"file":"types-generator.d.ts","sourceRoot":"","sources":["../src/types-generator.ts"],"names":[],"mappings":"AAMA,OAAO,KAAK,EAAE,oBAAoB,EAAE,MAAM,SAAS,CAAA;AAYnD;;;;;;;;;;;;;;;;;;;;;;;;;GAyBG;AACH,wBAAsB,iBAAiB,CAAE,MAAM,EAAE,oBAAoB,iBAuEpE"}
1
+ {"version":3,"file":"types-generator.d.ts","sourceRoot":"","sources":["../src/types-generator.ts"],"names":[],"mappings":"AAMA,OAAO,KAAK,EAAE,oBAAoB,EAAE,MAAM,SAAS,CAAA;AAanD;;;;;;;;;;;;;;;;;;;;;;;;;GAyBG;AACH,wBAAsB,iBAAiB,CAAE,MAAM,EAAE,oBAAoB,iBA0EpE"}