i18next-cli 1.0.0 → 1.0.2

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,14 @@ All notable changes to this project will be documented in this file.
5
5
  The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/),
6
6
  and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
7
7
 
8
+ ## [1.0.2](https://github.com/i18next/i18next-cli/compare/v1.0.1...v1.0.2) - 2025-10-01
9
+
10
+ - **Extractor & Linter:** Added a new `extract.ignore` option to provide a simpler and more reliable way to exclude files from processing. This option accepts an array of glob patterns and is respected by both the `extract` and `lint` commands, avoiding the need for complex negative glob patterns.
11
+
12
+ ## [1.0.1](https://github.com/i18next/i18next-cli/compare/v1.0.0...v1.0.1) - 2025-10-01
13
+
14
+ - **Extractor:** Fixed a bug where the comment parser was too aggressive, causing it to incorrectly extract keys from non-translation functions (like `test()` or `http.get()`) found inside comments. The parser is now more specific and safely targets only valid, commented-out `t()` calls. [#13](https://github.com/i18next/i18next-cli/issues/13)
15
+
8
16
  ## [1.0.0](https://github.com/i18next/i18next-cli/compare/v0.9.20...v1.0.0) - 2025-10-01
9
17
 
10
18
  šŸŽ‰ **Official v1.0.0 Release!**
package/README.md CHANGED
@@ -287,6 +287,9 @@ export default defineConfig({
287
287
  input: ['src/**/*.{ts,tsx}'],
288
288
  output: 'locales/{{language}}/{{namespace}}.json',
289
289
 
290
+ /** Glob pattern(s) for files to ignore during extraction */
291
+ ignore: ['node_modules/**'],
292
+
290
293
  // Use '.ts' files with `export default` instead of '.json'
291
294
  outputFormat: 'ts',
292
295
 
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("1.0.0"),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("1.0.2"),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);
@@ -1 +1 @@
1
- "use strict";var e=require("ora"),t=require("chalk"),a=require("@swc/core"),r=require("node:fs/promises"),o=require("node:path"),n=require("./key-finder.js"),s=require("./translation-manager.js"),i=require("../../utils/validation.js"),c=require("../plugin-manager.js"),l=require("../parsers/comment-parser.js"),u=require("../../utils/logger.js"),g=require("../../utils/file-utils.js");function f(e,t,a,r=new u.ConsoleLogger){if(e&&"object"==typeof e){for(const o of t)try{o.onVisitNode?.(e,a)}catch(e){r.warn(`Plugin ${o.name} onVisitNode failed:`,e)}for(const o of Object.keys(e)){const n=e[o];if(Array.isArray(n))for(const e of n)e&&"object"==typeof e&&f(e,t,a,r);else n&&"object"==typeof n&&f(n,t,a,r)}}}exports.extract=async function(e){e.extract.primaryLanguage||=e.locales[0]||"en",e.extract.secondaryLanguages||=e.locales.filter(t=>t!==e?.extract?.primaryLanguage),e.extract.functions||=["t"],e.extract.transComponents||=["Trans"];const{allKeys:t,objectKeys:a}=await n.findKeys(e);return s.getTranslations(t,a,e)},exports.processFile=async function(e,t,o,n,s=new u.ConsoleLogger){try{let i=await r.readFile(e,"utf-8");for(const a of t.plugins||[])i=await(a.onLoad?.(i,e))??i;const u=await a.parse(i,{syntax:"typescript",tsx:!0,decorators:!0,comments:!0}),g=c.createPluginContext(o);l.extractKeysFromComments(i,t.extract.functions||["t"],g,t),n.visit(u),(t.plugins||[]).length>0&&f(u,t.plugins||[],g,s)}catch(t){throw new i.ExtractorError("Failed to process file",e,t)}},exports.runExtractor=async function(a,c=new u.ConsoleLogger){a.extract.primaryLanguage||=a.locales[0]||"en",a.extract.secondaryLanguages||=a.locales.filter(e=>e!==a?.extract?.primaryLanguage),i.validateExtractorConfig(a);const l=e("Running i18next key extractor...\n").start();try{const{allKeys:e,objectKeys:i}=await n.findKeys(a,c);l.text=`Found ${e.size} unique keys. Updating translation files...`;const u=await s.getTranslations(e,i,a);let f=!1;for(const e of u)if(e.updated){f=!0;const n=g.serializeTranslationFile(e.newTranslations,a.extract.outputFormat,a.extract.indentation);await r.mkdir(o.dirname(e.path),{recursive:!0}),await r.writeFile(e.path,n),c.info(t.green(`Updated: ${e.path}`))}return l.succeed(t.bold("Extraction complete!")),f&&(console.log(t.yellow.bold("\nšŸ’” Tip: Tired of running the extractor manually?")),console.log(' Discover a real-time "push" workflow with `saveMissing` and Locize AI,'),console.log(" where keys are created and translated automatically as you code."),console.log(` Learn more: ${t.cyan("https://www.locize.com/blog/i18next-savemissing-ai-automation")}`),console.log(` Watch the video: ${t.cyan("https://youtu.be/joPsZghT3wM")}`)),f}catch(e){throw l.fail(t.red("Extraction failed.")),e}};
1
+ "use strict";var e=require("ora"),t=require("chalk"),a=require("@swc/core"),r=require("node:fs/promises"),o=require("node:path"),n=require("./key-finder.js"),s=require("./translation-manager.js"),i=require("../../utils/validation.js"),c=require("../plugin-manager.js"),l=require("../parsers/comment-parser.js"),u=require("../../utils/logger.js"),g=require("../../utils/file-utils.js");function f(e,t,a,r=new u.ConsoleLogger){if(e&&"object"==typeof e){for(const o of t)try{o.onVisitNode?.(e,a)}catch(e){r.warn(`Plugin ${o.name} onVisitNode failed:`,e)}for(const o of Object.keys(e)){const n=e[o];if(Array.isArray(n))for(const e of n)e&&"object"==typeof e&&f(e,t,a,r);else n&&"object"==typeof n&&f(n,t,a,r)}}}exports.extract=async function(e){e.extract.primaryLanguage||=e.locales[0]||"en",e.extract.secondaryLanguages||=e.locales.filter(t=>t!==e?.extract?.primaryLanguage),e.extract.functions||=["t"],e.extract.transComponents||=["Trans"];const{allKeys:t,objectKeys:a}=await n.findKeys(e);return s.getTranslations(t,a,e)},exports.processFile=async function(e,t,o,n,s=new u.ConsoleLogger){try{let i=await r.readFile(e,"utf-8");for(const a of t.plugins||[])i=await(a.onLoad?.(i,e))??i;const u=await a.parse(i,{syntax:"typescript",tsx:!0,decorators:!0,comments:!0}),g=c.createPluginContext(o);l.extractKeysFromComments(i,g,t),n.visit(u),(t.plugins||[]).length>0&&f(u,t.plugins||[],g,s)}catch(t){throw new i.ExtractorError("Failed to process file",e,t)}},exports.runExtractor=async function(a,c=new u.ConsoleLogger){a.extract.primaryLanguage||=a.locales[0]||"en",a.extract.secondaryLanguages||=a.locales.filter(e=>e!==a?.extract?.primaryLanguage),i.validateExtractorConfig(a);const l=e("Running i18next key extractor...\n").start();try{const{allKeys:e,objectKeys:i}=await n.findKeys(a,c);l.text=`Found ${e.size} unique keys. Updating translation files...`;const u=await s.getTranslations(e,i,a);let f=!1;for(const e of u)if(e.updated){f=!0;const n=g.serializeTranslationFile(e.newTranslations,a.extract.outputFormat,a.extract.indentation);await r.mkdir(o.dirname(e.path),{recursive:!0}),await r.writeFile(e.path,n),c.info(t.green(`Updated: ${e.path}`))}return l.succeed(t.bold("Extraction complete!")),f&&(console.log(t.yellow.bold("\nšŸ’” Tip: Tired of running the extractor manually?")),console.log(' Discover a real-time "push" workflow with `saveMissing` and Locize AI,'),console.log(" where keys are created and translated automatically as you code."),console.log(` Learn more: ${t.cyan("https://www.locize.com/blog/i18next-savemissing-ai-automation")}`),console.log(` Watch the video: ${t.cyan("https://youtu.be/joPsZghT3wM")}`)),f}catch(e){throw l.fail(t.red("Extraction failed.")),e}};
@@ -1 +1 @@
1
- "use strict";var e=require("glob"),r=require("./extractor.js"),s=require("../../utils/logger.js"),i=require("../plugin-manager.js"),t=require("../parsers/ast-visitors.js");exports.findKeys=async function(n,o=new s.ConsoleLogger){const a=await async function(r){return await e.glob(r.extract.input,{ignore:"node_modules/**",cwd:process.cwd()})}(n),u=new Map,c=new t.ASTVisitors(n,i.createPluginContext(u),o);await i.initializePlugins(n.plugins||[]);for(const e of a)await r.processFile(e,n,u,c,o);for(const e of n.plugins||[])await(e.onEnd?.(u));return{allKeys:u,objectKeys:c.objectKeys}};
1
+ "use strict";var e=require("glob"),r=require("./extractor.js"),t=require("../../utils/logger.js"),i=require("../plugin-manager.js"),s=require("../parsers/ast-visitors.js");exports.findKeys=async function(n,o=new t.ConsoleLogger){const a=await async function(r){const t=["node_modules/**"],i=Array.isArray(r.extract.ignore)?r.extract.ignore:r.extract.ignore?[r.extract.ignore]:[];return await e.glob(r.extract.input,{ignore:[...t,...i],cwd:process.cwd()})}(n),c=new Map,u=new s.ASTVisitors(n,i.createPluginContext(c),o);await i.initializePlugins(n.plugins||[]);for(const e of a)await r.processFile(e,n,c,u,o);for(const e of n.plugins||[])await(e.onEnd?.(c));return{allKeys:c,objectKeys:u.objectKeys}};
@@ -1 +1 @@
1
- "use strict";function e(e){const t=/^\s*,\s*(['"])(.*?)\1/.exec(e);if(t)return t[2];const s=/^\s*,\s*\{[^}]*defaultValue\s*:\s*(['"])(.*?)\1/.exec(e);return s?s[2]:void 0}function t(e){const t=/^\s*,\s*\{[^}]*ns\s*:\s*(['"])(.*?)\1/.exec(e);if(t)return t[2]}exports.extractKeysFromComments=function(s,n,c,o){const r=n.map(e=>e.replace(/[.+?^${}()|[\]\\]/g,"\\$&")).join("|"),u=new RegExp(`(?:${r})\\s*\\(\\s*(['"])([^'"]+)\\1`,"g"),i=function(e){const t=[],s=new Set,n=/\/\/(.*)|\/\*([\s\S]*?)\*\//g;let c;for(;null!==(c=n.exec(e));){const e=(c[1]??c[2]).trim();e&&!s.has(e)&&(s.add(e),t.push(e))}return t}(s);for(const s of i){let n;for(;null!==(n=u.exec(s));){let r,u=n[2];const i=s.slice(n.index+n[0].length),l=e(i);r=t(i);const a=o.extract.nsSeparator??":";if(!r&&a&&u.includes(a)){const e=u.split(a);r=e.shift(),u=e.join(a)}r||(r=o.extract.defaultNS),c.addKey({key:u,ns:r,defaultValue:l??u})}}};
1
+ "use strict";function t(t){const e=/^\s*,\s*(['"])(.*?)\1/.exec(t);if(e)return e[2];const s=/^\s*,\s*\{[^}]*defaultValue\s*:\s*(['"])(.*?)\1/.exec(t);return s?s[2]:void 0}function e(t){const e=/^\s*,\s*\{[^}]*ns\s*:\s*(['"])(.*?)\1/.exec(t);if(e)return e[2]}exports.extractKeysFromComments=function(s,n,c){const o=new RegExp("\\bt\\s*\\(\\s*(['\"])([^'\"]+)\\1","g"),r=function(t){const e=[],s=new Set,n=/\/\/(.*)|\/\*([\s\S]*?)\*\//g;let c;for(;null!==(c=n.exec(t));){const t=(c[1]??c[2]).trim();t&&!s.has(t)&&(s.add(t),e.push(t))}return e}(s);for(const s of r){let r;for(;null!==(r=o.exec(s));){let o,u=r[2];const i=s.slice(r.index+r[0].length),l=t(i);o=e(i);const f=c.extract.nsSeparator??":";if(!o&&f&&u.includes(f)){const t=u.split(f);o=t.shift(),u=t.join(f)}o||(o=c.extract.defaultNS),n.addKey({key:u,ns:o,defaultValue:l??u})}}};
@@ -1 +1 @@
1
- "use strict";var e=require("glob"),t=require("node:fs/promises"),r=require("@swc/core"),s=require("chalk"),n=require("ora");const o=e=>/^(https|http|\/\/|^\/)/.test(e);function i(e,t,r){const s=[],n=[],i=e=>t.substring(0,e).split("\n").length,a=r.extract.transComponents||["Trans"],c=r.extract.ignoredTags||[],l=new Set([...a,"script","style","code",...c]),u=r.extract.ignoredAttributes||[],f=new Set(["className","key","id","style","href","i18nKey","defaults","type","target",...u]),p=(e,t)=>{if(!e||"object"!=typeof e)return;const r=[...t,e];if("JSXText"===e.type){if(!r.some(e=>{if("JSXElement"!==e.type)return!1;const t=e.opening?.name?.value;return l.has(t)})){const t=e.value.trim();t&&t.length>1&&!o(t)&&isNaN(Number(t))&&!t.startsWith("{{")&&n.push(e)}}if("StringLiteral"===e.type){const t=r[r.length-2];if("JSXAttribute"===t?.type&&!f.has(t.name.value)){const t=e.value.trim();t&&!o(t)&&isNaN(Number(t))&&n.push(e)}}for(const t of Object.keys(e)){if("span"===t)continue;const s=e[t];Array.isArray(s)?s.forEach(e=>p(e,r)):s&&"object"==typeof s&&p(s,r)}};p(e,[]);let d=0;for(const e of n){const r=e.raw??e.value,n=t.indexOf(r,d);n>-1&&(s.push({text:e.value.trim(),line:i(n)}),d=n+r.length)}return s}exports.runLinter=async function(o){const a=n("Analyzing source files...\n").start();try{const n=await e.glob(o.extract.input);let c=0;const l=new Map;for(const e of n){const s=await t.readFile(e,"utf-8"),n=i(await r.parse(s,{syntax:"typescript",tsx:!0,decorators:!0}),s,o);n.length>0&&(c+=n.length,l.set(e,n))}if(c>0){a.fail(s.red.bold(`Linter found ${c} potential issues.`));for(const[e,t]of l.entries())console.log(s.yellow(`\n${e}`)),t.forEach(({text:e,line:t})=>{console.log(` ${s.gray(`${t}:`)} ${s.red("Error:")} Found hardcoded string: "${e}"`)});process.exit(1)}else a.succeed(s.green.bold("No issues found."))}catch(e){a.fail(s.red("Linter failed to run.")),console.error(e),process.exit(1)}};
1
+ "use strict";var e=require("glob"),t=require("node:fs/promises"),r=require("@swc/core"),n=require("chalk"),s=require("ora");const o=e=>/^(https|http|\/\/|^\/)/.test(e);function i(e,t,r){const n=[],s=[],i=e=>t.substring(0,e).split("\n").length,a=r.extract.transComponents||["Trans"],c=r.extract.ignoredTags||[],l=new Set([...a,"script","style","code",...c]),u=r.extract.ignoredAttributes||[],f=new Set(["className","key","id","style","href","i18nKey","defaults","type","target",...u]),p=(e,t)=>{if(!e||"object"!=typeof e)return;const r=[...t,e];if("JSXText"===e.type){if(!r.some(e=>{if("JSXElement"!==e.type)return!1;const t=e.opening?.name?.value;return l.has(t)})){const t=e.value.trim();t&&t.length>1&&!o(t)&&isNaN(Number(t))&&!t.startsWith("{{")&&s.push(e)}}if("StringLiteral"===e.type){const t=r[r.length-2];if("JSXAttribute"===t?.type&&!f.has(t.name.value)){const t=e.value.trim();t&&!o(t)&&isNaN(Number(t))&&s.push(e)}}for(const t of Object.keys(e)){if("span"===t)continue;const n=e[t];Array.isArray(n)?n.forEach(e=>p(e,r)):n&&"object"==typeof n&&p(n,r)}};p(e,[]);let g=0;for(const e of s){const r=e.raw??e.value,s=t.indexOf(r,g);s>-1&&(n.push({text:e.value.trim(),line:i(s)}),g=s+r.length)}return n}exports.runLinter=async function(o){const a=s("Analyzing source files...\n").start();try{const s=["node_modules/**"],c=Array.isArray(o.extract.ignore)?o.extract.ignore:o.extract.ignore?[o.extract.ignore]:[],l=await e.glob(o.extract.input,{ignore:[...s,...c]});let u=0;const f=new Map;for(const e of l){const n=await t.readFile(e,"utf-8"),s=i(await r.parse(n,{syntax:"typescript",tsx:!0,decorators:!0}),n,o);s.length>0&&(u+=s.length,f.set(e,s))}if(u>0){a.fail(n.red.bold(`Linter found ${u} potential issues.`));for(const[e,t]of f.entries())console.log(n.yellow(`\n${e}`)),t.forEach(({text:e,line:t})=>{console.log(` ${n.gray(`${t}:`)} ${n.red("Error:")} Found hardcoded string: "${e}"`)});process.exit(1)}else a.succeed(n.green.bold("No issues found."))}catch(e){a.fail(n.red("Linter failed to run.")),console.error(e),process.exit(1)}};
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("1.0.0"),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("1.0.2"),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);
@@ -1 +1 @@
1
- import t from"ora";import o from"chalk";import{parse as e}from"@swc/core";import{mkdir as a,writeFile as r,readFile as n}from"node:fs/promises";import{dirname as s}from"node:path";import{findKeys as i}from"./key-finder.js";import{getTranslations as c}from"./translation-manager.js";import{validateExtractorConfig as l,ExtractorError as f}from"../../utils/validation.js";import{createPluginContext as m}from"../plugin-manager.js";import{extractKeysFromComments as p}from"../parsers/comment-parser.js";import{ConsoleLogger as u}from"../../utils/logger.js";import{serializeTranslationFile as g}from"../../utils/file-utils.js";async function y(e,n=new u){e.extract.primaryLanguage||=e.locales[0]||"en",e.extract.secondaryLanguages||=e.locales.filter(t=>t!==e?.extract?.primaryLanguage),l(e);const f=t("Running i18next key extractor...\n").start();try{const{allKeys:t,objectKeys:l}=await i(e,n);f.text=`Found ${t.size} unique keys. Updating translation files...`;const m=await c(t,l,e);let p=!1;for(const t of m)if(t.updated){p=!0;const i=g(t.newTranslations,e.extract.outputFormat,e.extract.indentation);await a(s(t.path),{recursive:!0}),await r(t.path,i),n.info(o.green(`Updated: ${t.path}`))}return f.succeed(o.bold("Extraction complete!")),p&&(console.log(o.yellow.bold("\nšŸ’” Tip: Tired of running the extractor manually?")),console.log(' Discover a real-time "push" workflow with `saveMissing` and Locize AI,'),console.log(" where keys are created and translated automatically as you code."),console.log(` Learn more: ${o.cyan("https://www.locize.com/blog/i18next-savemissing-ai-automation")}`),console.log(` Watch the video: ${o.cyan("https://youtu.be/joPsZghT3wM")}`)),p}catch(t){throw f.fail(o.red("Extraction failed.")),t}}async function d(t,o,a,r,s=new u){try{let i=await n(t,"utf-8");for(const e of o.plugins||[])i=await(e.onLoad?.(i,t))??i;const c=await e(i,{syntax:"typescript",tsx:!0,decorators:!0,comments:!0}),l=m(a);p(i,o.extract.functions||["t"],l,o),r.visit(c),(o.plugins||[]).length>0&&w(c,o.plugins||[],l,s)}catch(o){throw new f("Failed to process file",t,o)}}function w(t,o,e,a=new u){if(t&&"object"==typeof t){for(const r of o)try{r.onVisitNode?.(t,e)}catch(t){a.warn(`Plugin ${r.name} onVisitNode failed:`,t)}for(const r of Object.keys(t)){const n=t[r];if(Array.isArray(n))for(const t of n)t&&"object"==typeof t&&w(t,o,e,a);else n&&"object"==typeof n&&w(n,o,e,a)}}}async function x(t){t.extract.primaryLanguage||=t.locales[0]||"en",t.extract.secondaryLanguages||=t.locales.filter(o=>o!==t?.extract?.primaryLanguage),t.extract.functions||=["t"],t.extract.transComponents||=["Trans"];const{allKeys:o,objectKeys:e}=await i(t);return c(o,e,t)}export{x as extract,d as processFile,y as runExtractor};
1
+ import t from"ora";import o from"chalk";import{parse as e}from"@swc/core";import{mkdir as a,writeFile as r,readFile as n}from"node:fs/promises";import{dirname as s}from"node:path";import{findKeys as i}from"./key-finder.js";import{getTranslations as c}from"./translation-manager.js";import{validateExtractorConfig as l,ExtractorError as f}from"../../utils/validation.js";import{createPluginContext as m}from"../plugin-manager.js";import{extractKeysFromComments as p}from"../parsers/comment-parser.js";import{ConsoleLogger as u}from"../../utils/logger.js";import{serializeTranslationFile as g}from"../../utils/file-utils.js";async function y(e,n=new u){e.extract.primaryLanguage||=e.locales[0]||"en",e.extract.secondaryLanguages||=e.locales.filter(t=>t!==e?.extract?.primaryLanguage),l(e);const f=t("Running i18next key extractor...\n").start();try{const{allKeys:t,objectKeys:l}=await i(e,n);f.text=`Found ${t.size} unique keys. Updating translation files...`;const m=await c(t,l,e);let p=!1;for(const t of m)if(t.updated){p=!0;const i=g(t.newTranslations,e.extract.outputFormat,e.extract.indentation);await a(s(t.path),{recursive:!0}),await r(t.path,i),n.info(o.green(`Updated: ${t.path}`))}return f.succeed(o.bold("Extraction complete!")),p&&(console.log(o.yellow.bold("\nšŸ’” Tip: Tired of running the extractor manually?")),console.log(' Discover a real-time "push" workflow with `saveMissing` and Locize AI,'),console.log(" where keys are created and translated automatically as you code."),console.log(` Learn more: ${o.cyan("https://www.locize.com/blog/i18next-savemissing-ai-automation")}`),console.log(` Watch the video: ${o.cyan("https://youtu.be/joPsZghT3wM")}`)),p}catch(t){throw f.fail(o.red("Extraction failed.")),t}}async function d(t,o,a,r,s=new u){try{let i=await n(t,"utf-8");for(const e of o.plugins||[])i=await(e.onLoad?.(i,t))??i;const c=await e(i,{syntax:"typescript",tsx:!0,decorators:!0,comments:!0}),l=m(a);p(i,l,o),r.visit(c),(o.plugins||[]).length>0&&w(c,o.plugins||[],l,s)}catch(o){throw new f("Failed to process file",t,o)}}function w(t,o,e,a=new u){if(t&&"object"==typeof t){for(const r of o)try{r.onVisitNode?.(t,e)}catch(t){a.warn(`Plugin ${r.name} onVisitNode failed:`,t)}for(const r of Object.keys(t)){const n=t[r];if(Array.isArray(n))for(const t of n)t&&"object"==typeof t&&w(t,o,e,a);else n&&"object"==typeof n&&w(n,o,e,a)}}}async function h(t){t.extract.primaryLanguage||=t.locales[0]||"en",t.extract.secondaryLanguages||=t.locales.filter(o=>o!==t?.extract?.primaryLanguage),t.extract.functions||=["t"],t.extract.transComponents||=["Trans"];const{allKeys:o,objectKeys:e}=await i(t);return c(o,e,t)}export{h as extract,d as processFile,y as runExtractor};
@@ -1 +1 @@
1
- import{glob as o}from"glob";import{processFile as t}from"./extractor.js";import{ConsoleLogger as r}from"../../utils/logger.js";import{initializePlugins as n,createPluginContext as s}from"../plugin-manager.js";import{ASTVisitors as a}from"../parsers/ast-visitors.js";async function e(e,i=new r){const c=await async function(t){return await o(t.extract.input,{ignore:"node_modules/**",cwd:process.cwd()})}(e),p=new Map,m=new a(e,s(p),i);await n(e.plugins||[]);for(const o of c)await t(o,e,p,m,i);for(const o of e.plugins||[])await(o.onEnd?.(p));return{allKeys:p,objectKeys:m.objectKeys}}export{e as findKeys};
1
+ import{glob as r}from"glob";import{processFile as t}from"./extractor.js";import{ConsoleLogger as o}from"../../utils/logger.js";import{initializePlugins as e,createPluginContext as n}from"../plugin-manager.js";import{ASTVisitors as a}from"../parsers/ast-visitors.js";async function i(i,s=new o){const c=await async function(t){const o=["node_modules/**"],e=Array.isArray(t.extract.ignore)?t.extract.ignore:t.extract.ignore?[t.extract.ignore]:[];return await r(t.extract.input,{ignore:[...o,...e],cwd:process.cwd()})}(i),p=new Map,g=new a(i,n(p),s);await e(i.plugins||[]);for(const r of c)await t(r,i,p,g,s);for(const r of i.plugins||[])await(r.onEnd?.(p));return{allKeys:p,objectKeys:g.objectKeys}}export{i as findKeys};
@@ -1 +1 @@
1
- function e(e,s,c,o){const r=s.map(e=>e.replace(/[.+?^${}()|[\]\\]/g,"\\$&")).join("|"),l=new RegExp(`(?:${r})\\s*\\(\\s*(['"])([^'"]+)\\1`,"g"),u=function(e){const t=[],n=new Set,s=/\/\/(.*)|\/\*([\s\S]*?)\*\//g;let c;for(;null!==(c=s.exec(e));){const e=(c[1]??c[2]).trim();e&&!n.has(e)&&(n.add(e),t.push(e))}return t}(e);for(const e of u){let s;for(;null!==(s=l.exec(e));){let r,l=s[2];const u=e.slice(s.index+s[0].length),i=t(u);r=n(u);const f=o.extract.nsSeparator??":";if(!r&&f&&l.includes(f)){const e=l.split(f);r=e.shift(),l=e.join(f)}r||(r=o.extract.defaultNS),c.addKey({key:l,ns:r,defaultValue:i??l})}}}function t(e){const t=/^\s*,\s*(['"])(.*?)\1/.exec(e);if(t)return t[2];const n=/^\s*,\s*\{[^}]*defaultValue\s*:\s*(['"])(.*?)\1/.exec(e);return n?n[2]:void 0}function n(e){const t=/^\s*,\s*\{[^}]*ns\s*:\s*(['"])(.*?)\1/.exec(e);if(t)return t[2]}export{e as extractKeysFromComments};
1
+ function e(e,s,c){const o=new RegExp("\\bt\\s*\\(\\s*(['\"])([^'\"]+)\\1","g"),r=function(e){const t=[],n=new Set,s=/\/\/(.*)|\/\*([\s\S]*?)\*\//g;let c;for(;null!==(c=s.exec(e));){const e=(c[1]??c[2]).trim();e&&!n.has(e)&&(n.add(e),t.push(e))}return t}(e);for(const e of r){let r;for(;null!==(r=o.exec(e));){let o,u=r[2];const l=e.slice(r.index+r[0].length),f=t(l);o=n(l);const i=c.extract.nsSeparator??":";if(!o&&i&&u.includes(i)){const e=u.split(i);o=e.shift(),u=e.join(i)}o||(o=c.extract.defaultNS),s.addKey({key:u,ns:o,defaultValue:f??u})}}}function t(e){const t=/^\s*,\s*(['"])(.*?)\1/.exec(e);if(t)return t[2];const n=/^\s*,\s*\{[^}]*defaultValue\s*:\s*(['"])(.*?)\1/.exec(e);return n?n[2]:void 0}function n(e){const t=/^\s*,\s*\{[^}]*ns\s*:\s*(['"])(.*?)\1/.exec(e);if(t)return t[2]}export{e as extractKeysFromComments};
@@ -1 +1 @@
1
- import{glob as t}from"glob";import{readFile as e}from"node:fs/promises";import{parse as o}from"@swc/core";import r from"chalk";import n from"ora";async function s(s){const i=n("Analyzing source files...\n").start();try{const n=await t(s.extract.input);let c=0;const l=new Map;for(const t of n){const r=await e(t,"utf-8"),n=a(await o(r,{syntax:"typescript",tsx:!0,decorators:!0}),r,s);n.length>0&&(c+=n.length,l.set(t,n))}if(c>0){i.fail(r.red.bold(`Linter found ${c} potential issues.`));for(const[t,e]of l.entries())console.log(r.yellow(`\n${t}`)),e.forEach(({text:t,line:e})=>{console.log(` ${r.gray(`${e}:`)} ${r.red("Error:")} Found hardcoded string: "${t}"`)});process.exit(1)}else i.succeed(r.green.bold("No issues found."))}catch(t){i.fail(r.red("Linter failed to run.")),console.error(t),process.exit(1)}}const i=t=>/^(https|http|\/\/|^\/)/.test(t);function a(t,e,o){const r=[],n=[],s=t=>e.substring(0,t).split("\n").length,a=o.extract.transComponents||["Trans"],c=o.extract.ignoredTags||[],l=new Set([...a,"script","style","code",...c]),f=o.extract.ignoredAttributes||[],u=new Set(["className","key","id","style","href","i18nKey","defaults","type","target",...f]),p=(t,e)=>{if(!t||"object"!=typeof t)return;const o=[...e,t];if("JSXText"===t.type){if(!o.some(t=>{if("JSXElement"!==t.type)return!1;const e=t.opening?.name?.value;return l.has(e)})){const e=t.value.trim();e&&e.length>1&&!i(e)&&isNaN(Number(e))&&!e.startsWith("{{")&&n.push(t)}}if("StringLiteral"===t.type){const e=o[o.length-2];if("JSXAttribute"===e?.type&&!u.has(e.name.value)){const e=t.value.trim();e&&!i(e)&&isNaN(Number(e))&&n.push(t)}}for(const e of Object.keys(t)){if("span"===e)continue;const r=t[e];Array.isArray(r)?r.forEach(t=>p(t,o)):r&&"object"==typeof r&&p(r,o)}};p(t,[]);let m=0;for(const t of n){const o=t.raw??t.value,n=e.indexOf(o,m);n>-1&&(r.push({text:t.value.trim(),line:s(n)}),m=n+o.length)}return r}export{s as runLinter};
1
+ import{glob as t}from"glob";import{readFile as e}from"node:fs/promises";import{parse as r}from"@swc/core";import o from"chalk";import n from"ora";async function s(s){const i=n("Analyzing source files...\n").start();try{const n=["node_modules/**"],c=Array.isArray(s.extract.ignore)?s.extract.ignore:s.extract.ignore?[s.extract.ignore]:[],l=await t(s.extract.input,{ignore:[...n,...c]});let f=0;const u=new Map;for(const t of l){const o=await e(t,"utf-8"),n=a(await r(o,{syntax:"typescript",tsx:!0,decorators:!0}),o,s);n.length>0&&(f+=n.length,u.set(t,n))}if(f>0){i.fail(o.red.bold(`Linter found ${f} potential issues.`));for(const[t,e]of u.entries())console.log(o.yellow(`\n${t}`)),e.forEach(({text:t,line:e})=>{console.log(` ${o.gray(`${e}:`)} ${o.red("Error:")} Found hardcoded string: "${t}"`)});process.exit(1)}else i.succeed(o.green.bold("No issues found."))}catch(t){i.fail(o.red("Linter failed to run.")),console.error(t),process.exit(1)}}const i=t=>/^(https|http|\/\/|^\/)/.test(t);function a(t,e,r){const o=[],n=[],s=t=>e.substring(0,t).split("\n").length,a=r.extract.transComponents||["Trans"],c=r.extract.ignoredTags||[],l=new Set([...a,"script","style","code",...c]),f=r.extract.ignoredAttributes||[],u=new Set(["className","key","id","style","href","i18nKey","defaults","type","target",...f]),p=(t,e)=>{if(!t||"object"!=typeof t)return;const r=[...e,t];if("JSXText"===t.type){if(!r.some(t=>{if("JSXElement"!==t.type)return!1;const e=t.opening?.name?.value;return l.has(e)})){const e=t.value.trim();e&&e.length>1&&!i(e)&&isNaN(Number(e))&&!e.startsWith("{{")&&n.push(t)}}if("StringLiteral"===t.type){const e=r[r.length-2];if("JSXAttribute"===e?.type&&!u.has(e.name.value)){const e=t.value.trim();e&&!i(e)&&isNaN(Number(e))&&n.push(t)}}for(const e of Object.keys(t)){if("span"===e)continue;const o=t[e];Array.isArray(o)?o.forEach(t=>p(t,r)):o&&"object"==typeof o&&p(o,r)}};p(t,[]);let g=0;for(const t of n){const r=t.raw??t.value,n=e.indexOf(r,g);n>-1&&(o.push({text:t.value.trim(),line:s(n)}),g=n+r.length)}return o}export{s as runLinter};
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "i18next-cli",
3
- "version": "1.0.0",
3
+ "version": "1.0.2",
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('1.0.0')
24
+ .version('1.0.2')
25
25
 
26
26
  program
27
27
  .command('extract')
@@ -131,7 +131,7 @@ export async function processFile (
131
131
  const pluginContext = createPluginContext(allKeys)
132
132
 
133
133
  // Extract keys from comments
134
- extractKeysFromComments(code, config.extract.functions || ['t'], pluginContext, config)
134
+ extractKeysFromComments(code, pluginContext, config)
135
135
 
136
136
  astVisitors.visit(ast)
137
137
 
@@ -67,8 +67,16 @@ export async function findKeys (
67
67
  * @internal
68
68
  */
69
69
  async function processSourceFiles (config: I18nextToolkitConfig): Promise<string[]> {
70
+ const defaultIgnore = ['node_modules/**']
71
+
72
+ // Normalize the user's ignore option into an array
73
+ const userIgnore = Array.isArray(config.extract.ignore)
74
+ ? config.extract.ignore
75
+ : config.extract.ignore ? [config.extract.ignore] : []
76
+
70
77
  return await glob(config.extract.input, {
71
- ignore: 'node_modules/**',
78
+ // Combine default ignore patterns with user-configured ones
79
+ ignore: [...defaultIgnore, ...userIgnore],
72
80
  cwd: process.cwd(),
73
81
  })
74
82
  }
@@ -23,14 +23,14 @@ import type { PluginContext, I18nextToolkitConfig } from '../../types'
23
23
  */
24
24
  export function extractKeysFromComments (
25
25
  code: string,
26
- functionNames: string[],
27
26
  pluginContext: PluginContext,
28
27
  config: I18nextToolkitConfig
29
28
  ): void {
30
- const functionPattern = functionNames
31
- .map(n => n.replace(/[.+?^${}()|[\]\\]/g, '\\$&'))
32
- .join('|')
33
- const keyRegex = new RegExp(`(?:${functionPattern})\\s*\\(\\s*(['"])([^'"]+)\\1`, 'g')
29
+ // Hardcode the function name to 't' to prevent parsing other functions like 'test()'.
30
+ const functionNameToFind = 't'
31
+
32
+ // Use a reliable word boundary (\b) to match 't(...)' but not 'http.get(...)'.
33
+ const keyRegex = new RegExp(`\\b${functionNameToFind}\\s*\\(\\s*(['"])([^'"]+)\\1`, 'g')
34
34
 
35
35
  const commentTexts = collectCommentTexts(code)
36
36
 
package/src/linter.ts CHANGED
@@ -39,7 +39,14 @@ export async function runLinter (config: I18nextToolkitConfig) {
39
39
  const spinner = ora('Analyzing source files...\n').start()
40
40
 
41
41
  try {
42
- const sourceFiles = await glob(config.extract.input)
42
+ const defaultIgnore = ['node_modules/**']
43
+ const userIgnore = Array.isArray(config.extract.ignore)
44
+ ? config.extract.ignore
45
+ : config.extract.ignore ? [config.extract.ignore] : []
46
+
47
+ const sourceFiles = await glob(config.extract.input, {
48
+ ignore: [...defaultIgnore, ...userIgnore]
49
+ })
43
50
  let totalIssues = 0
44
51
  const issuesByFile = new Map<string, HardcodedString[]>()
45
52
 
package/src/types.ts CHANGED
@@ -30,6 +30,9 @@ export interface I18nextToolkitConfig {
30
30
  /** Glob pattern(s) for source files to scan for translation keys */
31
31
  input: string | string[];
32
32
 
33
+ /** Glob pattern(s) for files to ignore during extraction */
34
+ ignore?: string | string[];
35
+
33
36
  /** Output path template with placeholders: {{language}} for locale, {{namespace}} for namespace */
34
37
  output: string;
35
38
 
@@ -20,5 +20,5 @@ import type { PluginContext, I18nextToolkitConfig } from '../../types';
20
20
  * // Extracts: user.name and app.title with their respective settings
21
21
  * ```
22
22
  */
23
- export declare function extractKeysFromComments(code: string, functionNames: string[], pluginContext: PluginContext, config: I18nextToolkitConfig): void;
23
+ export declare function extractKeysFromComments(code: string, pluginContext: PluginContext, config: I18nextToolkitConfig): void;
24
24
  //# sourceMappingURL=comment-parser.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"comment-parser.d.ts","sourceRoot":"","sources":["../../../src/extractor/parsers/comment-parser.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,aAAa,EAAE,oBAAoB,EAAE,MAAM,aAAa,CAAA;AAEtE;;;;;;;;;;;;;;;;;;;;GAoBG;AACH,wBAAgB,uBAAuB,CACrC,IAAI,EAAE,MAAM,EACZ,aAAa,EAAE,MAAM,EAAE,EACvB,aAAa,EAAE,aAAa,EAC5B,MAAM,EAAE,oBAAoB,GAC3B,IAAI,CA+BN"}
1
+ {"version":3,"file":"comment-parser.d.ts","sourceRoot":"","sources":["../../../src/extractor/parsers/comment-parser.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,aAAa,EAAE,oBAAoB,EAAE,MAAM,aAAa,CAAA;AAEtE;;;;;;;;;;;;;;;;;;;;GAoBG;AACH,wBAAgB,uBAAuB,CACrC,IAAI,EAAE,MAAM,EACZ,aAAa,EAAE,aAAa,EAC5B,MAAM,EAAE,oBAAoB,GAC3B,IAAI,CAgCN"}
@@ -1 +1 @@
1
- {"version":3,"file":"linter.d.ts","sourceRoot":"","sources":["../src/linter.ts"],"names":[],"mappings":"AAKA,OAAO,KAAK,EAAE,oBAAoB,EAAE,MAAM,SAAS,CAAA;AAEnD;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GA6BG;AACH,wBAAsB,SAAS,CAAE,MAAM,EAAE,oBAAoB,iBA0C5D"}
1
+ {"version":3,"file":"linter.d.ts","sourceRoot":"","sources":["../src/linter.ts"],"names":[],"mappings":"AAKA,OAAO,KAAK,EAAE,oBAAoB,EAAE,MAAM,SAAS,CAAA;AAEnD;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GA6BG;AACH,wBAAsB,SAAS,CAAE,MAAM,EAAE,oBAAoB,iBAiD5D"}
package/types/types.d.ts CHANGED
@@ -27,6 +27,8 @@ export interface I18nextToolkitConfig {
27
27
  extract: {
28
28
  /** Glob pattern(s) for source files to scan for translation keys */
29
29
  input: string | string[];
30
+ /** Glob pattern(s) for files to ignore during extraction */
31
+ ignore?: string | string[];
30
32
  /** Output path template with placeholders: {{language}} for locale, {{namespace}} for namespace */
31
33
  output: string;
32
34
  /** Default namespace when none is specified (default: 'translation') */
@@ -1 +1 @@
1
- {"version":3,"file":"types.d.ts","sourceRoot":"","sources":["../src/types.ts"],"names":[],"mappings":"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"}
1
+ {"version":3,"file":"types.d.ts","sourceRoot":"","sources":["../src/types.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,IAAI,EAAE,UAAU,EAAE,gBAAgB,EAAE,MAAM,WAAW,CAAA;AAEnE;;;;;;;;;;;;;;;;;;;;GAoBG;AACH,MAAM,WAAW,oBAAoB;IACnC,iEAAiE;IACjE,OAAO,EAAE,MAAM,EAAE,CAAC;IAElB,2DAA2D;IAC3D,OAAO,EAAE;QACP,oEAAoE;QACpE,KAAK,EAAE,MAAM,GAAG,MAAM,EAAE,CAAC;QAEzB,4DAA4D;QAC5D,MAAM,CAAC,EAAE,MAAM,GAAG,MAAM,EAAE,CAAC;QAE3B,mGAAmG;QACnG,MAAM,EAAE,MAAM,CAAC;QAEf,wEAAwE;QACxE,SAAS,CAAC,EAAE,MAAM,CAAC;QAEnB,uEAAuE;QACvE,YAAY,CAAC,EAAE,MAAM,GAAG,KAAK,GAAG,IAAI,CAAC;QAErC,8EAA8E;QAC9E,WAAW,CAAC,EAAE,MAAM,GAAG,KAAK,GAAG,IAAI,CAAC;QAEpC,oDAAoD;QACpD,gBAAgB,CAAC,EAAE,MAAM,CAAC;QAE1B,mDAAmD;QACnD,eAAe,CAAC,EAAE,MAAM,CAAC;QAEzB,wEAAwE;QACxE,SAAS,CAAC,EAAE,MAAM,EAAE,CAAC;QAErB,4EAA4E;QAC5E,eAAe,CAAC,EAAE,MAAM,EAAE,CAAC;QAE3B;;;;;WAKG;QACH,mBAAmB,CAAC,EAAE,KAAK,CAAC,MAAM,GAAG;YACnC,IAAI,EAAE,MAAM,CAAC;YACb,KAAK,CAAC,EAAE,MAAM,CAAC;YACf,YAAY,CAAC,EAAE,MAAM,CAAC;SACvB,CAAC,CAAC;QAEH,kFAAkF;QAClF,iBAAiB,CAAC,EAAE,MAAM,EAAE,CAAC;QAE7B,kGAAkG;QAClG,WAAW,CAAC,EAAE,MAAM,EAAE,CAAC;QAEvB,8FAA8F;QAC9F,0BAA0B,CAAC,EAAE,MAAM,EAAE,CAAC;QAEtC,wFAAwF;QACxF,gBAAgB,CAAC,EAAE,MAAM,EAAE,CAAC;QAE5B,0EAA0E;QAC1E,IAAI,CAAC,EAAE,OAAO,CAAC;QAEf,yDAAyD;QACzD,WAAW,CAAC,EAAE,MAAM,CAAC;QAErB,2EAA2E;QAC3E,YAAY,CAAC,EAAE,MAAM,CAAC;QAEtB,4EAA4E;QAC5E,eAAe,CAAC,EAAE,MAAM,CAAC;QAEzB,0DAA0D;QAC1D,kBAAkB,CAAC,EAAE,MAAM,EAAE,CAAC;QAE9B;;;;;;;WAOG;QACH,YAAY,CAAC,EAAE,MAAM,GAAG,IAAI,GAAG,QAAQ,GAAG,QAAQ,GAAG,QAAQ,GAAG,IAAI,CAAC;QAErE;;;;;WAKG;QACH,eAAe,CAAC,EAAE,OAAO,CAAC;KAC3B,CAAC;IAEF,2DAA2D;IAC3D,KAAK,CAAC,EAAE;QACN,mEAAmE;QACnE,KAAK,EAAE,MAAM,GAAG,MAAM,EAAE,CAAC;QAEzB,0DAA0D;QAC1D,MAAM,EAAE,MAAM,CAAC;QAEf,8EAA8E;QAC9E,cAAc,CAAC,EAAE,OAAO,GAAG,UAAU,CAAC;QAEtC,qDAAqD;QACrD,aAAa,CAAC,EAAE,MAAM,CAAC;KACxB,CAAC;IAEF,+CAA+C;IAC/C,OAAO,CAAC,EAAE,MAAM,EAAE,CAAC;IAEnB,2CAA2C;IAC3C,MAAM,CAAC,EAAE;QACP,wBAAwB;QACxB,SAAS,CAAC,EAAE,MAAM,CAAC;QAEnB,gEAAgE;QAChE,MAAM,CAAC,EAAE,MAAM,CAAC;QAEhB,+CAA+C;QAC/C,OAAO,CAAC,EAAE,MAAM,CAAC;QAEjB,8DAA8D;QAC9D,YAAY,CAAC,EAAE,OAAO,CAAC;QAEvB,8CAA8C;QAC9C,kBAAkB,CAAC,EAAE,OAAO,CAAC;QAE7B,8CAA8C;QAC9C,uBAAuB,CAAC,EAAE,OAAO,CAAC;QAElC,0CAA0C;QAC1C,MAAM,CAAC,EAAE,OAAO,CAAC;KAClB,CAAC;CACH;AAED;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GA8BG;AACH,MAAM,WAAW,MAAM;IACrB,iCAAiC;IACjC,IAAI,EAAE,MAAM,CAAC;IAEb;;;OAGG;IACH,KAAK,CAAC,EAAE,MAAM,IAAI,GAAG,OAAO,CAAC,IAAI,CAAC,CAAC;IAEnC;;;;;;;OAOG;IACH,MAAM,CAAC,EAAE,CAAC,IAAI,EAAE,MAAM,EAAE,IAAI,EAAE,MAAM,KAAK,MAAM,GAAG,OAAO,CAAC,MAAM,CAAC,CAAC;IAElE;;;;;;OAMG;IACH,WAAW,CAAC,EAAE,CAAC,IAAI,EAAE,IAAI,EAAE,OAAO,EAAE,aAAa,KAAK,IAAI,CAAC;IAE3D;;;;;OAKG;IACH,KAAK,CAAC,EAAE,CAAC,IAAI,EAAE,GAAG,CAAC,MAAM,EAAE;QAAE,GAAG,EAAE,MAAM,CAAC;QAAC,YAAY,CAAC,EAAE,MAAM,CAAA;KAAE,CAAC,KAAK,IAAI,GAAG,OAAO,CAAC,IAAI,CAAC,CAAC;CAC7F;AAED;;;;;;;;;;;;;GAaG;AACH,MAAM,WAAW,YAAY;IAC3B,0DAA0D;IAC1D,GAAG,EAAE,MAAM,CAAC;IAEZ,mDAAmD;IACnD,YAAY,CAAC,EAAE,MAAM,CAAC;IAEtB,oCAAoC;IACpC,EAAE,CAAC,EAAE,MAAM,CAAC;IAEZ,oEAAoE;IACpE,QAAQ,CAAC,EAAE,OAAO,CAAC;IAEnB,0DAA0D;IAC1D,SAAS,CAAC,EAAE,OAAO,CAAC;IAEpB,8EAA8E;IAC9E,WAAW,CAAC,EAAE,gBAAgB,CAAC;IAE/B,mDAAmD;IACnD,iBAAiB,CAAC,EAAE,UAAU,CAAC;CAChC;AAED;;;;;;;;;;;;;GAaG;AACH,MAAM,WAAW,iBAAiB;IAChC,uEAAuE;IACvE,IAAI,EAAE,MAAM,CAAC;IAEb,+DAA+D;IAC/D,OAAO,EAAE,OAAO,CAAC;IAEjB,2DAA2D;IAC3D,eAAe,EAAE,MAAM,CAAC,MAAM,EAAE,GAAG,CAAC,CAAC;IAErC,kEAAkE;IAClE,oBAAoB,EAAE,MAAM,CAAC,MAAM,EAAE,GAAG,CAAC,CAAC;CAC3C;AAED;;;;;;;;;;;;GAYG;AACH,MAAM,WAAW,MAAM;IACrB;;;OAGG;IACH,IAAI,CAAC,OAAO,EAAE,MAAM,GAAG,IAAI,CAAC;IAE5B;;;OAGG;IACH,IAAI,CAAC,OAAO,EAAE,MAAM,EAAE,IAAI,CAAC,EAAE,GAAG,GAAG,IAAI,CAAC;IAExC;;;OAGG;IACH,KAAK,CAAC,OAAO,EAAE,MAAM,GAAG,GAAG,GAAG,IAAI,CAAC;CACpC;AAED;;;;;;;;;;;;;;;;;GAiBG;AACH,MAAM,WAAW,aAAa;IAC5B;;;;;OAKG;IACH,MAAM,EAAE,CAAC,OAAO,EAAE,YAAY,KAAK,IAAI,CAAC;CACzC"}