i18next-cli 0.9.12 → 0.9.13

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,10 @@ 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.13](https://github.com/i18next/i18next-cli/compare/v0.9.12...v0.9.13) - 2025-09-27
13
+
14
+ - **Linter & Extractor:** Fixed a parser crash that occurred when analyzing TypeScript files containing decorator syntax. Both the `lint` and `extract` commands will now correctly process files that use decorators. [#6](https://github.com/i18next/i18next-cli/issues/6)
15
+
12
16
  ## [0.9.12](https://github.com/i18next/i18next-cli/compare/v0.9.11...v0.9.12) - 2025-09-27
13
17
 
14
18
  ### 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.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);
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.13"),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"),r=require("@swc/core"),a=require("node:fs/promises"),n=require("node:path"),o=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"),f=require("../../utils/file-utils.js");function g(e,t,r,a=new u.ConsoleLogger){if(e&&"object"==typeof e){for(const n of t)try{n.onVisitNode?.(e,r)}catch(e){a.warn(`Plugin ${n.name} onVisitNode failed:`,e)}for(const n of Object.keys(e)){const o=e[n];if(Array.isArray(o))for(const e of o)e&&"object"==typeof e&&g(e,t,r,a);else o&&"object"==typeof o&&g(o,t,r,a)}}}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:r}=await o.findKeys(e);return s.getTranslations(t,r,e)},exports.processFile=async function(e,t,n,o,s=new u.ConsoleLogger){try{let i=await a.readFile(e,"utf-8");for(const r of t.plugins||[])i=await(r.onLoad?.(i,e))??i;const u=await r.parse(i,{syntax:"typescript",tsx:!0,comments:!0}),f=c.createPluginContext(n);l.extractKeysFromComments(i,t.extract.functions||["t"],f,t),o.visit(u),(t.plugins||[]).length>0&&g(u,t.plugins||[],f,s)}catch(t){throw new i.ExtractorError("Failed to process file",e,t)}},exports.runExtractor=async function(r,c=new u.ConsoleLogger){r.extract.primaryLanguage||=r.locales[0]||"en",r.extract.secondaryLanguages||=r.locales.filter(e=>e!==r?.extract?.primaryLanguage),i.validateExtractorConfig(r);const l=e("Running i18next key extractor...\n").start();try{const{allKeys:e,objectKeys:i}=await o.findKeys(r,c);l.text=`Found ${e.size} unique keys. Updating translation files...`;const u=await s.getTranslations(e,i,r);let g=!1;for(const e of u)if(e.updated){g=!0;const o=f.serializeTranslationFile(e.newTranslations,r.extract.outputFormat,r.extract.indentation);await a.mkdir(n.dirname(e.path),{recursive:!0}),await a.writeFile(e.path,o),c.info(t.green(`Updated: ${e.path}`))}return l.succeed(t.bold("Extraction complete!")),g}catch(e){throw l.fail(t.red("Extraction failed.")),e}};
1
+ "use strict";var e=require("ora"),t=require("chalk"),r=require("@swc/core"),a=require("node:fs/promises"),n=require("node:path"),o=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"),f=require("../../utils/file-utils.js");function g(e,t,r,a=new u.ConsoleLogger){if(e&&"object"==typeof e){for(const n of t)try{n.onVisitNode?.(e,r)}catch(e){a.warn(`Plugin ${n.name} onVisitNode failed:`,e)}for(const n of Object.keys(e)){const o=e[n];if(Array.isArray(o))for(const e of o)e&&"object"==typeof e&&g(e,t,r,a);else o&&"object"==typeof o&&g(o,t,r,a)}}}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:r}=await o.findKeys(e);return s.getTranslations(t,r,e)},exports.processFile=async function(e,t,n,o,s=new u.ConsoleLogger){try{let i=await a.readFile(e,"utf-8");for(const r of t.plugins||[])i=await(r.onLoad?.(i,e))??i;const u=await r.parse(i,{syntax:"typescript",tsx:!0,decorators:!0,comments:!0}),f=c.createPluginContext(n);l.extractKeysFromComments(i,t.extract.functions||["t"],f,t),o.visit(u),(t.plugins||[]).length>0&&g(u,t.plugins||[],f,s)}catch(t){throw new i.ExtractorError("Failed to process file",e,t)}},exports.runExtractor=async function(r,c=new u.ConsoleLogger){r.extract.primaryLanguage||=r.locales[0]||"en",r.extract.secondaryLanguages||=r.locales.filter(e=>e!==r?.extract?.primaryLanguage),i.validateExtractorConfig(r);const l=e("Running i18next key extractor...\n").start();try{const{allKeys:e,objectKeys:i}=await o.findKeys(r,c);l.text=`Found ${e.size} unique keys. Updating translation files...`;const u=await s.getTranslations(e,i,r);let g=!1;for(const e of u)if(e.updated){g=!0;const o=f.serializeTranslationFile(e.newTranslations,r.extract.outputFormat,r.extract.indentation);await a.mkdir(n.dirname(e.path),{recursive:!0}),await a.writeFile(e.path,o),c.info(t.green(`Updated: ${e.path}`))}return l.succeed(t.bold("Extraction complete!")),g}catch(e){throw l.fail(t.red("Extraction failed.")),e}};
@@ -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}),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"),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)}};
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.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);
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.13"),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 a}from"@swc/core";import{mkdir as e,writeFile as r,readFile as n}from"node:fs/promises";import{dirname as i}from"node:path";import{findKeys as s}from"./key-finder.js";import{getTranslations as c}from"./translation-manager.js";import{validateExtractorConfig as f,ExtractorError as l}from"../../utils/validation.js";import{createPluginContext as p}from"../plugin-manager.js";import{extractKeysFromComments as m}from"../parsers/comment-parser.js";import{ConsoleLogger as u}from"../../utils/logger.js";import{serializeTranslationFile as y}from"../../utils/file-utils.js";async function g(a,n=new u){a.extract.primaryLanguage||=a.locales[0]||"en",a.extract.secondaryLanguages||=a.locales.filter(t=>t!==a?.extract?.primaryLanguage),f(a);const l=t("Running i18next key extractor...\n").start();try{const{allKeys:t,objectKeys:f}=await s(a,n);l.text=`Found ${t.size} unique keys. Updating translation files...`;const p=await c(t,f,a);let m=!1;for(const t of p)if(t.updated){m=!0;const s=y(t.newTranslations,a.extract.outputFormat,a.extract.indentation);await e(i(t.path),{recursive:!0}),await r(t.path,s),n.info(o.green(`Updated: ${t.path}`))}return l.succeed(o.bold("Extraction complete!")),m}catch(t){throw l.fail(o.red("Extraction failed.")),t}}async function d(t,o,e,r,i=new u){try{let s=await n(t,"utf-8");for(const a of o.plugins||[])s=await(a.onLoad?.(s,t))??s;const c=await a(s,{syntax:"typescript",tsx:!0,comments:!0}),f=p(e);m(s,o.extract.functions||["t"],f,o),r.visit(c),(o.plugins||[]).length>0&&x(c,o.plugins||[],f,i)}catch(o){throw new l("Failed to process file",t,o)}}function x(t,o,a,e=new u){if(t&&"object"==typeof t){for(const r of o)try{r.onVisitNode?.(t,a)}catch(t){e.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&&x(t,o,a,e);else n&&"object"==typeof n&&x(n,o,a,e)}}}async function w(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:a}=await s(t);return c(o,a,t)}export{w as extract,d as processFile,g as runExtractor};
1
+ import t from"ora";import o from"chalk";import{parse as a}from"@swc/core";import{mkdir as e,writeFile as r,readFile as n}from"node:fs/promises";import{dirname as i}from"node:path";import{findKeys as s}from"./key-finder.js";import{getTranslations as c}from"./translation-manager.js";import{validateExtractorConfig as f,ExtractorError as l}from"../../utils/validation.js";import{createPluginContext as p}from"../plugin-manager.js";import{extractKeysFromComments as m}from"../parsers/comment-parser.js";import{ConsoleLogger as u}from"../../utils/logger.js";import{serializeTranslationFile as y}from"../../utils/file-utils.js";async function g(a,n=new u){a.extract.primaryLanguage||=a.locales[0]||"en",a.extract.secondaryLanguages||=a.locales.filter(t=>t!==a?.extract?.primaryLanguage),f(a);const l=t("Running i18next key extractor...\n").start();try{const{allKeys:t,objectKeys:f}=await s(a,n);l.text=`Found ${t.size} unique keys. Updating translation files...`;const p=await c(t,f,a);let m=!1;for(const t of p)if(t.updated){m=!0;const s=y(t.newTranslations,a.extract.outputFormat,a.extract.indentation);await e(i(t.path),{recursive:!0}),await r(t.path,s),n.info(o.green(`Updated: ${t.path}`))}return l.succeed(o.bold("Extraction complete!")),m}catch(t){throw l.fail(o.red("Extraction failed.")),t}}async function d(t,o,e,r,i=new u){try{let s=await n(t,"utf-8");for(const a of o.plugins||[])s=await(a.onLoad?.(s,t))??s;const c=await a(s,{syntax:"typescript",tsx:!0,decorators:!0,comments:!0}),f=p(e);m(s,o.extract.functions||["t"],f,o),r.visit(c),(o.plugins||[]).length>0&&x(c,o.plugins||[],f,i)}catch(o){throw new l("Failed to process file",t,o)}}function x(t,o,a,e=new u){if(t&&"object"==typeof t){for(const r of o)try{r.onVisitNode?.(t,a)}catch(t){e.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&&x(t,o,a,e);else n&&"object"==typeof n&&x(n,o,a,e)}}}async function w(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:a}=await s(t);return c(o,a,t)}export{w as extract,d as processFile,g as runExtractor};
@@ -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}),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 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};
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "i18next-cli",
3
- "version": "0.9.12",
3
+ "version": "0.9.13",
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.12')
24
+ .version('0.9.13')
25
25
 
