i18next-cli 1.20.4 → 1.21.1

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.21.1](https://github.com/i18next/i18next-cli/compare/v1.21.0...v1.21.1) - 2025-11-05
9
+
10
+ - **Extractor (`--sync-primary`):** Fixed a bug where `<Trans i18nKey='namespace:key' />` components with namespaced keys were having their existing primary language translations overwritten with the full namespaced key string (e.g., `'translation:app.preservedTrans'`) when using the `--sync-primary` flag. The extractor now correctly identifies namespaced keys in defaultValue as derived (auto-generated) defaults rather than explicit developer-provided values, preserving existing translations when no explicit defaultValue is provided. This ensures that `<Trans>` components without explicit defaults behave consistently with `t()` calls under `--sync-primary`. [#92](https://github.com/i18next/i18next-cli/issues/92)
11
+
12
+ ## [1.21.0](https://github.com/i18next/i18next-cli/compare/v1.20.4...v1.21.0) - 2025-11-05
13
+
14
+ - **Extractor:** Enhanced `preservePatterns` with namespace pattern support. You can now preserve entire namespaces or namespace-specific key patterns using the namespace separator (e.g., `'assets:*'` to preserve all keys in the `assets` namespace, or `'common:button.*'` to preserve specific patterns within a namespace). This is particularly useful for managing translations that are handled externally, loaded dynamically, or constructed at runtime. Pattern matching respects the configured `nsSeparator` setting. [#90](https://github.com/i18next/i18next-cli/issues/90)
15
+
8
16
  ## [1.20.4](https://github.com/i18next/i18next-cli/compare/v1.20.3...v1.20.4) - 2025-11-04
9
17
 
10
18
  - improve(extractor): tolerate JSX/TSX in `.ts` files by retrying parse with TSX enabled. [#75](https://github.com/i18next/i18next-cli/issues/75)
package/README.md CHANGED
@@ -359,8 +359,17 @@ export default defineConfig({
359
359
 
360
360
  // Preserve dynamic keys matching patterns
361
361
  preservePatterns: [
362
- 'dynamic.feature.*',
363
- 'generated.*.key'
362
+ // Key patterns
363
+ 'dynamic.feature.*', // Matches dynamic.feature.anything
364
+ 'generated.*.key', // Matches generated.anything.key
365
+
366
+ // Namespace patterns
367
+ 'assets:*', // Preserves ALL keys in the 'assets' namespace
368
+ 'common:button.*', // Preserves keys like common:button.save, common:button.cancel
369
+ 'errors:api.*', // Preserves keys like errors:api.timeout, errors:api.server
370
+
371
+ // Specific key preservation across namespaces
372
+ 'dynamic:user.*.profile', // Matches dynamic:user.admin.profile, dynamic:user.guest.profile
364
373
  ],
365
374
 
366
375
  // Output formatting
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("minimatch"),i=require("chalk"),a=require("./config.js"),r=require("./heuristic-config.js"),c=require("./extractor/core/extractor.js");require("node:path"),require("node:fs/promises"),require("jiti");var s=require("./types-generator.js"),l=require("./syncer.js"),u=require("./migrator.js"),g=require("./init.js"),d=require("./linter.js"),p=require("./status.js"),f=require("./locize.js");const m=new e.Command;m.name("i18next-cli").description("A unified, high-performance i18next CLI.").version("1.20.4"),m.option("-c, --config <path>","Path to i18next-cli config file (overrides detection)"),m.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.").option("--dry-run","Run the extractor without writing any files to disk.").option("--sync-primary","Sync primary language values with default values from code.").action(async e=>{try{const o=m.opts().config,i=await a.ensureConfig(o),r=async()=>{const t=await c.runExtractor(i,{isWatchMode:!!e.watch,isDryRun:!!e.dryRun,syncPrimaryWithDefaults:!!e.syncPrimary});return e.ci&&!t?(console.log("✅ No files were updated."),process.exit(0)):e.ci&&t&&(console.error("❌ Some files were updated. This should not happen in CI mode."),process.exit(1)),t};if(await r(),e.watch){console.log("\nWatching for changes...");const e=await w(i.extract.input),o=y(i.extract.ignore),a=h(i.extract.output),c=[...o,...a].filter(Boolean),s=e.filter(e=>!c.some(t=>n.minimatch(e,t,{dot:!0})));t.watch(s,{ignored:/node_modules/,persistent:!0}).on("change",e=>{console.log(`\nFile changed: ${e}`),r()})}}catch(e){console.error("Error running extractor:",e),process.exit(1)}}),m.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)=>{const o=m.opts().config;let n=await a.loadConfig(o);if(!n){console.log(i.blue("No config file found. Attempting to detect project structure..."));const e=await r.detectConfig();e||(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!")),n=e}await p.runStatus(n,{detail:e,namespace:t.namespace})}),m.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 o=m.opts().config,i=await a.ensureConfig(o),r=()=>s.runTypesGenerator(i);if(await r(),e.watch){console.log("\nWatching for changes...");const e=await w(i.types?.input||[]),o=[...y(i.extract?.ignore)].filter(Boolean),a=e.filter(e=>!o.some(t=>n.minimatch(e,t,{dot:!0})));t.watch(a,{persistent:!0}).on("change",e=>{console.log(`\nFile changed: ${e}`),r()})}}),m.command("sync").description("Synchronize secondary language files with the primary language file.").action(async()=>{const e=m.opts().config,t=await a.ensureConfig(e);await l.runSyncer(t)}),m.command("migrate-config [configPath]").description("Migrate a legacy i18next-parser.config.js to the new format.").action(async e=>{await u.runMigrator(e)}),m.command("init").description("Create a new i18next.config.ts/js file with an interactive setup wizard.").action(g.runInit),m.command("lint").description("Find potential issues like hardcoded strings in your codebase.").option("-w, --watch","Watch for file changes and re-run the linter.").action(async e=>{const o=m.opts().config,c=async()=>{let e=await a.loadConfig(o);if(!e){console.log(i.blue("No config file found. Attempting to detect project structure..."));const t=await r.detectConfig();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 i18next-cli init")}`),process.exit(1)),console.log(i.green("Project structure detected successfully!")),e=t}await d.runLinterCli(e)};if(await c(),e.watch){console.log("\nWatching for changes...");const e=await a.loadConfig(o);if(e?.extract?.input){const o=await w(e.extract.input),i=[...y(e.extract.ignore),...h(e.extract.output)].filter(Boolean),a=o.filter(e=>!i.some(t=>n.minimatch(e,t,{dot:!0})));t.watch(a,{ignored:/node_modules/,persistent:!0}).on("change",e=>{console.log(`\nFile changed: ${e}`),c()})}}}),m.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=m.opts().config,o=await a.ensureConfig(t);await f.runLocizeSync(o,e)}),m.command("locize-download").description("Download all translations from your locize project.").action(async e=>{const t=m.opts().config,o=await a.ensureConfig(t);await f.runLocizeDownload(o,e)}),m.command("locize-migrate").description("Migrate local translation files to a new locize project.").action(async e=>{const t=m.opts().config,o=await a.ensureConfig(t);await f.runLocizeMigrate(o,e)}),m.parse(process.argv);const y=e=>Array.isArray(e)?e:e?[e]:[],h=e=>e&&"string"==typeof e?[e.replace(/\{\{[^}]+\}\}/g,"*")]:[],w=async(e=[])=>{const t=y(e),n=await Promise.all(t.map(e=>o.glob(e||"",{nodir:!0})));return Array.from(new Set(n.flat()))};
2
+ "use strict";var e=require("commander"),t=require("chokidar"),o=require("glob"),n=require("minimatch"),i=require("chalk"),a=require("./config.js"),r=require("./heuristic-config.js"),c=require("./extractor/core/extractor.js");require("node:path"),require("node:fs/promises"),require("jiti");var s=require("./types-generator.js"),l=require("./syncer.js"),u=require("./migrator.js"),g=require("./init.js"),d=require("./linter.js"),p=require("./status.js"),f=require("./locize.js");const m=new e.Command;m.name("i18next-cli").description("A unified, high-performance i18next CLI.").version("1.21.1"),m.option("-c, --config <path>","Path to i18next-cli config file (overrides detection)"),m.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.").option("--dry-run","Run the extractor without writing any files to disk.").option("--sync-primary","Sync primary language values with default values from code.").action(async e=>{try{const o=m.opts().config,i=await a.ensureConfig(o),r=async()=>{const t=await c.runExtractor(i,{isWatchMode:!!e.watch,isDryRun:!!e.dryRun,syncPrimaryWithDefaults:!!e.syncPrimary});return e.ci&&!t?(console.log("✅ No files were updated."),process.exit(0)):e.ci&&t&&(console.error("❌ Some files were updated. This should not happen in CI mode."),process.exit(1)),t};if(await r(),e.watch){console.log("\nWatching for changes...");const e=await w(i.extract.input),o=y(i.extract.ignore),a=h(i.extract.output),c=[...o,...a].filter(Boolean),s=e.filter(e=>!c.some(t=>n.minimatch(e,t,{dot:!0})));t.watch(s,{ignored:/node_modules/,persistent:!0}).on("change",e=>{console.log(`\nFile changed: ${e}`),r()})}}catch(e){console.error("Error running extractor:",e),process.exit(1)}}),m.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)=>{const o=m.opts().config;let n=await a.loadConfig(o);if(!n){console.log(i.blue("No config file found. Attempting to detect project structure..."));const e=await r.detectConfig();e||(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!")),n=e}await p.runStatus(n,{detail:e,namespace:t.namespace})}),m.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 o=m.opts().config,i=await a.ensureConfig(o),r=()=>s.runTypesGenerator(i);if(await r(),e.watch){console.log("\nWatching for changes...");const e=await w(i.types?.input||[]),o=[...y(i.extract?.ignore)].filter(Boolean),a=e.filter(e=>!o.some(t=>n.minimatch(e,t,{dot:!0})));t.watch(a,{persistent:!0}).on("change",e=>{console.log(`\nFile changed: ${e}`),r()})}}),m.command("sync").description("Synchronize secondary language files with the primary language file.").action(async()=>{const e=m.opts().config,t=await a.ensureConfig(e);await l.runSyncer(t)}),m.command("migrate-config [configPath]").description("Migrate a legacy i18next-parser.config.js to the new format.").action(async e=>{await u.runMigrator(e)}),m.command("init").description("Create a new i18next.config.ts/js file with an interactive setup wizard.").action(g.runInit),m.command("lint").description("Find potential issues like hardcoded strings in your codebase.").option("-w, --watch","Watch for file changes and re-run the linter.").action(async e=>{const o=m.opts().config,c=async()=>{let e=await a.loadConfig(o);if(!e){console.log(i.blue("No config file found. Attempting to detect project structure..."));const t=await r.detectConfig();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 i18next-cli init")}`),process.exit(1)),console.log(i.green("Project structure detected successfully!")),e=t}await d.runLinterCli(e)};if(await c(),e.watch){console.log("\nWatching for changes...");const e=await a.loadConfig(o);if(e?.extract?.input){const o=await w(e.extract.input),i=[...y(e.extract.ignore),...h(e.extract.output)].filter(Boolean),a=o.filter(e=>!i.some(t=>n.minimatch(e,t,{dot:!0})));t.watch(a,{ignored:/node_modules/,persistent:!0}).on("change",e=>{console.log(`\nFile changed: ${e}`),c()})}}}),m.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=m.opts().config,o=await a.ensureConfig(t);await f.runLocizeSync(o,e)}),m.command("locize-download").description("Download all translations from your locize project.").action(async e=>{const t=m.opts().config,o=await a.ensureConfig(t);await f.runLocizeDownload(o,e)}),m.command("locize-migrate").description("Migrate local translation files to a new locize project.").action(async e=>{const t=m.opts().config,o=await a.ensureConfig(t);await f.runLocizeMigrate(o,e)}),m.parse(process.argv);const y=e=>Array.isArray(e)?e:e?[e]:[],h=e=>e&&"string"==typeof e?[e.replace(/\{\{[^}]+\}\}/g,"*")]:[],w=async(e=[])=>{const t=y(e),n=await Promise.all(t.map(e=>o.glob(e||"",{nodir:!0})));return Array.from(new Set(n.flat()))};
@@ -1 +1 @@
1
- "use strict";var e=require("node:path"),t=require("glob"),s=require("../../utils/nested-object.js"),n=require("../../utils/file-utils.js"),r=require("../../utils/default-value.js");function a(e){const t=`^${e.replace(/[.+?^${}()|[\]\\]/g,"\\$&").replace(/\*/g,".*")}$`;return new RegExp(t)}function o(e,t){if("object"!=typeof e||null===e||Array.isArray(e))return e;const s={},n=t?.extract?.pluralSeparator??"_",r=["zero","one","two","few","many","other"],a=r.map(e=>`ordinal${n}${e}`),l=Object.keys(e).sort((e,t)=>{const s=e=>{for(const t of a)if(e.endsWith(`${n}${t}`)){return{base:e.slice(0,-(n.length+t.length)),form:t,isOrdinal:!0,isPlural:!0,fullKey:e}}for(const t of r)if(e.endsWith(`${n}${t}`)){return{base:e.slice(0,-(n.length+t.length)),form:t,isOrdinal:!1,isPlural:!0,fullKey:e}}return{base:e,form:"",isOrdinal:!1,isPlural:!1,fullKey:e}},o=s(e),l=s(t);if(o.isPlural&&l.isPlural){const e=o.base.localeCompare(l.base,void 0,{sensitivity:"base"});if(0!==e)return e;if(o.isOrdinal!==l.isOrdinal)return o.isOrdinal?1:-1;const t=o.isOrdinal?a:r,s=t.indexOf(o.form),n=t.indexOf(l.form);return-1!==s&&-1!==n?s-n:o.form.localeCompare(l.form)}const i=e.localeCompare(t,void 0,{sensitivity:"base"});return 0===i?e.localeCompare(t,void 0,{sensitivity:"case"}):i});for(const n of l)s[n]=o(e[n],t);return s}function l(e,t,n,a,l,i=[],c=new Set,u=!1){const{keySeparator:f=".",sort:d=!0,removeUnusedKeys:p=!0,primaryLanguage:g,defaultValue:y="",pluralSeparator:h="_",contextSeparator:x="_"}=n.extract,m=new Set;let O=[],v=[];try{const e=new Intl.PluralRules(a,{type:"cardinal"}),t=new Intl.PluralRules(a,{type:"ordinal"});O=e.resolvedOptions().pluralCategories,v=t.resolvedOptions().pluralCategories,O.forEach(e=>m.add(e)),t.resolvedOptions().pluralCategories.forEach(e=>m.add(`ordinal_${e}`))}catch(e){const t=g||"en",s=new Intl.PluralRules(t,{type:"cardinal"}),n=new Intl.PluralRules(t,{type:"ordinal"});O=s.resolvedOptions().pluralCategories,v=n.resolvedOptions().pluralCategories,O.forEach(e=>m.add(e)),n.resolvedOptions().pluralCategories.forEach(e=>m.add(`ordinal_${e}`))}const S=e.filter(({key:e,hasCount:t,isOrdinal:s})=>{if(i.some(t=>t.test(e)))return!1;if(!t)return!0;const n=e.split(h);if(t&&1===n.length)return!0;if(1===O.length&&"other"===O[0]&&1===n.length)return!0;if(s&&n.includes("ordinal")){const e=n[n.length-1];return m.has(`ordinal_${e}`)}if(t){const e=n[n.length-1];return m.has(e)}return!0}),N=new Set;for(const e of S)if(e.isExpandedPlural){const t=String(e.key).split(h);t.length>=3&&"ordinal"===t[t.length-2]?N.add(t.slice(0,-2).join(h)):N.add(t.slice(0,-1).join(h))}let $=p?{}:JSON.parse(JSON.stringify(t));const w=s.getNestedKeys(t,f??".");for(const e of w)if(i.some(t=>t.test(e))){const n=s.getNestedValue(t,e,f??".");s.setNestedValue($,e,n,f??".")}if(p){const e=s.getNestedKeys(t,f??".");for(const n of e){const e=n.split(h);if("zero"===e[e.length-1]){const r=e.slice(0,-1).join(h);if(S.some(({key:e})=>e.split(h).slice(0,-1).join(h)===r)){const e=s.getNestedValue(t,n,f??".");s.setNestedValue($,n,e,f??".")}}}}for(const{key:e,defaultValue:o,explicitDefault:i,hasCount:d,isExpandedPlural:p,isOrdinal:m}of S){if(d&&!p){const t=String(e).split(h);let s=e;if(t.length>=3&&"ordinal"===t[t.length-2]?s=t.slice(0,-2).join(h):t.length>=2&&(s=t.slice(0,-1).join(h)),N.has(s))continue}if(d&&!p){if(1===String(e).split(h).length&&a!==g){const i=e;if(N.has(i));else{const e=m?v:O;for(const c of e){const e=m?`${i}${h}ordinal${h}${c}`:`${i}${h}${c}`,u=s.getNestedValue(t,e,f??".");if(void 0===u){let t;t="string"==typeof o?o:r.resolveDefaultValue(y,String(i),l||n?.extract?.defaultNS||"translation",a,o),s.setNestedValue($,e,t,f??".")}else s.setNestedValue($,e,u,f??".")}}continue}}const w=s.getNestedValue(t,e,f??"."),b=!1===f||!S.some(t=>t.key!==e&&t.key.startsWith(`${e}${f}`)),j="object"==typeof w&&null!==w&&(c.has(e)||!o||o===e),P="object"==typeof w&&null!==w&&b&&!c.has(e)&&!j;if(j){s.setNestedValue($,e,w,f??".");continue}let k;if(void 0===w||P)if(a===g)if(u){const t=o&&(o===e||e!==o&&(e.startsWith(o+h)||e.startsWith(o+x)));k=o&&!t?o:r.resolveDefaultValue(y,e,l||n?.extract?.defaultNS||"translation",a,o)}else k=o||e;else k=r.resolveDefaultValue(y,e,l||n?.extract?.defaultNS||"translation",a,o);else if(a===g&&u){const t=o&&(o===e||e!==o&&(e.startsWith(o+h)||e.startsWith(o+x)));k=(e.includes(h)||e.includes(x))&&!i?w:o&&!t?o:w}else k=w;s.setNestedValue($,e,k,f??".")}if(!0===d)return o($,n);if("function"==typeof d){const e={},t=Object.keys($),s=new Map;for(const e of S){const t=!1===f?e.key:e.key.split(f)[0];s.has(t)||s.set(t,e)}t.sort((e,t)=>{if("function"==typeof d){const n=s.get(e),r=s.get(t);if(n&&r)return d(n,r)}return e.localeCompare(t,void 0,{sensitivity:"base"})});for(const s of t)e[s]=o($[s],n);$=e}return $}exports.getTranslations=async function(s,r,o,{syncPrimaryWithDefaults:i=!1}={}){o.extract.primaryLanguage||=o.locales[0]||"en",o.extract.secondaryLanguages||=o.locales.filter(e=>e!==o?.extract?.primaryLanguage);const c=[...o.extract.preservePatterns||[]],u=o.extract.indentation??2;for(const e of r)c.push(`${e}.*`);const f=c.map(a),d="__no_namespace__",p=new Map;for(const e of s.values()){const t=e.nsIsImplicit&&!1===o.extract.defaultNS?d:String(e.ns??o.extract.defaultNS??"translation");p.has(t)||p.set(t,[]),p.get(t).push(e)}const g=[],y=Array.isArray(o.extract.ignore)?o.extract.ignore:o.extract.ignore?[o.extract.ignore]:[];for(const s of o.locales){if(o.extract.mergeNamespaces||"string"==typeof o.extract.output&&!o.extract.output.includes("{{namespace}}")){const t={},a=n.getOutputPath(o.extract.output,s),c=e.resolve(process.cwd(),a),y=await n.loadTranslationFile(c)||{},h=Object.keys(y),x=!1!==o.extract.defaultNS&&h.some(e=>{const t=y[e];return"object"==typeof t&&null!==t&&!Array.isArray(t)})?new Set([...p.keys(),...h]):new Set([...p.keys(),d]);for(const e of x){const n=p.get(e)||[];if(e===d){const e=l(n,y,o,s,void 0,f,r,i);Object.assign(t,e)}else{const a=y[e]||{};t[e]=l(n,a,o,s,e,f,r,i)}}const m=JSON.stringify(y,null,u),O=JSON.stringify(t,null,u);g.push({path:c,updated:O!==m,newTranslations:t,existingTranslations:y})}else{const a=new Set(p.keys()),c=n.getOutputPath(o.extract.output,s,"*").replace(/\\/g,"/"),d=await t.glob(c,{ignore:y});for(const t of d)a.add(e.basename(t,e.extname(t)));for(const t of a){const a=p.get(t)||[],c=n.getOutputPath(o.extract.output,s,t),d=e.resolve(process.cwd(),c),y=await n.loadTranslationFile(d)||{},h=l(a,y,o,s,t,f,r,i),x=JSON.stringify(y,null,u),m=JSON.stringify(h,null,u);g.push({path:d,updated:m!==x,newTranslations:h,existingTranslations:y})}}}return g};
1
+ "use strict";var e=require("node:path"),t=require("glob"),s=require("../../utils/nested-object.js"),n=require("../../utils/file-utils.js"),r=require("../../utils/default-value.js");function o(e){const t=`^${e.replace(/[.+?^${}()|[\]\\]/g,"\\$&").replace(/\*/g,".*")}$`;return new RegExp(t)}function a(e,t){if("object"!=typeof e||null===e||Array.isArray(e))return e;const s={},n=t?.extract?.pluralSeparator??"_",r=["zero","one","two","few","many","other"],o=r.map(e=>`ordinal${n}${e}`),i=Object.keys(e).sort((e,t)=>{const s=e=>{for(const t of o)if(e.endsWith(`${n}${t}`)){return{base:e.slice(0,-(n.length+t.length)),form:t,isOrdinal:!0,isPlural:!0,fullKey:e}}for(const t of r)if(e.endsWith(`${n}${t}`)){return{base:e.slice(0,-(n.length+t.length)),form:t,isOrdinal:!1,isPlural:!0,fullKey:e}}return{base:e,form:"",isOrdinal:!1,isPlural:!1,fullKey:e}},a=s(e),i=s(t);if(a.isPlural&&i.isPlural){const e=a.base.localeCompare(i.base,void 0,{sensitivity:"base"});if(0!==e)return e;if(a.isOrdinal!==i.isOrdinal)return a.isOrdinal?1:-1;const t=a.isOrdinal?o:r,s=t.indexOf(a.form),n=t.indexOf(i.form);return-1!==s&&-1!==n?s-n:a.form.localeCompare(i.form)}const l=e.localeCompare(t,void 0,{sensitivity:"base"});return 0===l?e.localeCompare(t,void 0,{sensitivity:"case"}):l});for(const n of i)s[n]=a(e[n],t);return s}function i(e,t,n,i,l,c=[],u=new Set,f=!1){const{keySeparator:d=".",sort:p=!0,removeUnusedKeys:g=!0,primaryLanguage:y,defaultValue:h="",pluralSeparator:x="_",contextSeparator:m="_"}=n.extract,O=new Set;let S=[],v=[];try{const e=new Intl.PluralRules(i,{type:"cardinal"}),t=new Intl.PluralRules(i,{type:"ordinal"});S=e.resolvedOptions().pluralCategories,v=t.resolvedOptions().pluralCategories,S.forEach(e=>O.add(e)),t.resolvedOptions().pluralCategories.forEach(e=>O.add(`ordinal_${e}`))}catch(e){const t=y||"en",s=new Intl.PluralRules(t,{type:"cardinal"}),n=new Intl.PluralRules(t,{type:"ordinal"});S=s.resolvedOptions().pluralCategories,v=n.resolvedOptions().pluralCategories,S.forEach(e=>O.add(e)),n.resolvedOptions().pluralCategories.forEach(e=>O.add(`ordinal_${e}`))}const N=n.extract.preservePatterns||[],$="string"==typeof n.extract.nsSeparator?n.extract.nsSeparator:":",w=e=>{if(c.some(t=>t.test(e)))return!0;for(const t of N)if("string"==typeof t){if(t.endsWith(`${$}*`)){const e=t.slice(0,-($.length+1));if("*"===e||l&&e===l)return!0}if(t.includes($)&&l){const[s,n]=t.split($);if(s===l){if(o(n).test(e))return!0}}}return!1},b=e.filter(({key:e,hasCount:t,isOrdinal:s})=>{if((e=>{if(c.some(t=>t.test(e)))return!0;for(const e of N)if("string"==typeof e&&e.endsWith(`${$}*`)){const t=e.slice(0,-($.length+1));if("*"===t||l&&t===l)return!0}return!1})(e))return!1;if(!t)return!0;const n=e.split(x);if(t&&1===n.length)return!0;if(1===S.length&&"other"===S[0]&&1===n.length)return!0;if(s&&n.includes("ordinal")){const e=n[n.length-1];return O.has(`ordinal_${e}`)}if(t){const e=n[n.length-1];return O.has(e)}return!0}),j=new Set;for(const e of b)if(e.isExpandedPlural){const t=String(e.key).split(x);t.length>=3&&"ordinal"===t[t.length-2]?j.add(t.slice(0,-2).join(x)):j.add(t.slice(0,-1).join(x))}let P=g?{}:JSON.parse(JSON.stringify(t));const k=s.getNestedKeys(t,d??".");for(const e of k)if(w(e)){const n=s.getNestedValue(t,e,d??".");s.setNestedValue(P,e,n,d??".")}if(g){const e=s.getNestedKeys(t,d??".");for(const n of e){const e=n.split(x);if("zero"===e[e.length-1]){const r=e.slice(0,-1).join(x);if(b.some(({key:e})=>e.split(x).slice(0,-1).join(x)===r)){const e=s.getNestedValue(t,n,d??".");s.setNestedValue(P,n,e,d??".")}}}}for(const{key:e,defaultValue:o,explicitDefault:a,hasCount:c,isExpandedPlural:p,isOrdinal:g}of b){if(c&&!p){const t=String(e).split(x);let s=e;if(t.length>=3&&"ordinal"===t[t.length-2]?s=t.slice(0,-2).join(x):t.length>=2&&(s=t.slice(0,-1).join(x)),j.has(s))continue}if(c&&!p){if(1===String(e).split(x).length&&i!==y){const a=e;if(j.has(a));else{const e=g?v:S;for(const c of e){const e=g?`${a}${x}ordinal${x}${c}`:`${a}${x}${c}`,u=s.getNestedValue(t,e,d??".");if(void 0===u){let t;t="string"==typeof o?o:r.resolveDefaultValue(h,String(a),l||n?.extract?.defaultNS||"translation",i,o),s.setNestedValue(P,e,t,d??".")}else s.setNestedValue(P,e,u,d??".")}}continue}}const O=s.getNestedValue(t,e,d??"."),N=!1===d||!b.some(t=>t.key!==e&&t.key.startsWith(`${e}${d}`)),w="object"==typeof O&&null!==O&&(u.has(e)||!o||o===e),k="object"==typeof O&&null!==O&&N&&!u.has(e)&&!w;if(w){s.setNestedValue(P,e,O,d??".");continue}let V;if(void 0===O||k)if(i===y)if(f){const t=o&&(o===e||o.includes($)||e!==o&&(e.startsWith(o+x)||e.startsWith(o+m)));V=o&&!t?o:r.resolveDefaultValue(h,e,l||n?.extract?.defaultNS||"translation",i,o)}else V=o||e;else V=r.resolveDefaultValue(h,e,l||n?.extract?.defaultNS||"translation",i,o);else if(i===y&&f){const t=o&&(o===e||o.includes($)||e!==o&&(e.startsWith(o+x)||e.startsWith(o+m)));V=(e.includes(x)||e.includes(m))&&!a?O:o&&!t?o:O}else V=O;s.setNestedValue(P,e,V,d??".")}if(!0===p)return a(P,n);if("function"==typeof p){const e={},t=Object.keys(P),s=new Map;for(const e of b){const t=!1===d?e.key:e.key.split(d)[0];s.has(t)||s.set(t,e)}t.sort((e,t)=>{if("function"==typeof p){const n=s.get(e),r=s.get(t);if(n&&r)return p(n,r)}return e.localeCompare(t,void 0,{sensitivity:"base"})});for(const s of t)e[s]=a(P[s],n);P=e}return P}exports.getTranslations=async function(s,r,a,{syncPrimaryWithDefaults:l=!1}={}){a.extract.primaryLanguage||=a.locales[0]||"en",a.extract.secondaryLanguages||=a.locales.filter(e=>e!==a?.extract?.primaryLanguage);const c=[...a.extract.preservePatterns||[]],u=a.extract.indentation??2;for(const e of r)c.push(`${e}.*`);const f=c.map(o),d="__no_namespace__",p=new Map;for(const e of s.values()){const t=e.nsIsImplicit&&!1===a.extract.defaultNS?d:String(e.ns??a.extract.defaultNS??"translation");p.has(t)||p.set(t,[]),p.get(t).push(e)}const g=[],y=Array.isArray(a.extract.ignore)?a.extract.ignore:a.extract.ignore?[a.extract.ignore]:[];for(const s of a.locales){if(a.extract.mergeNamespaces||"string"==typeof a.extract.output&&!a.extract.output.includes("{{namespace}}")){const t={},o=n.getOutputPath(a.extract.output,s),c=e.resolve(process.cwd(),o),y=await n.loadTranslationFile(c)||{},h=Object.keys(y),x=!1!==a.extract.defaultNS&&h.some(e=>{const t=y[e];return"object"==typeof t&&null!==t&&!Array.isArray(t)})?new Set([...p.keys(),...h]):new Set([...p.keys(),d]);for(const e of x){const n=p.get(e)||[];if(e===d){const e=i(n,y,a,s,void 0,f,r,l);Object.assign(t,e)}else{const o=y[e]||{};t[e]=i(n,o,a,s,e,f,r,l)}}const m=JSON.stringify(y,null,u),O=JSON.stringify(t,null,u);g.push({path:c,updated:O!==m,newTranslations:t,existingTranslations:y})}else{const o=new Set(p.keys()),c=n.getOutputPath(a.extract.output,s,"*").replace(/\\/g,"/"),d=await t.glob(c,{ignore:y});for(const t of d)o.add(e.basename(t,e.extname(t)));for(const t of o){const o=p.get(t)||[],c=n.getOutputPath(a.extract.output,s,t),d=e.resolve(process.cwd(),c),y=await n.loadTranslationFile(d)||{},h=i(o,y,a,s,t,f,r,l),x=JSON.stringify(y,null,u),m=JSON.stringify(h,null,u);g.push({path:d,updated:m!==x,newTranslations:h,existingTranslations:y})}}}return g};
@@ -1 +1 @@
1
- "use strict";function e(e,t,n,s,a,r=!1){try{const o=r?"ordinal":"cardinal",l=new Set;for(const e of a.locales)try{const t=new Intl.PluralRules(e,{type:o});t.resolvedOptions().pluralCategories.forEach(e=>l.add(e))}catch(e){const t=new Intl.PluralRules("en",{type:o});t.resolvedOptions().pluralCategories.forEach(e=>l.add(e))}const c=Array.from(l).sort(),u=a.extract.pluralSeparator??"_";if(1===c.length&&"other"===c[0])return void s.addKey({key:e,ns:n,defaultValue:t,hasCount:!0});for(const a of c){const o=r?`${e}${u}ordinal${u}${a}`:`${e}${u}${a}`;s.addKey({key:o,ns:n,defaultValue:t,hasCount:!0,isOrdinal:r})}}catch(a){s.addKey({key:e,ns:n,defaultValue:t})}}function t(e,t,n,s,a,r,o=!1){try{const l=o?"ordinal":"cardinal",c=new Set;for(const e of r.locales)try{const t=new Intl.PluralRules(e,{type:l});t.resolvedOptions().pluralCategories.forEach(e=>c.add(e))}catch(e){const t=new Intl.PluralRules(r.extract.primaryLanguage||"en",{type:l});t.resolvedOptions().pluralCategories.forEach(e=>c.add(e))}const u=Array.from(c).sort(),i=r.extract.pluralSeparator??"_";for(const r of u){const l=o?`${e}_${s}${i}ordinal${i}${r}`:`${e}_${s}${i}${r}`;a.addKey({key:l,ns:n,defaultValue:t,hasCount:!0,isOrdinal:o})}}catch(r){a.addKey({key:`${e}_${s}`,ns:n,defaultValue:t})}}function n(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 s(e){const t=/^\s*,\s*\{[^}]*ns\s*:\s*(['"])(.*?)\1/.exec(e);if(t)return t[2]}function a(e){const t=/^\s*,\s*\{[^}]*context\s*:\s*(['"])(.*?)\1/.exec(e);if(t)return t[2]}function r(e){const t=/^\s*,\s*\{[^}]*count\s*:\s*(\d+)/.exec(e);if(t)return parseInt(t[1],10)}function o(e){const t=/^\s*,\s*\{[^}]*ordinal\s*:\s*(true|false)/.exec(e);if(t)return"true"===t[1]}function l(e){const t=`^${e.replace(/[.+?^${}()|[\]\\]/g,"\\$&").replace(/\*/g,".*")}$`;return new RegExp(t)}exports.extractKeysFromComments=function(c,u,i,d){const f=new RegExp("\\bt\\s*\\(\\s*(['\"])([^'\"]+)\\1","g"),y=(i.extract.preservePatterns||[]).map(l),p=function(e){const t=[],n=new Set,s=/\/\/(.*)|\/\*([\s\S]*?)\*\//g;let a;for(;null!==(a=s.exec(e));){const e=(a[1]??a[2]).trim();e&&!n.has(e)&&(n.add(e),t.push(e))}return t}(c);for(const l of p){let c;for(;null!==(c=f.exec(l));){let f,p=c[2];if(!p||""===p.trim())continue;if(y.some(e=>e.test(p)))continue;const $=l.slice(c.index+c[0].length),x=n($),h=a($),g=r($),m=o($);let K=!1;const V=i.extract.pluralSeparator??"_";if(p.endsWith(`${V}ordinal`)){if(K=!0,p=p.slice(0,-(V.length+7)),!p||""===p.trim())continue;if(y.some(e=>e.test(p)))continue}const k=!0===m||K;f=s($);const w=i.extract.nsSeparator??":";if(!f&&w&&p.includes(w)){const e=p.split(w);if(f=e.shift(),p=e.join(w),!p||""===p.trim())continue;if(y.some(e=>e.test(p)))continue}if(!f&&d){const e=d("t");e?.defaultNs&&(f=e.defaultNs)}if(f||(f=i.extract.defaultNS),i.extract.disablePlurals)h?u.addKey({key:`${p}_${h}`,ns:f,defaultValue:x??p}):u.addKey({key:p,ns:f,defaultValue:x??p});else if(h&&g){t(p,x??p,f,h,u,i,k);!1!==i.extract?.generateBasePluralForms&&e(p,x??p,f,u,i,k)}else h?(u.addKey({key:p,ns:f,defaultValue:x??p}),u.addKey({key:`${p}_${h}`,ns:f,defaultValue:x??p})):g?e(p,x??p,f,u,i,k):u.addKey({key:p,ns:f,defaultValue:x??p})}}};
1
+ "use strict";function e(e,t,n,s,r,a=!1){try{const o=a?"ordinal":"cardinal",l=new Set;for(const e of r.locales)try{const t=new Intl.PluralRules(e,{type:o});t.resolvedOptions().pluralCategories.forEach(e=>l.add(e))}catch(e){const t=new Intl.PluralRules("en",{type:o});t.resolvedOptions().pluralCategories.forEach(e=>l.add(e))}const c=Array.from(l).sort(),u=r.extract.pluralSeparator??"_";if(1===c.length&&"other"===c[0])return void s.addKey({key:e,ns:n,defaultValue:t,hasCount:!0});for(const r of c){const o=a?`${e}${u}ordinal${u}${r}`:`${e}${u}${r}`;s.addKey({key:o,ns:n,defaultValue:t,hasCount:!0,isOrdinal:a})}}catch(r){s.addKey({key:e,ns:n,defaultValue:t})}}function t(e,t,n,s,r,a,o=!1){try{const l=o?"ordinal":"cardinal",c=new Set;for(const e of a.locales)try{const t=new Intl.PluralRules(e,{type:l});t.resolvedOptions().pluralCategories.forEach(e=>c.add(e))}catch(e){const t=new Intl.PluralRules(a.extract.primaryLanguage||"en",{type:l});t.resolvedOptions().pluralCategories.forEach(e=>c.add(e))}const u=Array.from(c).sort(),i=a.extract.pluralSeparator??"_";for(const a of u){const l=o?`${e}_${s}${i}ordinal${i}${a}`:`${e}_${s}${i}${a}`;r.addKey({key:l,ns:n,defaultValue:t,hasCount:!0,isOrdinal:o})}}catch(a){r.addKey({key:`${e}_${s}`,ns:n,defaultValue:t})}}function n(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 s(e){const t=/^\s*,\s*\{[^}]*ns\s*:\s*(['"])(.*?)\1/.exec(e);if(t)return t[2]}function r(e){const t=/^\s*,\s*\{[^}]*context\s*:\s*(['"])(.*?)\1/.exec(e);if(t)return t[2]}function a(e){const t=/^\s*,\s*\{[^}]*count\s*:\s*(\d+)/.exec(e);if(t)return parseInt(t[1],10)}function o(e){const t=/^\s*,\s*\{[^}]*ordinal\s*:\s*(true|false)/.exec(e);if(t)return"true"===t[1]}function l(e){const t=`^${e.replace(/[.+?^${}()|[\]\\]/g,"\\$&").replace(/\*/g,".*")}$`;return new RegExp(t)}exports.extractKeysFromComments=function(c,u,i,f){const d=new RegExp("\\bt\\s*\\(\\s*(['\"])([^'\"]+)\\1","g"),y=i.extract.preservePatterns||[],p=y.map(l),$=i.extract.nsSeparator??":",x=(e,t)=>{if(p.some(t=>t.test(e)))return!0;for(const e of y)if("string"==typeof e&&e.endsWith(`${$}*`)){const n="string"==typeof $&&$.length>0?e.slice(0,-($.length+1)):e.slice(0,-1);if("*"===n||t&&n===t)return!0}return!1},h=function(e){const t=[],n=new Set,s=/\/\/(.*)|\/\*([\s\S]*?)\*\//g;let r;for(;null!==(r=s.exec(e));){const e=(r[1]??r[2]).trim();e&&!n.has(e)&&(n.add(e),t.push(e))}return t}(c);for(const l of h){let c;for(;null!==(c=d.exec(l));){let d,y=c[2];if(!y||""===y.trim())continue;const p=l.slice(c.index+c[0].length),$=n(p),h=r(p),g=a(p),m=o(p);let K=!1;const V=i.extract.pluralSeparator??"_";if(y.endsWith(`${V}ordinal`)){if(K=!0,y=y.slice(0,-(V.length+7)),!y||""===y.trim())continue;if(x(y,d))continue}const k=!0===m||K;d=s(p);const S=i.extract.nsSeparator??":";if(!d&&S&&y.includes(S)){const e=y.split(S);if(d=e.shift(),y=e.join(S),!y||""===y.trim())continue;if(x(y,d))continue}if(!d&&f){const e=f("t");e?.defaultNs&&(d=e.defaultNs)}if(!x(y,d))if(d||(d=i.extract.defaultNS),i.extract.disablePlurals)h?u.addKey({key:`${y}_${h}`,ns:d,defaultValue:$??y}):u.addKey({key:y,ns:d,defaultValue:$??y});else if(h&&g){t(y,$??y,d,h,u,i,k);!1!==i.extract?.generateBasePluralForms&&e(y,$??y,d,u,i,k)}else h?(u.addKey({key:y,ns:d,defaultValue:$??y}),u.addKey({key:`${y}_${h}`,ns:d,defaultValue:$??y})):g?e(y,$??y,d,u,i,k):u.addKey({key:y,ns:d,defaultValue:$??y})}}};
package/dist/esm/cli.js CHANGED
@@ -1,2 +1,2 @@
1
1
  #!/usr/bin/env node
2
- import{Command as t}from"commander";import o from"chokidar";import{glob as e}from"glob";import{minimatch as i}from"minimatch";import n from"chalk";import{ensureConfig as a,loadConfig as r}from"./config.js";import{detectConfig as c}from"./heuristic-config.js";import{runExtractor as s}from"./extractor/core/extractor.js";import"node:path";import"node:fs/promises";import"jiti";import{runTypesGenerator as l}from"./types-generator.js";import{runSyncer as p}from"./syncer.js";import{runMigrator as m}from"./migrator.js";import{runInit as f}from"./init.js";import{runLinterCli as d}from"./linter.js";import{runStatus as g}from"./status.js";import{runLocizeSync as u,runLocizeDownload as y,runLocizeMigrate as h}from"./locize.js";const w=new t;w.name("i18next-cli").description("A unified, high-performance i18next CLI.").version("1.20.4"),w.option("-c, --config <path>","Path to i18next-cli config file (overrides detection)"),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.").option("--dry-run","Run the extractor without writing any files to disk.").option("--sync-primary","Sync primary language values with default values from code.").action(async t=>{try{const e=w.opts().config,n=await a(e),r=async()=>{const o=await s(n,{isWatchMode:!!t.watch,isDryRun:!!t.dryRun,syncPrimaryWithDefaults:!!t.syncPrimary});return t.ci&&!o?(console.log("✅ No files were updated."),process.exit(0)):t.ci&&o&&(console.error("❌ Some files were updated. This should not happen in CI mode."),process.exit(1)),o};if(await r(),t.watch){console.log("\nWatching for changes...");const t=await z(n.extract.input),e=x(n.extract.ignore),a=j(n.extract.output),c=[...e,...a].filter(Boolean),s=t.filter(t=>!c.some(o=>i(t,o,{dot:!0})));o.watch(s,{ignored:/node_modules/,persistent:!0}).on("change",t=>{console.log(`\nFile changed: ${t}`),r()})}}catch(t){console.error("Error running extractor:",t),process.exit(1)}}),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(t,o)=>{const e=w.opts().config;let i=await r(e);if(!i){console.log(n.blue("No config file found. Attempting to detect project structure..."));const t=await c();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 i18next-cli init")}`),process.exit(1)),console.log(n.green("Project structure detected successfully!")),i=t}await g(i,{detail:t,namespace:o.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 t=>{const e=w.opts().config,n=await a(e),r=()=>l(n);if(await r(),t.watch){console.log("\nWatching for changes...");const t=await z(n.types?.input||[]),e=[...x(n.extract?.ignore)].filter(Boolean),a=t.filter(t=>!e.some(o=>i(t,o,{dot:!0})));o.watch(a,{persistent:!0}).on("change",t=>{console.log(`\nFile changed: ${t}`),r()})}}),w.command("sync").description("Synchronize secondary language files with the primary language file.").action(async()=>{const t=w.opts().config,o=await a(t);await p(o)}),w.command("migrate-config [configPath]").description("Migrate a legacy i18next-parser.config.js to the new format.").action(async t=>{await m(t)}),w.command("init").description("Create a new i18next.config.ts/js file with an interactive setup wizard.").action(f),w.command("lint").description("Find potential issues like hardcoded strings in your codebase.").option("-w, --watch","Watch for file changes and re-run the linter.").action(async t=>{const e=w.opts().config,a=async()=>{let t=await r(e);if(!t){console.log(n.blue("No config file found. Attempting to detect project structure..."));const o=await c();o||(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!")),t=o}await d(t)};if(await a(),t.watch){console.log("\nWatching for changes...");const t=await r(e);if(t?.extract?.input){const e=await z(t.extract.input),n=[...x(t.extract.ignore),...j(t.extract.output)].filter(Boolean),r=e.filter(t=>!n.some(o=>i(t,o,{dot:!0})));o.watch(r,{ignored:/node_modules/,persistent:!0}).on("change",t=>{console.log(`\nFile changed: ${t}`),a()})}}}),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 t=>{const o=w.opts().config,e=await a(o);await u(e,t)}),w.command("locize-download").description("Download all translations from your locize project.").action(async t=>{const o=w.opts().config,e=await a(o);await y(e,t)}),w.command("locize-migrate").description("Migrate local translation files to a new locize project.").action(async t=>{const o=w.opts().config,e=await a(o);await h(e,t)}),w.parse(process.argv);const x=t=>Array.isArray(t)?t:t?[t]:[],j=t=>t&&"string"==typeof t?[t.replace(/\{\{[^}]+\}\}/g,"*")]:[],z=async(t=[])=>{const o=x(t),i=await Promise.all(o.map(t=>e(t||"",{nodir:!0})));return Array.from(new Set(i.flat()))};
2
+ import{Command as t}from"commander";import o from"chokidar";import{glob as e}from"glob";import{minimatch as i}from"minimatch";import n from"chalk";import{ensureConfig as a,loadConfig as r}from"./config.js";import{detectConfig as c}from"./heuristic-config.js";import{runExtractor as s}from"./extractor/core/extractor.js";import"node:path";import"node:fs/promises";import"jiti";import{runTypesGenerator as l}from"./types-generator.js";import{runSyncer as p}from"./syncer.js";import{runMigrator as m}from"./migrator.js";import{runInit as f}from"./init.js";import{runLinterCli as d}from"./linter.js";import{runStatus as g}from"./status.js";import{runLocizeSync as u,runLocizeDownload as y,runLocizeMigrate as h}from"./locize.js";const w=new t;w.name("i18next-cli").description("A unified, high-performance i18next CLI.").version("1.21.1"),w.option("-c, --config <path>","Path to i18next-cli config file (overrides detection)"),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.").option("--dry-run","Run the extractor without writing any files to disk.").option("--sync-primary","Sync primary language values with default values from code.").action(async t=>{try{const e=w.opts().config,n=await a(e),r=async()=>{const o=await s(n,{isWatchMode:!!t.watch,isDryRun:!!t.dryRun,syncPrimaryWithDefaults:!!t.syncPrimary});return t.ci&&!o?(console.log("✅ No files were updated."),process.exit(0)):t.ci&&o&&(console.error("❌ Some files were updated. This should not happen in CI mode."),process.exit(1)),o};if(await r(),t.watch){console.log("\nWatching for changes...");const t=await z(n.extract.input),e=x(n.extract.ignore),a=j(n.extract.output),c=[...e,...a].filter(Boolean),s=t.filter(t=>!c.some(o=>i(t,o,{dot:!0})));o.watch(s,{ignored:/node_modules/,persistent:!0}).on("change",t=>{console.log(`\nFile changed: ${t}`),r()})}}catch(t){console.error("Error running extractor:",t),process.exit(1)}}),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(t,o)=>{const e=w.opts().config;let i=await r(e);if(!i){console.log(n.blue("No config file found. Attempting to detect project structure..."));const t=await c();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 i18next-cli init")}`),process.exit(1)),console.log(n.green("Project structure detected successfully!")),i=t}await g(i,{detail:t,namespace:o.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 t=>{const e=w.opts().config,n=await a(e),r=()=>l(n);if(await r(),t.watch){console.log("\nWatching for changes...");const t=await z(n.types?.input||[]),e=[...x(n.extract?.ignore)].filter(Boolean),a=t.filter(t=>!e.some(o=>i(t,o,{dot:!0})));o.watch(a,{persistent:!0}).on("change",t=>{console.log(`\nFile changed: ${t}`),r()})}}),w.command("sync").description("Synchronize secondary language files with the primary language file.").action(async()=>{const t=w.opts().config,o=await a(t);await p(o)}),w.command("migrate-config [configPath]").description("Migrate a legacy i18next-parser.config.js to the new format.").action(async t=>{await m(t)}),w.command("init").description("Create a new i18next.config.ts/js file with an interactive setup wizard.").action(f),w.command("lint").description("Find potential issues like hardcoded strings in your codebase.").option("-w, --watch","Watch for file changes and re-run the linter.").action(async t=>{const e=w.opts().config,a=async()=>{let t=await r(e);if(!t){console.log(n.blue("No config file found. Attempting to detect project structure..."));const o=await c();o||(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!")),t=o}await d(t)};if(await a(),t.watch){console.log("\nWatching for changes...");const t=await r(e);if(t?.extract?.input){const e=await z(t.extract.input),n=[...x(t.extract.ignore),...j(t.extract.output)].filter(Boolean),r=e.filter(t=>!n.some(o=>i(t,o,{dot:!0})));o.watch(r,{ignored:/node_modules/,persistent:!0}).on("change",t=>{console.log(`\nFile changed: ${t}`),a()})}}}),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 t=>{const o=w.opts().config,e=await a(o);await u(e,t)}),w.command("locize-download").description("Download all translations from your locize project.").action(async t=>{const o=w.opts().config,e=await a(o);await y(e,t)}),w.command("locize-migrate").description("Migrate local translation files to a new locize project.").action(async t=>{const o=w.opts().config,e=await a(o);await h(e,t)}),w.parse(process.argv);const x=t=>Array.isArray(t)?t:t?[t]:[],j=t=>t&&"string"==typeof t?[t.replace(/\{\{[^}]+\}\}/g,"*")]:[],z=async(t=[])=>{const o=x(t),i=await Promise.all(o.map(t=>e(t||"",{nodir:!0})));return Array.from(new Set(i.flat()))};
@@ -1 +1 @@
1
- import{resolve as t,basename as e,extname as n}from"node:path";import{glob as r}from"glob";import{getNestedKeys as s,getNestedValue as o,setNestedValue as a}from"../../utils/nested-object.js";import{getOutputPath as i,loadTranslationFile as l}from"../../utils/file-utils.js";import{resolveDefaultValue as c}from"../../utils/default-value.js";function f(t){const e=`^${t.replace(/[.+?^${}()|[\]\\]/g,"\\$&").replace(/\*/g,".*")}$`;return new RegExp(e)}function u(t,e){if("object"!=typeof t||null===t||Array.isArray(t))return t;const n={},r=e?.extract?.pluralSeparator??"_",s=["zero","one","two","few","many","other"],o=s.map(t=>`ordinal${r}${t}`),a=Object.keys(t).sort((t,e)=>{const n=t=>{for(const e of o)if(t.endsWith(`${r}${e}`)){return{base:t.slice(0,-(r.length+e.length)),form:e,isOrdinal:!0,isPlural:!0,fullKey:t}}for(const e of s)if(t.endsWith(`${r}${e}`)){return{base:t.slice(0,-(r.length+e.length)),form:e,isOrdinal:!1,isPlural:!0,fullKey:t}}return{base:t,form:"",isOrdinal:!1,isPlural:!1,fullKey:t}},a=n(t),i=n(e);if(a.isPlural&&i.isPlural){const t=a.base.localeCompare(i.base,void 0,{sensitivity:"base"});if(0!==t)return t;if(a.isOrdinal!==i.isOrdinal)return a.isOrdinal?1:-1;const e=a.isOrdinal?o:s,n=e.indexOf(a.form),r=e.indexOf(i.form);return-1!==n&&-1!==r?n-r:a.form.localeCompare(i.form)}const l=t.localeCompare(e,void 0,{sensitivity:"base"});return 0===l?t.localeCompare(e,void 0,{sensitivity:"case"}):l});for(const r of a)n[r]=u(t[r],e);return n}function p(t,e,n,r,i,l=[],f=new Set,p=!1){const{keySeparator:d=".",sort:g=!0,removeUnusedKeys:y=!0,primaryLanguage:h,defaultValue:m="",pluralSeparator:x="_",contextSeparator:O="_"}=n.extract,S=new Set;let $=[],w=[];try{const t=new Intl.PluralRules(r,{type:"cardinal"}),e=new Intl.PluralRules(r,{type:"ordinal"});$=t.resolvedOptions().pluralCategories,w=e.resolvedOptions().pluralCategories,$.forEach(t=>S.add(t)),e.resolvedOptions().pluralCategories.forEach(t=>S.add(`ordinal_${t}`))}catch(t){const e=h||"en",n=new Intl.PluralRules(e,{type:"cardinal"}),r=new Intl.PluralRules(e,{type:"ordinal"});$=n.resolvedOptions().pluralCategories,w=r.resolvedOptions().pluralCategories,$.forEach(t=>S.add(t)),r.resolvedOptions().pluralCategories.forEach(t=>S.add(`ordinal_${t}`))}const v=t.filter(({key:t,hasCount:e,isOrdinal:n})=>{if(l.some(e=>e.test(t)))return!1;if(!e)return!0;const r=t.split(x);if(e&&1===r.length)return!0;if(1===$.length&&"other"===$[0]&&1===r.length)return!0;if(n&&r.includes("ordinal")){const t=r[r.length-1];return S.has(`ordinal_${t}`)}if(e){const t=r[r.length-1];return S.has(t)}return!0}),b=new Set;for(const t of v)if(t.isExpandedPlural){const e=String(t.key).split(x);e.length>=3&&"ordinal"===e[e.length-2]?b.add(e.slice(0,-2).join(x)):b.add(e.slice(0,-1).join(x))}let j=y?{}:JSON.parse(JSON.stringify(e));const k=s(e,d??".");for(const t of k)if(l.some(e=>e.test(t))){const n=o(e,t,d??".");a(j,t,n,d??".")}if(y){const t=s(e,d??".");for(const n of t){const t=n.split(x);if("zero"===t[t.length-1]){const r=t.slice(0,-1).join(x);if(v.some(({key:t})=>t.split(x).slice(0,-1).join(x)===r)){const t=o(e,n,d??".");a(j,n,t,d??".")}}}}for(const{key:t,defaultValue:s,explicitDefault:l,hasCount:u,isExpandedPlural:g,isOrdinal:y}of v){if(u&&!g){const e=String(t).split(x);let n=t;if(e.length>=3&&"ordinal"===e[e.length-2]?n=e.slice(0,-2).join(x):e.length>=2&&(n=e.slice(0,-1).join(x)),b.has(n))continue}if(u&&!g){if(1===String(t).split(x).length&&r!==h){const l=t;if(b.has(l));else{const t=y?w:$;for(const f of t){const t=y?`${l}${x}ordinal${x}${f}`:`${l}${x}${f}`,u=o(e,t,d??".");if(void 0===u){let e;e="string"==typeof s?s:c(m,String(l),i||n?.extract?.defaultNS||"translation",r,s),a(j,t,e,d??".")}else a(j,t,u,d??".")}}continue}}const S=o(e,t,d??"."),k=!1===d||!v.some(e=>e.key!==t&&e.key.startsWith(`${t}${d}`)),C="object"==typeof S&&null!==S&&(f.has(t)||!s||s===t),N="object"==typeof S&&null!==S&&k&&!f.has(t)&&!C;if(C){a(j,t,S,d??".");continue}let P;if(void 0===S||N)if(r===h)if(p){const e=s&&(s===t||t!==s&&(t.startsWith(s+x)||t.startsWith(s+O)));P=s&&!e?s:c(m,t,i||n?.extract?.defaultNS||"translation",r,s)}else P=s||t;else P=c(m,t,i||n?.extract?.defaultNS||"translation",r,s);else if(r===h&&p){const e=s&&(s===t||t!==s&&(t.startsWith(s+x)||t.startsWith(s+O)));P=(t.includes(x)||t.includes(O))&&!l?S:s&&!e?s:S}else P=S;a(j,t,P,d??".")}if(!0===g)return u(j,n);if("function"==typeof g){const t={},e=Object.keys(j),r=new Map;for(const t of v){const e=!1===d?t.key:t.key.split(d)[0];r.has(e)||r.set(e,t)}e.sort((t,e)=>{if("function"==typeof g){const n=r.get(t),s=r.get(e);if(n&&s)return g(n,s)}return t.localeCompare(e,void 0,{sensitivity:"base"})});for(const r of e)t[r]=u(j[r],n);j=t}return j}async function d(s,o,a,{syncPrimaryWithDefaults:c=!1}={}){a.extract.primaryLanguage||=a.locales[0]||"en",a.extract.secondaryLanguages||=a.locales.filter(t=>t!==a?.extract?.primaryLanguage);const u=[...a.extract.preservePatterns||[]],d=a.extract.indentation??2;for(const t of o)u.push(`${t}.*`);const g=u.map(f),y="__no_namespace__",h=new Map;for(const t of s.values()){const e=t.nsIsImplicit&&!1===a.extract.defaultNS?y:String(t.ns??a.extract.defaultNS??"translation");h.has(e)||h.set(e,[]),h.get(e).push(t)}const m=[],x=Array.isArray(a.extract.ignore)?a.extract.ignore:a.extract.ignore?[a.extract.ignore]:[];for(const s of a.locales){if(a.extract.mergeNamespaces||"string"==typeof a.extract.output&&!a.extract.output.includes("{{namespace}}")){const e={},n=i(a.extract.output,s),r=t(process.cwd(),n),f=await l(r)||{},u=Object.keys(f),x=!1!==a.extract.defaultNS&&u.some(t=>{const e=f[t];return"object"==typeof e&&null!==e&&!Array.isArray(e)})?new Set([...h.keys(),...u]):new Set([...h.keys(),y]);for(const t of x){const n=h.get(t)||[];if(t===y){const t=p(n,f,a,s,void 0,g,o,c);Object.assign(e,t)}else{const r=f[t]||{};e[t]=p(n,r,a,s,t,g,o,c)}}const O=JSON.stringify(f,null,d),S=JSON.stringify(e,null,d);m.push({path:r,updated:S!==O,newTranslations:e,existingTranslations:f})}else{const f=new Set(h.keys()),u=i(a.extract.output,s,"*").replace(/\\/g,"/"),y=await r(u,{ignore:x});for(const t of y)f.add(e(t,n(t)));for(const e of f){const n=h.get(e)||[],r=i(a.extract.output,s,e),f=t(process.cwd(),r),u=await l(f)||{},y=p(n,u,a,s,e,g,o,c),x=JSON.stringify(u,null,d),O=JSON.stringify(y,null,d);m.push({path:f,updated:O!==x,newTranslations:y,existingTranslations:u})}}}return m}export{d as getTranslations};
1
+ import{resolve as t,basename as e,extname as n}from"node:path";import{glob as r}from"glob";import{getNestedKeys as s,getNestedValue as o,setNestedValue as i}from"../../utils/nested-object.js";import{getOutputPath as a,loadTranslationFile as l}from"../../utils/file-utils.js";import{resolveDefaultValue as c}from"../../utils/default-value.js";function f(t){const e=`^${t.replace(/[.+?^${}()|[\]\\]/g,"\\$&").replace(/\*/g,".*")}$`;return new RegExp(e)}function u(t,e){if("object"!=typeof t||null===t||Array.isArray(t))return t;const n={},r=e?.extract?.pluralSeparator??"_",s=["zero","one","two","few","many","other"],o=s.map(t=>`ordinal${r}${t}`),i=Object.keys(t).sort((t,e)=>{const n=t=>{for(const e of o)if(t.endsWith(`${r}${e}`)){return{base:t.slice(0,-(r.length+e.length)),form:e,isOrdinal:!0,isPlural:!0,fullKey:t}}for(const e of s)if(t.endsWith(`${r}${e}`)){return{base:t.slice(0,-(r.length+e.length)),form:e,isOrdinal:!1,isPlural:!0,fullKey:t}}return{base:t,form:"",isOrdinal:!1,isPlural:!1,fullKey:t}},i=n(t),a=n(e);if(i.isPlural&&a.isPlural){const t=i.base.localeCompare(a.base,void 0,{sensitivity:"base"});if(0!==t)return t;if(i.isOrdinal!==a.isOrdinal)return i.isOrdinal?1:-1;const e=i.isOrdinal?o:s,n=e.indexOf(i.form),r=e.indexOf(a.form);return-1!==n&&-1!==r?n-r:i.form.localeCompare(a.form)}const l=t.localeCompare(e,void 0,{sensitivity:"base"});return 0===l?t.localeCompare(e,void 0,{sensitivity:"case"}):l});for(const r of i)n[r]=u(t[r],e);return n}function p(t,e,n,r,a,l=[],p=new Set,d=!1){const{keySeparator:g=".",sort:y=!0,removeUnusedKeys:h=!0,primaryLanguage:m,defaultValue:x="",pluralSeparator:S="_",contextSeparator:O="_"}=n.extract,$=new Set;let v=[],w=[];try{const t=new Intl.PluralRules(r,{type:"cardinal"}),e=new Intl.PluralRules(r,{type:"ordinal"});v=t.resolvedOptions().pluralCategories,w=e.resolvedOptions().pluralCategories,v.forEach(t=>$.add(t)),e.resolvedOptions().pluralCategories.forEach(t=>$.add(`ordinal_${t}`))}catch(t){const e=m||"en",n=new Intl.PluralRules(e,{type:"cardinal"}),r=new Intl.PluralRules(e,{type:"ordinal"});v=n.resolvedOptions().pluralCategories,w=r.resolvedOptions().pluralCategories,v.forEach(t=>$.add(t)),r.resolvedOptions().pluralCategories.forEach(t=>$.add(`ordinal_${t}`))}const b=n.extract.preservePatterns||[],j="string"==typeof n.extract.nsSeparator?n.extract.nsSeparator:":",k=t=>{if(l.some(e=>e.test(t)))return!0;for(const e of b)if("string"==typeof e){if(e.endsWith(`${j}*`)){const t=e.slice(0,-(j.length+1));if("*"===t||a&&t===a)return!0}if(e.includes(j)&&a){const[n,r]=e.split(j);if(n===a){if(f(r).test(t))return!0}}}return!1},P=t.filter(({key:t,hasCount:e,isOrdinal:n})=>{if((t=>{if(l.some(e=>e.test(t)))return!0;for(const t of b)if("string"==typeof t&&t.endsWith(`${j}*`)){const e=t.slice(0,-(j.length+1));if("*"===e||a&&e===a)return!0}return!1})(t))return!1;if(!e)return!0;const r=t.split(S);if(e&&1===r.length)return!0;if(1===v.length&&"other"===v[0]&&1===r.length)return!0;if(n&&r.includes("ordinal")){const t=r[r.length-1];return $.has(`ordinal_${t}`)}if(e){const t=r[r.length-1];return $.has(t)}return!0}),C=new Set;for(const t of P)if(t.isExpandedPlural){const e=String(t.key).split(S);e.length>=3&&"ordinal"===e[e.length-2]?C.add(e.slice(0,-2).join(S)):C.add(e.slice(0,-1).join(S))}let N=h?{}:JSON.parse(JSON.stringify(e));const _=s(e,g??".");for(const t of _)if(k(t)){const n=o(e,t,g??".");i(N,t,n,g??".")}if(h){const t=s(e,g??".");for(const n of t){const t=n.split(S);if("zero"===t[t.length-1]){const r=t.slice(0,-1).join(S);if(P.some(({key:t})=>t.split(S).slice(0,-1).join(S)===r)){const t=o(e,n,g??".");i(N,n,t,g??".")}}}}for(const{key:t,defaultValue:s,explicitDefault:l,hasCount:f,isExpandedPlural:u,isOrdinal:y}of P){if(f&&!u){const e=String(t).split(S);let n=t;if(e.length>=3&&"ordinal"===e[e.length-2]?n=e.slice(0,-2).join(S):e.length>=2&&(n=e.slice(0,-1).join(S)),C.has(n))continue}if(f&&!u){if(1===String(t).split(S).length&&r!==m){const l=t;if(C.has(l));else{const t=y?w:v;for(const f of t){const t=y?`${l}${S}ordinal${S}${f}`:`${l}${S}${f}`,u=o(e,t,g??".");if(void 0===u){let e;e="string"==typeof s?s:c(x,String(l),a||n?.extract?.defaultNS||"translation",r,s),i(N,t,e,g??".")}else i(N,t,u,g??".")}}continue}}const h=o(e,t,g??"."),$=!1===g||!P.some(e=>e.key!==t&&e.key.startsWith(`${t}${g}`)),b="object"==typeof h&&null!==h&&(p.has(t)||!s||s===t),k="object"==typeof h&&null!==h&&$&&!p.has(t)&&!b;if(b){i(N,t,h,g??".");continue}let _;if(void 0===h||k)if(r===m)if(d){const e=s&&(s===t||s.includes(j)||t!==s&&(t.startsWith(s+S)||t.startsWith(s+O)));_=s&&!e?s:c(x,t,a||n?.extract?.defaultNS||"translation",r,s)}else _=s||t;else _=c(x,t,a||n?.extract?.defaultNS||"translation",r,s);else if(r===m&&d){const e=s&&(s===t||s.includes(j)||t!==s&&(t.startsWith(s+S)||t.startsWith(s+O)));_=(t.includes(S)||t.includes(O))&&!l?h:s&&!e?s:h}else _=h;i(N,t,_,g??".")}if(!0===y)return u(N,n);if("function"==typeof y){const t={},e=Object.keys(N),r=new Map;for(const t of P){const e=!1===g?t.key:t.key.split(g)[0];r.has(e)||r.set(e,t)}e.sort((t,e)=>{if("function"==typeof y){const n=r.get(t),s=r.get(e);if(n&&s)return y(n,s)}return t.localeCompare(e,void 0,{sensitivity:"base"})});for(const r of e)t[r]=u(N[r],n);N=t}return N}async function d(s,o,i,{syncPrimaryWithDefaults:c=!1}={}){i.extract.primaryLanguage||=i.locales[0]||"en",i.extract.secondaryLanguages||=i.locales.filter(t=>t!==i?.extract?.primaryLanguage);const u=[...i.extract.preservePatterns||[]],d=i.extract.indentation??2;for(const t of o)u.push(`${t}.*`);const g=u.map(f),y="__no_namespace__",h=new Map;for(const t of s.values()){const e=t.nsIsImplicit&&!1===i.extract.defaultNS?y:String(t.ns??i.extract.defaultNS??"translation");h.has(e)||h.set(e,[]),h.get(e).push(t)}const m=[],x=Array.isArray(i.extract.ignore)?i.extract.ignore:i.extract.ignore?[i.extract.ignore]:[];for(const s of i.locales){if(i.extract.mergeNamespaces||"string"==typeof i.extract.output&&!i.extract.output.includes("{{namespace}}")){const e={},n=a(i.extract.output,s),r=t(process.cwd(),n),f=await l(r)||{},u=Object.keys(f),x=!1!==i.extract.defaultNS&&u.some(t=>{const e=f[t];return"object"==typeof e&&null!==e&&!Array.isArray(e)})?new Set([...h.keys(),...u]):new Set([...h.keys(),y]);for(const t of x){const n=h.get(t)||[];if(t===y){const t=p(n,f,i,s,void 0,g,o,c);Object.assign(e,t)}else{const r=f[t]||{};e[t]=p(n,r,i,s,t,g,o,c)}}const S=JSON.stringify(f,null,d),O=JSON.stringify(e,null,d);m.push({path:r,updated:O!==S,newTranslations:e,existingTranslations:f})}else{const f=new Set(h.keys()),u=a(i.extract.output,s,"*").replace(/\\/g,"/"),y=await r(u,{ignore:x});for(const t of y)f.add(e(t,n(t)));for(const e of f){const n=h.get(e)||[],r=a(i.extract.output,s,e),f=t(process.cwd(),r),u=await l(f)||{},y=p(n,u,i,s,e,g,o,c),x=JSON.stringify(u,null,d),S=JSON.stringify(y,null,d);m.push({path:f,updated:S!==x,newTranslations:y,existingTranslations:u})}}}return m}export{d as getTranslations};
@@ -1 +1 @@
1
- function e(e,u,i,d){const f=new RegExp("\\bt\\s*\\(\\s*(['\"])([^'\"]+)\\1","g"),y=(i.extract.preservePatterns||[]).map(c),p=function(e){const t=[],n=new Set,s=/\/\/(.*)|\/\*([\s\S]*?)\*\//g;let a;for(;null!==(a=s.exec(e));){const e=(a[1]??a[2]).trim();e&&!n.has(e)&&(n.add(e),t.push(e))}return t}(e);for(const e of p){let c;for(;null!==(c=f.exec(e));){let f,p=c[2];if(!p||""===p.trim())continue;if(y.some(e=>e.test(p)))continue;const $=e.slice(c.index+c[0].length),x=s($),h=r($),g=o($),m=l($);let V=!1;const k=i.extract.pluralSeparator??"_";if(p.endsWith(`${k}ordinal`)){if(V=!0,p=p.slice(0,-(k.length+7)),!p||""===p.trim())continue;if(y.some(e=>e.test(p)))continue}const K=!0===m||V;f=a($);const w=i.extract.nsSeparator??":";if(!f&&w&&p.includes(w)){const e=p.split(w);if(f=e.shift(),p=e.join(w),!p||""===p.trim())continue;if(y.some(e=>e.test(p)))continue}if(!f&&d){const e=d("t");e?.defaultNs&&(f=e.defaultNs)}if(f||(f=i.extract.defaultNS),i.extract.disablePlurals)h?u.addKey({key:`${p}_${h}`,ns:f,defaultValue:x??p}):u.addKey({key:p,ns:f,defaultValue:x??p});else if(h&&g){n(p,x??p,f,h,u,i,K);!1!==i.extract?.generateBasePluralForms&&t(p,x??p,f,u,i,K)}else h?(u.addKey({key:p,ns:f,defaultValue:x??p}),u.addKey({key:`${p}_${h}`,ns:f,defaultValue:x??p})):g?t(p,x??p,f,u,i,K):u.addKey({key:p,ns:f,defaultValue:x??p})}}}function t(e,t,n,s,a,r=!1){try{const o=r?"ordinal":"cardinal",l=new Set;for(const e of a.locales)try{const t=new Intl.PluralRules(e,{type:o});t.resolvedOptions().pluralCategories.forEach(e=>l.add(e))}catch(e){const t=new Intl.PluralRules("en",{type:o});t.resolvedOptions().pluralCategories.forEach(e=>l.add(e))}const c=Array.from(l).sort(),u=a.extract.pluralSeparator??"_";if(1===c.length&&"other"===c[0])return void s.addKey({key:e,ns:n,defaultValue:t,hasCount:!0});for(const a of c){const o=r?`${e}${u}ordinal${u}${a}`:`${e}${u}${a}`;s.addKey({key:o,ns:n,defaultValue:t,hasCount:!0,isOrdinal:r})}}catch(a){s.addKey({key:e,ns:n,defaultValue:t})}}function n(e,t,n,s,a,r,o=!1){try{const l=o?"ordinal":"cardinal",c=new Set;for(const e of r.locales)try{const t=new Intl.PluralRules(e,{type:l});t.resolvedOptions().pluralCategories.forEach(e=>c.add(e))}catch(e){const t=new Intl.PluralRules(r.extract.primaryLanguage||"en",{type:l});t.resolvedOptions().pluralCategories.forEach(e=>c.add(e))}const u=Array.from(c).sort(),i=r.extract.pluralSeparator??"_";for(const r of u){const l=o?`${e}_${s}${i}ordinal${i}${r}`:`${e}_${s}${i}${r}`;a.addKey({key:l,ns:n,defaultValue:t,hasCount:!0,isOrdinal:o})}}catch(r){a.addKey({key:`${e}_${s}`,ns:n,defaultValue:t})}}function s(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 a(e){const t=/^\s*,\s*\{[^}]*ns\s*:\s*(['"])(.*?)\1/.exec(e);if(t)return t[2]}function r(e){const t=/^\s*,\s*\{[^}]*context\s*:\s*(['"])(.*?)\1/.exec(e);if(t)return t[2]}function o(e){const t=/^\s*,\s*\{[^}]*count\s*:\s*(\d+)/.exec(e);if(t)return parseInt(t[1],10)}function l(e){const t=/^\s*,\s*\{[^}]*ordinal\s*:\s*(true|false)/.exec(e);if(t)return"true"===t[1]}function c(e){const t=`^${e.replace(/[.+?^${}()|[\]\\]/g,"\\$&").replace(/\*/g,".*")}$`;return new RegExp(t)}export{e as extractKeysFromComments};
1
+ function e(e,u,i,f){const d=new RegExp("\\bt\\s*\\(\\s*(['\"])([^'\"]+)\\1","g"),p=i.extract.preservePatterns||[],y=p.map(c),$=i.extract.nsSeparator??":",x=(e,t)=>{if(y.some(t=>t.test(e)))return!0;for(const e of p)if("string"==typeof e&&e.endsWith(`${$}*`)){const n="string"==typeof $&&$.length>0?e.slice(0,-($.length+1)):e.slice(0,-1);if("*"===n||t&&n===t)return!0}return!1},h=function(e){const t=[],n=new Set,s=/\/\/(.*)|\/\*([\s\S]*?)\*\//g;let r;for(;null!==(r=s.exec(e));){const e=(r[1]??r[2]).trim();e&&!n.has(e)&&(n.add(e),t.push(e))}return t}(e);for(const e of h){let c;for(;null!==(c=d.exec(e));){let d,p=c[2];if(!p||""===p.trim())continue;const y=e.slice(c.index+c[0].length),$=s(y),h=a(y),g=o(y),V=l(y);let k=!1;const m=i.extract.pluralSeparator??"_";if(p.endsWith(`${m}ordinal`)){if(k=!0,p=p.slice(0,-(m.length+7)),!p||""===p.trim())continue;if(x(p,d))continue}const K=!0===V||k;d=r(y);const S=i.extract.nsSeparator??":";if(!d&&S&&p.includes(S)){const e=p.split(S);if(d=e.shift(),p=e.join(S),!p||""===p.trim())continue;if(x(p,d))continue}if(!d&&f){const e=f("t");e?.defaultNs&&(d=e.defaultNs)}if(!x(p,d))if(d||(d=i.extract.defaultNS),i.extract.disablePlurals)h?u.addKey({key:`${p}_${h}`,ns:d,defaultValue:$??p}):u.addKey({key:p,ns:d,defaultValue:$??p});else if(h&&g){n(p,$??p,d,h,u,i,K);!1!==i.extract?.generateBasePluralForms&&t(p,$??p,d,u,i,K)}else h?(u.addKey({key:p,ns:d,defaultValue:$??p}),u.addKey({key:`${p}_${h}`,ns:d,defaultValue:$??p})):g?t(p,$??p,d,u,i,K):u.addKey({key:p,ns:d,defaultValue:$??p})}}}function t(e,t,n,s,r,a=!1){try{const o=a?"ordinal":"cardinal",l=new Set;for(const e of r.locales)try{const t=new Intl.PluralRules(e,{type:o});t.resolvedOptions().pluralCategories.forEach(e=>l.add(e))}catch(e){const t=new Intl.PluralRules("en",{type:o});t.resolvedOptions().pluralCategories.forEach(e=>l.add(e))}const c=Array.from(l).sort(),u=r.extract.pluralSeparator??"_";if(1===c.length&&"other"===c[0])return void s.addKey({key:e,ns:n,defaultValue:t,hasCount:!0});for(const r of c){const o=a?`${e}${u}ordinal${u}${r}`:`${e}${u}${r}`;s.addKey({key:o,ns:n,defaultValue:t,hasCount:!0,isOrdinal:a})}}catch(r){s.addKey({key:e,ns:n,defaultValue:t})}}function n(e,t,n,s,r,a,o=!1){try{const l=o?"ordinal":"cardinal",c=new Set;for(const e of a.locales)try{const t=new Intl.PluralRules(e,{type:l});t.resolvedOptions().pluralCategories.forEach(e=>c.add(e))}catch(e){const t=new Intl.PluralRules(a.extract.primaryLanguage||"en",{type:l});t.resolvedOptions().pluralCategories.forEach(e=>c.add(e))}const u=Array.from(c).sort(),i=a.extract.pluralSeparator??"_";for(const a of u){const l=o?`${e}_${s}${i}ordinal${i}${a}`:`${e}_${s}${i}${a}`;r.addKey({key:l,ns:n,defaultValue:t,hasCount:!0,isOrdinal:o})}}catch(a){r.addKey({key:`${e}_${s}`,ns:n,defaultValue:t})}}function s(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 r(e){const t=/^\s*,\s*\{[^}]*ns\s*:\s*(['"])(.*?)\1/.exec(e);if(t)return t[2]}function a(e){const t=/^\s*,\s*\{[^}]*context\s*:\s*(['"])(.*?)\1/.exec(e);if(t)return t[2]}function o(e){const t=/^\s*,\s*\{[^}]*count\s*:\s*(\d+)/.exec(e);if(t)return parseInt(t[1],10)}function l(e){const t=/^\s*,\s*\{[^}]*ordinal\s*:\s*(true|false)/.exec(e);if(t)return"true"===t[1]}function c(e){const t=`^${e.replace(/[.+?^${}()|[\]\\]/g,"\\$&").replace(/\*/g,".*")}$`;return new RegExp(t)}export{e as extractKeysFromComments};
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "i18next-cli",
3
- "version": "1.20.4",
3
+ "version": "1.21.1",
4
4
  "description": "A unified, high-performance i18next CLI.",
5
5
  "type": "module",
6
6
  "bin": {
package/src/cli.ts CHANGED
@@ -22,7 +22,7 @@ const program = new Command()
22
22
  program
23
23
  .name('i18next-cli')
24
24
  .description('A unified, high-performance i18next CLI.')
25
- .version('1.20.4')
25
+ .version('1.21.1')
26
26
 
27
27
  // new: global config override option
28
28
  program.option('-c, --config <path>', 'Path to i18next-cli config file (overrides detection)')
@@ -106,7 +106,7 @@ function buildNewTranslationsForNs (
106
106
  existingTranslations: Record<string, any>,
107
107
  config: I18nextToolkitConfig,
108
108
  locale: string,
109
- namespace?: string, // optional: undefined indicates "no namespace / top-level file"
109
+ namespace?: string,
110
110
  preservePatterns: RegExp[] = [],
111
111
  objectKeys: Set<string> = new Set(),
112
112
  syncPrimaryWithDefaults: boolean = false
@@ -120,6 +120,7 @@ function buildNewTranslationsForNs (
120
120
  pluralSeparator = '_',
121
121
  contextSeparator = '_',
122
122
  } = config.extract
123
+
123
124
  // Get the plural categories for the target language
124
125
  const targetLanguagePluralCategories = new Set<string>()
125
126
  // Track cardinal plural categories separately so we can special-case single-"other" languages
@@ -145,11 +146,69 @@ function buildNewTranslationsForNs (
145
146
  ordinalRules.resolvedOptions().pluralCategories.forEach(cat => targetLanguagePluralCategories.add(`ordinal_${cat}`))
146
147
  }
147
148
 
149
+ // Prepare namespace pattern checking helpers
150
+ const rawPreserve = config.extract.preservePatterns || []
151
+ const nsSep = typeof config.extract.nsSeparator === 'string' ? config.extract.nsSeparator : ':'
152
+
153
+ // Helper to check if a key should be filtered out during extraction
154
+ const shouldFilterKey = (key: string): boolean => {
155
+ // 1) regex based patterns (existing behavior)
156
+ if (preservePatterns.some(re => re.test(key))) {
157
+ return true
158
+ }
159
+ // 2) namespace:* style patterns (respect nsSeparator)
160
+ for (const rp of rawPreserve) {
161
+ if (typeof rp !== 'string') continue
162
+ if (rp.endsWith(`${nsSep}*`)) {
163
+ const nsPrefix = rp.slice(0, -(nsSep.length + 1))
164
+ // If namespace is provided to this builder, and pattern targets this namespace, skip keys from this ns
165
+ // Support wildcard namespace '*' to match any namespace
166
+ if (nsPrefix === '*' || (namespace && nsPrefix === namespace)) {
167
+ return true
168
+ }
169
+ }
170
+ }
171
+ return false
172
+ }
173
+
174
+ // Helper to check if an existing key should be preserved
175
+ const shouldPreserveExistingKey = (key: string): boolean => {
176
+ // 1) regex-style patterns
177
+ if (preservePatterns.some(re => re.test(key))) {
178
+ return true
179
+ }
180
+ // 2) namespace:key patterns - check if pattern matches this namespace:key combination
181
+ for (const rp of rawPreserve) {
182
+ if (typeof rp !== 'string') continue
183
+
184
+ // Handle namespace:* patterns
185
+ if (rp.endsWith(`${nsSep}*`)) {
186
+ const nsPrefix = rp.slice(0, -(nsSep.length + 1))
187
+ if (nsPrefix === '*' || (namespace && nsPrefix === namespace)) {
188
+ return true
189
+ }
190
+ }
191
+
192
+ // Handle namespace:specificKey patterns (e.g., 'other:okey', 'other:second*')
193
+ if (rp.includes(nsSep) && namespace) {
194
+ const [patternNs, patternKey] = rp.split(nsSep)
195
+ if (patternNs === namespace) {
196
+ // Convert the key part to regex (handle wildcards)
197
+ const keyRegex = globToRegex(patternKey)
198
+ if (keyRegex.test(key)) {
199
+ return true
200
+ }
201
+ }
202
+ }
203
+ }
204
+ return false
205
+ }
206
+
148
207
  // Filter nsKeys to only include keys relevant to this language
149
208
  const filteredKeys = nsKeys.filter(({ key, hasCount, isOrdinal }) => {
150
209
  // FIRST: Check if key matches preservePatterns and should be excluded
151
- if (preservePatterns.some(re => re.test(key))) {
152
- return false // Skip keys that match preserve patterns
210
+ if (shouldFilterKey(key)) {
211
+ return false
153
212
  }
154
213
 
155
214
  if (!hasCount) {
@@ -215,7 +274,7 @@ function buildNewTranslationsForNs (
215
274
  // Preserve keys that match the configured patterns
216
275
  const existingKeys = getNestedKeys(existingTranslations, keySeparator ?? '.')
217
276
  for (const existingKey of existingKeys) {
218
- if (preservePatterns.some(re => re.test(existingKey))) {
277
+ if (shouldPreserveExistingKey(existingKey)) {
219
278
  const value = getNestedValue(existingTranslations, existingKey, keySeparator ?? '.')
220
279
  setNestedValue(newTranslations, existingKey, value, keySeparator ?? '.')
221
280
  }
@@ -342,6 +401,8 @@ function buildNewTranslationsForNs (
342
401
  // - Otherwise use empty string for new keys
343
402
  const isDerivedDefault = defaultValue && (
344
403
  defaultValue === key || // Exact match
404
+ // Check if defaultValue includes namespace prefix (e.g., "translation:app.key")
405
+ defaultValue.includes(nsSep) ||
345
406
  // For variant keys (plural/context), check if defaultValue is the base
346
407
  (key !== defaultValue &&
347
408
  (key.startsWith(defaultValue + pluralSeparator) ||
@@ -363,6 +424,8 @@ function buildNewTranslationsForNs (
363
424
  // Only update when we have a meaningful defaultValue that's not derived from the key pattern.
364
425
  const isDerivedDefault = defaultValue && (
365
426
  defaultValue === key || // Exact match
427
+ // Check if defaultValue includes namespace prefix (e.g., "translation:app.key")
428
+ defaultValue.includes(nsSep) ||
366
429
  // For variant keys (plural/context), check if defaultValue is the base
367
430
  (key !== defaultValue &&
368
431
  (key.startsWith(defaultValue + pluralSeparator) ||
@@ -34,7 +34,26 @@ export function extractKeysFromComments (
34
34
  const keyRegex = new RegExp(`\\b${functionNameToFind}\\s*\\(\\s*(['"])([^'"]+)\\1`, 'g')
35
35
 
36
36
  // Prepare preservePatterns for filtering
37
- const preservePatterns = (config.extract.preservePatterns || []).map(globToRegex)
37
+ const rawPreservePatterns = config.extract.preservePatterns || []
38
+ const preservePatterns = rawPreservePatterns.map(globToRegex)
39
+ const nsSeparator = config.extract.nsSeparator ?? ':'
40
+
41
+ const matchesPreserve = (key: string, ns?: string) => {
42
+ // 1) regex-style matches (existing behavior)
43
+ if (preservePatterns.some(re => re.test(key))) return true
44
+ // 2) namespace:* style patterns => preserve entire namespace
45
+ for (const rp of rawPreservePatterns) {
46
+ if (typeof rp !== 'string') continue
47
+ if (rp.endsWith(`${nsSeparator}*`)) {
48
+ const nsPrefix = (typeof nsSeparator === 'string' && nsSeparator.length > 0)
49
+ ? rp.slice(0, -(nsSeparator.length + 1))
50
+ : rp.slice(0, -1)
51
+ // support '*' as a wildcard namespace
52
+ if (nsPrefix === '*' || (ns && nsPrefix === ns)) return true
53
+ }
54
+ }
55
+ return false
56
+ }
38
57
 
39
58
  const commentTexts = collectCommentTexts(code)
40
59
 
@@ -48,10 +67,7 @@ export function extractKeysFromComments (
48
67
  continue // Skip empty keys
49
68
  }
50
69
 
51
- // Check if key matches preservePatterns and should be excluded from extraction
52
- if (preservePatterns.some(re => re.test(key))) {
53
- continue // Skip keys that match preserve patterns
54
- }
70
+ // We'll check preservePatterns after namespace resolution below
55
71
 
56
72
  let ns: string | false | undefined
57
73
  const remainder = text.slice(match.index + match[0].length)
@@ -74,8 +90,8 @@ export function extractKeysFromComments (
74
90
  continue // Skip keys that become empty after normalization
75
91
  }
76
92
 
77
- // Re-check preservePatterns after key normalization
78
- if (preservePatterns.some(re => re.test(key))) {
93
+ // Re-check preservePatterns after key normalization (will check namespace-aware helper)
94
+ if (matchesPreserve(key, ns as string | undefined)) {
79
95
  continue // Skip normalized keys that match preserve patterns
80
96
  }
81
97
  }
@@ -97,8 +113,8 @@ export function extractKeysFromComments (
97
113
  continue // Skip keys that become empty after namespace removal
98
114
  }
99
115
 
100
- // Re-check preservePatterns after namespace processing
101
- if (preservePatterns.some(re => re.test(key))) {
116
+ // Re-check preservePatterns after namespace processing (namespace-aware)
117
+ if (matchesPreserve(key, ns as string | undefined)) {
102
118
  continue // Skip processed keys that match preserve patterns
103
119
  }
104
120
  }
@@ -112,6 +128,11 @@ export function extractKeysFromComments (
112
128
  }
113
129
  }
114
130
 
131
+ // Final preserve check for keys without prior namespace normalization
132
+ if (matchesPreserve(key, ns as string | undefined)) {
133
+ continue
134
+ }
135
+
115
136
  // 4. Final fallback to configured default namespace
116
137
  if (!ns) ns = config.extract.defaultNS
117
138
 
@@ -1 +1 @@
1
- {"version":3,"file":"translation-manager.d.ts","sourceRoot":"","sources":["../../../src/extractor/core/translation-manager.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,iBAAiB,EAAE,YAAY,EAAE,oBAAoB,EAAE,MAAM,aAAa,CAAA;AAobnF;;;;;;;;;;;;;;;;;;;;;;;;;;;;GA4BG;AACH,wBAAsB,eAAe,CACnC,IAAI,EAAE,GAAG,CAAC,MAAM,EAAE,YAAY,CAAC,EAC/B,UAAU,EAAE,GAAG,CAAC,MAAM,CAAC,EACvB,MAAM,EAAE,oBAAoB,EAC5B,EAAE,uBAA+B,EAAE,GAAE;IAAE,uBAAuB,CAAC,EAAE,OAAO,CAAA;CAAO,GAC9E,OAAO,CAAC,iBAAiB,EAAE,CAAC,CAgH9B"}
1
+ {"version":3,"file":"translation-manager.d.ts","sourceRoot":"","sources":["../../../src/extractor/core/translation-manager.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,iBAAiB,EAAE,YAAY,EAAE,oBAAoB,EAAE,MAAM,aAAa,CAAA;AAmfnF;;;;;;;;;;;;;;;;;;;;;;;;;;;;GA4BG;AACH,wBAAsB,eAAe,CACnC,IAAI,EAAE,GAAG,CAAC,MAAM,EAAE,YAAY,CAAC,EAC/B,UAAU,EAAE,GAAG,CAAC,MAAM,CAAC,EACvB,MAAM,EAAE,oBAAoB,EAC5B,EAAE,uBAA+B,EAAE,GAAE;IAAE,uBAAuB,CAAC,EAAE,OAAO,CAAA;CAAO,GAC9E,OAAO,CAAC,iBAAiB,EAAE,CAAC,CAgH9B"}
@@ -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,aAAa,EAC5B,MAAM,EAAE,oBAAoB,EAC5B,aAAa,CAAC,EAAE,CAAC,OAAO,EAAE,MAAM,KAAK;IAAE,SAAS,CAAC,EAAE,MAAM,CAAC;IAAC,SAAS,CAAC,EAAE,MAAM,CAAA;CAAE,GAAG,SAAS,GAC1F,IAAI,CA4HN"}
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,EAC5B,aAAa,CAAC,EAAE,CAAC,OAAO,EAAE,MAAM,KAAK;IAAE,SAAS,CAAC,EAAE,MAAM,CAAC;IAAC,SAAS,CAAC,EAAE,MAAM,CAAA;CAAE,GAAG,SAAS,GAC1F,IAAI,CAiJN"}