26
26
  program
27
27
  .command('extract')
@@ -118,6 +118,7 @@ export async function processFile (
118
118
  const ast = await parse(code, {
119
119
  syntax: 'typescript',
120
120
  tsx: true,
121
+ decorators: true,
121
122
  comments: true
122
123
  })
123
124
 
package/src/linter.ts CHANGED
@@ -45,7 +45,11 @@ export async function runLinter (config: I18nextToolkitConfig) {
45
45
 
46
46
  for (const file of sourceFiles) {
47
47
  const code = await readFile(file, 'utf-8')
48
- const ast = await parse(code, { syntax: 'typescript', tsx: true })
48
+ const ast = await parse(code, {
49
+ syntax: 'typescript',
50
+ tsx: true,
51
+ decorators: true
52
+ })
49
53
  const hardcodedStrings = findHardcodedStrings(ast, code, config)
50
54
 
51
55
  if (hardcodedStrings.length > 0) {
@@ -1 +1 @@
1
- {"version":3,"file":"extractor.d.ts","sourceRoot":"","sources":["../../../src/extractor/core/extractor.ts"],"names":[],"mappings":"AAKA,OAAO,KAAK,EAAE,MAAM,EAAE,YAAY,EAAiB,oBAAoB,EAAE,MAAM,aAAa,CAAA;AAM5F,OAAO,EAAE,WAAW,EAAE,MAAM,yBAAyB,CAAA;AAIrD;;;;;;;;;;;;;;;;;;;;;;;;;GAyBG;AACH,wBAAsB,YAAY,CAChC,MAAM,EAAE,oBAAoB,EAC5B,MAAM,GAAE,MAA4B,GACnC,OAAO,CAAC,OAAO,CAAC,CAoClB;AAED;;;;;;;;;;;;;;;;;;;GAmBG;AACH,wBAAsB,WAAW,CAC/B,IAAI,EAAE,MAAM,EACZ,MAAM,EAAE,oBAAoB,EAC5B,OAAO,EAAE,GAAG,CAAC,MAAM,EAAE,YAAY,CAAC,EAClC,WAAW,EAAE,WAAW,EACxB,MAAM,GAAE,MAA4B,GACnC,OAAO,CAAC,IAAI,CAAC,CA6Bf;AAmCD;;;;;;;;;;;;;;GAcG;AACH,wBAAsB,OAAO,CAAE,MAAM,EAAE,oBAAoB,sDAO1D"}
1
+ {"version":3,"file":"extractor.d.ts","sourceRoot":"","sources":["../../../src/extractor/core/extractor.ts"],"names":[],"mappings":"AAKA,OAAO,KAAK,EAAE,MAAM,EAAE,YAAY,EAAiB,oBAAoB,EAAE,MAAM,aAAa,CAAA;AAM5F,OAAO,EAAE,WAAW,EAAE,MAAM,yBAAyB,CAAA;AAIrD;;;;;;;;;;;;;;;;;;;;;;;;;GAyBG;AACH,wBAAsB,YAAY,CAChC,MAAM,EAAE,oBAAoB,EAC5B,MAAM,GAAE,MAA4B,GACnC,OAAO,CAAC,OAAO,CAAC,CAoClB;AAED;;;;;;;;;;;;;;;;;;;GAmBG;AACH,wBAAsB,WAAW,CAC/B,IAAI,EAAE,MAAM,EACZ,MAAM,EAAE,oBAAoB,EAC5B,OAAO,EAAE,GAAG,CAAC,MAAM,EAAE,YAAY,CAAC,EAClC,WAAW,EAAE,WAAW,EACxB,MAAM,GAAE,MAA4B,GACnC,OAAO,CAAC,IAAI,CAAC,CA8Bf;AAmCD;;;;;;;;;;;;;;GAcG;AACH,wBAAsB,OAAO,CAAE,MAAM,EAAE,oBAAoB,sDAO1D"}
@@ -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,iBAsC5D"}
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"}