i18next-cli 1.11.1 → 1.11.2

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/CHANGELOG.md CHANGED
@@ -5,6 +5,12 @@ 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.11.2](https://github.com/i18next/i18next-cli/compare/v1.11.1...v1.11.2) - 2025-10-13
9
+
10
+ - **Extractor (`--sync-primary`):** Fixed critical bug where keys without explicit defaultValue were being overwritten with empty strings when using the `--sync-primary` flag. The `syncPrimaryWithDefaults` feature now correctly distinguishes between explicit default values provided in code (e.g., `t('key', 'New default')`) and derived defaults (e.g., when defaultValue equals the key name). Keys without explicit defaults now preserve their existing translations in the primary language, while only keys with meaningful code-specified defaults are updated. This ensures the feature works as intended for incremental translation updates without destroying existing content. [#61](https://github.com/i18next/i18next-cli/issues/61)
11
+ - **Extractor (AST):** Fixed inconsistent defaultValue generation for plural keys where different plural forms of the same base key could receive different default values during AST extraction. All plural forms now correctly inherit the same explicit defaultValue from the `t()` call's second argument.
12
+ - **Extractor (AST):** Fixed context+plural combinations generating unwanted base plural keys. When extracting keys with static context (e.g., `t('notifications.new', { context: 'email', count: 3 })`), the extractor now only generates context-specific plural forms (`notifications.new_email_one`, `notifications.new_email_other`) without creating redundant base plural forms (`notifications.new_one`, `notifications.new_other`).
13
+
8
14
  ## [1.11.1](https://github.com/i18next/i18next-cli/compare/v1.11.0...v1.11.1) - 2025-10-12
9
15
 
10
16
  - **Syncer:** Enhanced fix for TypeScript resource file handling in `sync` command. Building on the initial fix in v1.10.4 that resolved sync command failures with `outputFormat: 'ts'`, this release improves the TypeScript file parsing reliability and error handling when loading translation files with complex export patterns and TypeScript-specific syntax. [#59](https://github.com/i18next/i18next-cli/issues/59)
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"),n=require("glob"),o=require("chalk"),i=require("./config.js"),a=require("./heuristic-config.js"),r=require("./extractor/core/extractor.js");require("node:path"),require("node:fs/promises"),require("jiti");var c=require("./types-generator.js"),s=require("./syncer.js"),l=require("./migrator.js"),u=require("./init.js"),d=require("./linter.js"),g=require("./status.js"),p=require("./locize.js");const f=new e.Command;f.name("i18next-cli").description("A unified, high-performance i18next CLI.").version("1.11.1"),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.").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 t=await i.ensureConfig(),n=await r.runExtractor(t,{isWatchMode:!!e.watch,isDryRun:!!e.dryRun,syncPrimaryWithDefaults:!!e.syncPrimary});e.ci&&!n?(console.log("✅ No files were updated."),process.exit(0)):e.ci&&n&&(console.error("❌ Some files were updated. This should not happen in CI mode."),process.exit(1))}catch(e){console.error("Error running extractor:",e),process.exit(1)}}),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 n=await i.loadConfig();if(!n){console.log(o.blue("No config file found. Attempting to detect project structure..."));const e=await a.detectConfig();e||(console.error(o.red("Could not automatically detect your project structure.")),console.log(`Please create a config file first by running: ${o.cyan("npx i18next-cli init")}`),process.exit(1)),console.log(o.green("Project structure detected successfully!")),n=e}await g.runStatus(n,{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 o=await i.ensureConfig(),a=()=>c.runTypesGenerator(o);if(await a(),e.watch){console.log("\nWatching for changes...");t.watch(await n.glob(o.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 [configPath]").description("Migrate a legacy i18next-parser.config.js to the new format.").action(async e=>{await l.runMigrator(e)}),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.").option("-w, --watch","Watch for file changes and re-run the linter.").action(async e=>{const r=async()=>{let e=await i.loadConfig();if(!e){console.log(o.blue("No config file found. Attempting to detect project structure..."));const t=await a.detectConfig();t||(console.error(o.red("Could not automatically detect your project structure.")),console.log(`Please create a config file first by running: ${o.cyan("npx i18next-cli init")}`),process.exit(1)),console.log(o.green("Project structure detected successfully!")),e=t}await d.runLinter(e)};if(await r(),e.watch){console.log("\nWatching for changes...");const e=await i.loadConfig();if(e?.extract?.input){t.watch(await n.glob(e.extract.input),{ignored:/node_modules/,persistent:!0}).on("change",e=>{console.log(`\nFile changed: ${e}`),r()})}}}),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"),n=require("glob"),o=require("chalk"),i=require("./config.js"),a=require("./heuristic-config.js"),r=require("./extractor/core/extractor.js");require("node:path"),require("node:fs/promises"),require("jiti");var c=require("./types-generator.js"),s=require("./syncer.js"),l=require("./migrator.js"),u=require("./init.js"),d=require("./linter.js"),g=require("./status.js"),p=require("./locize.js");const f=new e.Command;f.name("i18next-cli").description("A unified, high-performance i18next CLI.").version("1.11.2"),f.command("extract").description("Extract translation keys from source files and update resource files.").option("-w, --watch","Watch for file changes and re-run the extractor.").option("--ci","Exit with a non-zero status code if any files are updated.").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 t=await i.ensureConfig(),n=await r.runExtractor(t,{isWatchMode:!!e.watch,isDryRun:!!e.dryRun,syncPrimaryWithDefaults:!!e.syncPrimary});e.ci&&!n?(console.log("✅ No files were updated."),process.exit(0)):e.ci&&n&&(console.error("❌ Some files were updated. This should not happen in CI mode."),process.exit(1))}catch(e){console.error("Error running extractor:",e),process.exit(1)}}),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 n=await i.loadConfig();if(!n){console.log(o.blue("No config file found. Attempting to detect project structure..."));const e=await a.detectConfig();e||(console.error(o.red("Could not automatically detect your project structure.")),console.log(`Please create a config file first by running: ${o.cyan("npx i18next-cli init")}`),process.exit(1)),console.log(o.green("Project structure detected successfully!")),n=e}await g.runStatus(n,{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 o=await i.ensureConfig(),a=()=>c.runTypesGenerator(o);if(await a(),e.watch){console.log("\nWatching for changes...");t.watch(await n.glob(o.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 [configPath]").description("Migrate a legacy i18next-parser.config.js to the new format.").action(async e=>{await l.runMigrator(e)}),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.").option("-w, --watch","Watch for file changes and re-run the linter.").action(async e=>{const r=async()=>{let e=await i.loadConfig();if(!e){console.log(o.blue("No config file found. Attempting to detect project structure..."));const t=await a.detectConfig();t||(console.error(o.red("Could not automatically detect your project structure.")),console.log(`Please create a config file first by running: ${o.cyan("npx i18next-cli init")}`),process.exit(1)),console.log(o.green("Project structure detected successfully!")),e=t}await d.runLinter(e)};if(await r(),e.watch){console.log("\nWatching for changes...");const e=await i.loadConfig();if(e?.extract?.input){t.watch(await n.glob(e.extract.input),{ignored:/node_modules/,persistent:!0}).on("change",e=>{console.log(`\nFile changed: ${e}`),r()})}}}),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("node:path"),t=require("glob"),s=require("../../utils/nested-object.js"),r=require("../../utils/file-utils.js"),n=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={},r=t?.extract?.pluralSeparator??"_",n=["zero","one","two","few","many","other"],a=n.map(e=>`ordinal_${e}`),l=Object.keys(e).sort((e,t)=>{const s=e=>{for(const t of a)if(e.endsWith(`${r}${t}`)){return{base:e.slice(0,-(r.length+t.length)),form:t,isOrdinal:!0,isPlural:!0,fullKey:e}}for(const t of n)if(e.endsWith(`${r}${t}`)){return{base:e.slice(0,-(r.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:n,s=t.indexOf(o.form),r=t.indexOf(l.form);return-1!==s&&-1!==r?s-r: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 r of l)s[r]=o(e[r],t);return s}function l(e,t,r,a,l,i,c,u=!1){const{keySeparator:f=".",sort:d=!0,removeUnusedKeys:p=!0,primaryLanguage:g,defaultValue:y="",pluralSeparator:h="_"}=r.extract,m=new Set;try{const e=new Intl.PluralRules(a,{type:"cardinal"}),t=new Intl.PluralRules(a,{type:"ordinal"});e.resolvedOptions().pluralCategories.forEach(e=>m.add(e)),t.resolvedOptions().pluralCategories.forEach(e=>m.add(`ordinal_${e}`))}catch(e){const t=new Intl.PluralRules(g||"en",{type:"cardinal"}),s=new Intl.PluralRules(g||"en",{type:"ordinal"});t.resolvedOptions().pluralCategories.forEach(e=>m.add(e)),s.resolvedOptions().pluralCategories.forEach(e=>m.add(`ordinal_${e}`))}const O=e.filter(({key:e,hasCount:t,isOrdinal:s})=>{if(i.some(t=>t.test(e)))return!1;if(!t)return!0;const r=e.split(h);if(s&&r.includes("ordinal")){const e=r[r.length-1];return m.has(`ordinal_${e}`)}if(t){const e=r[r.length-1];return m.has(e)}return!0});let x=p?{}:JSON.parse(JSON.stringify(t));const v=s.getNestedKeys(t,f??".");for(const e of v)if(i.some(t=>t.test(e))){const r=s.getNestedValue(t,e,f??".");s.setNestedValue(x,e,r,f??".")}if(p){const e=s.getNestedKeys(t,f??".");for(const r of e){const e=r.split(h);if("zero"===e[e.length-1]){const n=e.slice(0,-1).join(h);if(O.some(({key:e})=>e.split(h).slice(0,-1).join(h)===n)){const e=s.getNestedValue(t,r,f??".");s.setNestedValue(x,r,e,f??".")}}}}for(const{key:e,defaultValue:r}of O){const o=s.getNestedValue(t,e,f??"."),i=!O.some(t=>t.key.startsWith(`${e}${f}`)&&t.key!==e),d="object"==typeof o&&null!==o&&(c.has(e)||!r||r===e),p="object"==typeof o&&null!==o&&i&&!c.has(e)&&!d;if(d){s.setNestedValue(x,e,o,f??".");continue}let h;h=void 0===o||p?a===g?r||e:n.resolveDefaultValue(y,e,l,a):a===g&&u&&r&&r!==e?r:o,s.setNestedValue(x,e,h,f??".")}if(!0===d)return o(x,r);if("function"==typeof d){const e={},t=Object.keys(x),s=new Map;for(const e of O){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 r=s.get(e),n=s.get(t);if(r&&n)return d(r,n)}return e.localeCompare(t,void 0,{sensitivity:"base"})});for(const s of t)e[s]=o(x[s],r);x=e}return x}exports.getTranslations=async function(s,n,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.defaultNS??"translation",u=[...o.extract.preservePatterns||[]],f=o.extract.indentation??2;for(const e of n)u.push(`${e}.*`);const d=u.map(a),p=new Map;for(const e of s.values()){const t=e.ns||c;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||!o.extract.output.includes("{{namespace}}")){const t={},a=r.getOutputPath(o.extract.output,s),c=e.resolve(process.cwd(),a),u=await r.loadTranslationFile(c)||{},y=new Set([...p.keys(),...Object.keys(u)]);for(const e of y){const r=p.get(e)||[],a=u[e]||{};t[e]=l(r,a,o,s,e,d,n,i)}const h=JSON.stringify(u,null,f),m=JSON.stringify(t,null,f);g.push({path:c,updated:m!==h,newTranslations:t,existingTranslations:u})}else{const a=new Set(p.keys()),c=r.getOutputPath(o.extract.output,s,"*"),u=await t.glob(c,{ignore:y});for(const t of u)a.add(e.basename(t,e.extname(t)));for(const t of a){const a=p.get(t)||[],c=r.getOutputPath(o.extract.output,s,t),u=e.resolve(process.cwd(),c),y=await r.loadTranslationFile(u)||{},h=l(a,y,o,s,t,d,n,i),m=JSON.stringify(y,null,f),O=JSON.stringify(h,null,f);g.push({path:u,updated:O!==m,newTranslations:h,existingTranslations:y})}}}return g};
1
+ "use strict";var e=require("node:path"),t=require("glob"),s=require("../../utils/nested-object.js"),r=require("../../utils/file-utils.js"),n=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={},r=t?.extract?.pluralSeparator??"_",n=["zero","one","two","few","many","other"],a=n.map(e=>`ordinal${r}${e}`),l=Object.keys(e).sort((e,t)=>{const s=e=>{for(const t of a)if(e.endsWith(`${r}${t}`)){return{base:e.slice(0,-(r.length+t.length)),form:t,isOrdinal:!0,isPlural:!0,fullKey:e}}for(const t of n)if(e.endsWith(`${r}${t}`)){return{base:e.slice(0,-(r.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:n,s=t.indexOf(o.form),r=t.indexOf(l.form);return-1!==s&&-1!==r?s-r: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 r of l)s[r]=o(e[r],t);return s}function l(e,t,r,a,l,i,c,u=!1){const{keySeparator:f=".",sort:d=!0,removeUnusedKeys:p=!0,primaryLanguage:g,defaultValue:y="",pluralSeparator:h="_",contextSeparator:m="_"}=r.extract,O=new Set;try{const e=new Intl.PluralRules(a,{type:"cardinal"}),t=new Intl.PluralRules(a,{type:"ordinal"});e.resolvedOptions().pluralCategories.forEach(e=>O.add(e)),t.resolvedOptions().pluralCategories.forEach(e=>O.add(`ordinal_${e}`))}catch(e){const t=new Intl.PluralRules(g||"en",{type:"cardinal"}),s=new Intl.PluralRules(g||"en",{type:"ordinal"});t.resolvedOptions().pluralCategories.forEach(e=>O.add(e)),s.resolvedOptions().pluralCategories.forEach(e=>O.add(`ordinal_${e}`))}const x=e.filter(({key:e,hasCount:t,isOrdinal:s})=>{if(i.some(t=>t.test(e)))return!1;if(!t)return!0;const r=e.split(h);if(s&&r.includes("ordinal")){const e=r[r.length-1];return O.has(`ordinal_${e}`)}if(t){const e=r[r.length-1];return O.has(e)}return!0});let v=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 r=s.getNestedValue(t,e,f??".");s.setNestedValue(v,e,r,f??".")}if(p){const e=s.getNestedKeys(t,f??".");for(const r of e){const e=r.split(h);if("zero"===e[e.length-1]){const n=e.slice(0,-1).join(h);if(x.some(({key:e})=>e.split(h).slice(0,-1).join(h)===n)){const e=s.getNestedValue(t,r,f??".");s.setNestedValue(v,r,e,f??".")}}}}for(const{key:e,defaultValue:r}of x){const o=s.getNestedValue(t,e,f??"."),i=!x.some(t=>t.key.startsWith(`${e}${f}`)&&t.key!==e),d="object"==typeof o&&null!==o&&(c.has(e)||!r||r===e),p="object"==typeof o&&null!==o&&i&&!c.has(e)&&!d;if(d){s.setNestedValue(v,e,o,f??".");continue}let O;if(void 0===o||p)if(a===g)if(u){const t=r&&(r===e||e!==r&&(e.startsWith(r+h)||e.startsWith(r+m)));O=r&&!t?r:n.resolveDefaultValue(y,e,l,a)}else O=r||e;else O=n.resolveDefaultValue(y,e,l,a);else if(a===g&&u){const t=r&&(r===e||e!==r&&(e.startsWith(r+h)||e.startsWith(r+m)));O=r&&!t?r:o}else O=o;s.setNestedValue(v,e,O,f??".")}if(!0===d)return o(v,r);if("function"==typeof d){const e={},t=Object.keys(v),s=new Map;for(const e of x){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 r=s.get(e),n=s.get(t);if(r&&n)return d(r,n)}return e.localeCompare(t,void 0,{sensitivity:"base"})});for(const s of t)e[s]=o(v[s],r);v=e}return v}exports.getTranslations=async function(s,n,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.defaultNS??"translation",u=[...o.extract.preservePatterns||[]],f=o.extract.indentation??2;for(const e of n)u.push(`${e}.*`);const d=u.map(a),p=new Map;for(const e of s.values()){const t=e.ns||c;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||!o.extract.output.includes("{{namespace}}")){const t={},a=r.getOutputPath(o.extract.output,s),c=e.resolve(process.cwd(),a),u=await r.loadTranslationFile(c)||{},y=new Set([...p.keys(),...Object.keys(u)]);for(const e of y){const r=p.get(e)||[],a=u[e]||{};t[e]=l(r,a,o,s,e,d,n,i)}const h=JSON.stringify(u,null,f),m=JSON.stringify(t,null,f);g.push({path:c,updated:m!==h,newTranslations:t,existingTranslations:u})}else{const a=new Set(p.keys()),c=r.getOutputPath(o.extract.output,s,"*"),u=await t.glob(c,{ignore:y});for(const t of u)a.add(e.basename(t,e.extname(t)));for(const t of a){const a=p.get(t)||[],c=r.getOutputPath(o.extract.output,s,t),u=e.resolve(process.cwd(),c),y=await r.loadTranslationFile(u)||{},h=l(a,y,o,s,t,d,n,i),m=JSON.stringify(y,null,f),O=JSON.stringify(h,null,f);g.push({path:u,updated:O!==m,newTranslations:h,existingTranslations:y})}}}return g};
@@ -1 +1 @@
1
- "use strict";var e=require("./ast-utils.js");exports.CallExpressionHandler=class{pluginContext;config;logger;expressionResolver;objectKeys=new Set;constructor(e,t,s,n){this.config=e,this.pluginContext=t,this.logger=s,this.expressionResolver=n}handleCallExpression(t,s){const n=this.getFunctionName(t.callee);if(!n)return;const r=s(n),o=this.config.extract.functions||["t","*.t"];let i=void 0!==r;if(!i)for(const e of o)if(e.startsWith("*.")){if(n.endsWith(e.substring(1))){i=!0;break}}else if(e===n){i=!0;break}if(!i||0===t.arguments.length)return;const{keysToProcess:l,isSelectorAPI:a}=this.handleCallExpressionArgument(t,0);if(0===l.length)return;let u=!1;const c=this.config.extract.pluralSeparator??"_";for(let e=0;e<l.length;e++)l[e].endsWith(`${c}ordinal`)&&(u=!0,l[e]=l[e].slice(0,-8));let f,p;if(t.arguments.length>1){const e=t.arguments[1].expression;"ObjectExpression"===e.type?p=e:"StringLiteral"===e.type&&(f=e.value)}if(t.arguments.length>2){const e=t.arguments[2].expression;"ObjectExpression"===e.type&&(p=e)}const g=p?e.getObjectPropValue(p,"defaultValue"):void 0,h="string"==typeof g?g:f;for(let t=0;t<l.length;t++){let s,n=l[t];if(p){const t=e.getObjectPropValue(p,"ns");"string"==typeof t&&(s=t)}const o=this.config.extract.nsSeparator??":";if(!s&&o&&n.includes(o)){const e=n.split(o);if(s=e.shift(),n=e.join(o),!n||""===n.trim()){this.logger.warn(`Skipping key that became empty after namespace removal: '${s}${o}'`);continue}}!s&&r?.defaultNs&&(s=r.defaultNs),s||(s=this.config.extract.defaultNS);let i=n;if(r?.keyPrefix){const e=this.config.extract.keySeparator??".";if(i=!1!==e?r.keyPrefix.endsWith(e)?`${r.keyPrefix}${n}`:`${r.keyPrefix}${e}${n}`:`${r.keyPrefix}${n}`,!1!==e){if(i.split(e).some(e=>""===e.trim())){this.logger.warn(`Skipping key with empty segments: '${i}' (keyPrefix: '${r.keyPrefix}', key: '${n}')`);continue}}}const c=t===l.length-1&&h||n;if(p){const t=e.getObjectProperty(p,"context"),n=[];if("StringLiteral"===t?.value?.type||"NumericLiteral"===t?.value.type||"BooleanLiteral"===t?.value.type){const e=`${t.value.value}`,r=this.config.extract.contextSeparator??"_";""!==e&&n.push({key:`${i}${r}${e}`,ns:s,defaultValue:c})}else if(t?.value){const e=this.expressionResolver.resolvePossibleContextStringValues(t.value),r=this.config.extract.contextSeparator??"_";e.length>0&&(e.forEach(e=>{n.push({key:`${i}${r}${e}`,ns:s,defaultValue:c})}),n.push({key:i,ns:s,defaultValue:c}))}const r=void 0!==e.getObjectPropValue(p,"count"),o=!0===e.getObjectPropValue(p,"ordinal");if(r||u){this.config.extract.disablePlurals?n.length>0?n.forEach(this.pluginContext.addKey):this.pluginContext.addKey({key:i,ns:s,defaultValue:c}):this.handlePluralKeys(i,s,p,o||u,h);continue}if(n.length>0){n.forEach(this.pluginContext.addKey);continue}!0===e.getObjectPropValue(p,"returnObjects")&&this.objectKeys.add(i)}a&&this.objectKeys.add(i),this.pluginContext.addKey({key:i,ns:s,defaultValue:c})}}handleCallExpressionArgument(e,t){const s=e.arguments[t].expression,n=[];let r=!1;if("ArrowFunctionExpression"===s.type){const e=this.extractKeyFromSelector(s);e&&(n.push(e),r=!0)}else if("ArrayExpression"===s.type)for(const e of s.elements)e?.expression&&n.push(...this.expressionResolver.resolvePossibleKeyStringValues(e.expression));else n.push(...this.expressionResolver.resolvePossibleKeyStringValues(s));return{keysToProcess:n.filter(e=>!!e),isSelectorAPI:r}}extractKeyFromSelector(e){let t=e.body;if("BlockStatement"===t.type){const e=t.stmts.find(e=>"ReturnStatement"===e.type);if("ReturnStatement"!==e?.type||!e.argument)return null;t=e.argument}let s=t;const n=[];for(;"MemberExpression"===s.type;){const e=s.property;if("Identifier"===e.type)n.unshift(e.value);else{if("Computed"!==e.type||"StringLiteral"!==e.expression.type)return null;n.unshift(e.expression.value)}s=s.object}if(n.length>0){const e=this.config.extract.keySeparator,t="string"==typeof e?e:".";return n.join(t)}return null}handlePluralKeys(t,s,n,r,o){try{const i=r?"ordinal":"cardinal",l=new Set;for(const e of this.config.locales)try{const t=new Intl.PluralRules(e,{type:i});t.resolvedOptions().pluralCategories.forEach(e=>l.add(e))}catch(e){const t=new Intl.PluralRules("en",{type:i});t.resolvedOptions().pluralCategories.forEach(e=>l.add(e))}const a=Array.from(l).sort(),u=this.config.extract.pluralSeparator??"_",c=e.getObjectPropValue(n,"defaultValue"),f=e.getObjectPropValue(n,`defaultValue${u}other`),p=e.getObjectPropValue(n,`defaultValue${u}ordinal${u}other`),g=e.getObjectPropValue(n,"count");let h;if("number"==typeof g)try{const e=this.config.extract?.primaryLanguage||this.config.locales[0]||"en";h=new Intl.PluralRules(e,{type:i}).select(g)}catch(e){}const y=e.getObjectProperty(n,"context"),d=[];if(y?.value){const e=this.expressionResolver.resolvePossibleContextStringValues(y.value);if(e.length>0){for(const s of e)s.length>0&&d.push({key:t,context:s});!1!==this.config.extract?.generateBasePluralForms&&d.push({key:t})}else d.push({key:t})}else d.push({key:t});for(const{key:t,context:i}of d)for(const l of a){const a=r?`defaultValue${u}ordinal${u}${l}`:`defaultValue${u}${l}`,g=e.getObjectPropValue(n,a);let y,d;if(y="string"==typeof g?g:"one"===l&&"string"==typeof c?c:r&&"string"==typeof p?p:r||"string"!=typeof f?"string"==typeof c?c:o&&h===l?o:t:f,i){const e=this.config.extract.contextSeparator??"_";d=r?`${t}${e}${i}${u}ordinal${u}${l}`:`${t}${e}${i}${u}${l}`}else d=r?`${t}${u}ordinal${u}${l}`:`${t}${u}${l}`;this.pluginContext.addKey({key:d,ns:s,defaultValue:y,hasCount:!0,isOrdinal:r})}}catch(r){this.logger.warn(`Could not determine plural rules for language "${this.config.extract?.primaryLanguage}". Falling back to simple key extraction.`);const i=o||e.getObjectPropValue(n,"defaultValue");this.pluginContext.addKey({key:t,ns:s,defaultValue:"string"==typeof i?i:t})}}getFunctionName(e){if("Identifier"===e.type)return e.value;if("MemberExpression"===e.type){const t=[];let s=e;for(;"MemberExpression"===s.type;){if("Identifier"!==s.property.type)return null;t.unshift(s.property.value),s=s.object}if("ThisExpression"===s.type)t.unshift("this");else{if("Identifier"!==s.type)return null;t.unshift(s.value)}return t.join(".")}return null}};
1
+ "use strict";var e=require("./ast-utils.js");exports.CallExpressionHandler=class{pluginContext;config;logger;expressionResolver;objectKeys=new Set;constructor(e,t,s,n){this.config=e,this.pluginContext=t,this.logger=s,this.expressionResolver=n}handleCallExpression(t,s){const n=this.getFunctionName(t.callee);if(!n)return;const r=s(n),o=this.config.extract.functions||["t","*.t"];let i=void 0!==r;if(!i)for(const e of o)if(e.startsWith("*.")){if(n.endsWith(e.substring(1))){i=!0;break}}else if(e===n){i=!0;break}if(!i||0===t.arguments.length)return;const{keysToProcess:l,isSelectorAPI:a}=this.handleCallExpressionArgument(t,0);if(0===l.length)return;let u=!1;const c=this.config.extract.pluralSeparator??"_";for(let e=0;e<l.length;e++)l[e].endsWith(`${c}ordinal`)&&(u=!0,l[e]=l[e].slice(0,-8));let f,p;if(t.arguments.length>1){const e=t.arguments[1].expression;"ObjectExpression"===e.type?p=e:"StringLiteral"===e.type&&(f=e.value)}if(t.arguments.length>2){const e=t.arguments[2].expression;"ObjectExpression"===e.type&&(p=e)}const g=p?e.getObjectPropValue(p,"defaultValue"):void 0,h="string"==typeof g?g:f;for(let t=0;t<l.length;t++){let s,n=l[t];if(p){const t=e.getObjectPropValue(p,"ns");"string"==typeof t&&(s=t)}const o=this.config.extract.nsSeparator??":";if(!s&&o&&n.includes(o)){const e=n.split(o);if(s=e.shift(),n=e.join(o),!n||""===n.trim()){this.logger.warn(`Skipping key that became empty after namespace removal: '${s}${o}'`);continue}}!s&&r?.defaultNs&&(s=r.defaultNs),s||(s=this.config.extract.defaultNS);let i=n;if(r?.keyPrefix){const e=this.config.extract.keySeparator??".";if(i=!1!==e?r.keyPrefix.endsWith(e)?`${r.keyPrefix}${n}`:`${r.keyPrefix}${e}${n}`:`${r.keyPrefix}${n}`,!1!==e){if(i.split(e).some(e=>""===e.trim())){this.logger.warn(`Skipping key with empty segments: '${i}' (keyPrefix: '${r.keyPrefix}', key: '${n}')`);continue}}}const c=t===l.length-1&&h||n;if(p){const t=e.getObjectProperty(p,"context"),n=[];if("StringLiteral"===t?.value?.type||"NumericLiteral"===t?.value.type||"BooleanLiteral"===t?.value.type){const e=`${t.value.value}`,r=this.config.extract.contextSeparator??"_";""!==e&&n.push({key:`${i}${r}${e}`,ns:s,defaultValue:c})}else if(t?.value){const e=this.expressionResolver.resolvePossibleContextStringValues(t.value),r=this.config.extract.contextSeparator??"_";e.length>0&&(e.forEach(e=>{n.push({key:`${i}${r}${e}`,ns:s,defaultValue:c})}),n.push({key:i,ns:s,defaultValue:c}))}const r=void 0!==e.getObjectPropValue(p,"count"),o=!0===e.getObjectPropValue(p,"ordinal");if(r||u){this.config.extract.disablePlurals?n.length>0?n.forEach(this.pluginContext.addKey):this.pluginContext.addKey({key:i,ns:s,defaultValue:c}):this.handlePluralKeys(i,s,p,o||u,h);continue}if(n.length>0){n.forEach(this.pluginContext.addKey);continue}!0===e.getObjectPropValue(p,"returnObjects")&&this.objectKeys.add(i)}a&&this.objectKeys.add(i),this.pluginContext.addKey({key:i,ns:s,defaultValue:c})}}handleCallExpressionArgument(e,t){const s=e.arguments[t].expression,n=[];let r=!1;if("ArrowFunctionExpression"===s.type){const e=this.extractKeyFromSelector(s);e&&(n.push(e),r=!0)}else if("ArrayExpression"===s.type)for(const e of s.elements)e?.expression&&n.push(...this.expressionResolver.resolvePossibleKeyStringValues(e.expression));else n.push(...this.expressionResolver.resolvePossibleKeyStringValues(s));return{keysToProcess:n.filter(e=>!!e),isSelectorAPI:r}}extractKeyFromSelector(e){let t=e.body;if("BlockStatement"===t.type){const e=t.stmts.find(e=>"ReturnStatement"===e.type);if("ReturnStatement"!==e?.type||!e.argument)return null;t=e.argument}let s=t;const n=[];for(;"MemberExpression"===s.type;){const e=s.property;if("Identifier"===e.type)n.unshift(e.value);else{if("Computed"!==e.type||"StringLiteral"!==e.expression.type)return null;n.unshift(e.expression.value)}s=s.object}if(n.length>0){const e=this.config.extract.keySeparator,t="string"==typeof e?e:".";return n.join(t)}return null}handlePluralKeys(t,s,n,r,o){try{const i=r?"ordinal":"cardinal",l=new Set;for(const e of this.config.locales)try{const t=new Intl.PluralRules(e,{type:i});t.resolvedOptions().pluralCategories.forEach(e=>l.add(e))}catch(e){const t=new Intl.PluralRules("en",{type:i});t.resolvedOptions().pluralCategories.forEach(e=>l.add(e))}const a=Array.from(l).sort(),u=this.config.extract.pluralSeparator??"_",c=e.getObjectPropValue(n,"defaultValue"),f=e.getObjectPropValue(n,`defaultValue${u}other`),p=e.getObjectPropValue(n,`defaultValue${u}ordinal${u}other`),g=e.getObjectProperty(n,"context"),h=[];if(g?.value){const e=this.expressionResolver.resolvePossibleContextStringValues(g.value);if(e.length>0)if("StringLiteral"===g.value.type)for(const s of e)s.length>0&&h.push({key:t,context:s});else{for(const s of e)s.length>0&&h.push({key:t,context:s});!1!==this.config.extract?.generateBasePluralForms&&h.push({key:t})}else h.push({key:t})}else h.push({key:t});for(const{key:t,context:i}of h)for(const l of a){const a=r?`defaultValue${u}ordinal${u}${l}`:`defaultValue${u}${l}`,g=e.getObjectPropValue(n,a);let h,y;if(h="string"==typeof g?g:"one"===l&&"string"==typeof c?c:"one"===l&&"string"==typeof o?o:r&&"string"==typeof p?p:r||"string"!=typeof f?"string"==typeof c?c:"string"==typeof o?o:t:f,i){const e=this.config.extract.contextSeparator??"_";y=r?`${t}${e}${i}${u}ordinal${u}${l}`:`${t}${e}${i}${u}${l}`}else y=r?`${t}${u}ordinal${u}${l}`:`${t}${u}${l}`;this.pluginContext.addKey({key:y,ns:s,defaultValue:h,hasCount:!0,isOrdinal:r})}}catch(r){this.logger.warn(`Could not determine plural rules for language "${this.config.extract?.primaryLanguage}". Falling back to simple key extraction.`);const i=o||e.getObjectPropValue(n,"defaultValue");this.pluginContext.addKey({key:t,ns:s,defaultValue:"string"==typeof i?i:t})}}getFunctionName(e){if("Identifier"===e.type)return e.value;if("MemberExpression"===e.type){const t=[];let s=e;for(;"MemberExpression"===s.type;){if("Identifier"!==s.property.type)return null;t.unshift(s.property.value),s=s.object}if("ThisExpression"===s.type)t.unshift("this");else{if("Identifier"!==s.type)return null;t.unshift(s.value)}return t.join(".")}return null}};
package/dist/esm/cli.js CHANGED
@@ -1,2 +1,2 @@
1
1
  #!/usr/bin/env node
2
- import{Command as t}from"commander";import e from"chokidar";import{glob as o}from"glob";import i from"chalk";import{ensureConfig as n,loadConfig as a}from"./config.js";import{detectConfig as r}from"./heuristic-config.js";import{runExtractor as c}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 p}from"./migrator.js";import{runInit as m}from"./init.js";import{runLinter as d}from"./linter.js";import{runStatus as u}from"./status.js";import{runLocizeSync as f,runLocizeDownload as g,runLocizeMigrate as y}from"./locize.js";const h=new t;h.name("i18next-cli").description("A unified, high-performance i18next CLI.").version("1.11.1"),h.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=await n(),o=await c(e,{isWatchMode:!!t.watch,isDryRun:!!t.dryRun,syncPrimaryWithDefaults:!!t.syncPrimary});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))}catch(t){console.error("Error running extractor:",t),process.exit(1)}}),h.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,e)=>{let o=await a();if(!o){console.log(i.blue("No config file found. Attempting to detect project structure..."));const t=await r();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!")),o=t}await u(o,{detail:t,namespace:e.namespace})}),h.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 i=await n(),a=()=>s(i);if(await a(),t.watch){console.log("\nWatching for changes...");e.watch(await o(i.types?.input||[]),{persistent:!0}).on("change",t=>{console.log(`\nFile changed: ${t}`),a()})}}),h.command("sync").description("Synchronize secondary language files with the primary language file.").action(async()=>{const t=await n();await l(t)}),h.command("migrate-config [configPath]").description("Migrate a legacy i18next-parser.config.js to the new format.").action(async t=>{await p(t)}),h.command("init").description("Create a new i18next.config.ts/js file with an interactive setup wizard.").action(m),h.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 n=async()=>{let t=await a();if(!t){console.log(i.blue("No config file found. Attempting to detect project structure..."));const e=await r();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!")),t=e}await d(t)};if(await n(),t.watch){console.log("\nWatching for changes...");const t=await a();if(t?.extract?.input){e.watch(await o(t.extract.input),{ignored:/node_modules/,persistent:!0}).on("change",t=>{console.log(`\nFile changed: ${t}`),n()})}}}),h.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 e=await n();await f(e,t)}),h.command("locize-download").description("Download all translations from your locize project.").action(async t=>{const e=await n();await g(e,t)}),h.command("locize-migrate").description("Migrate local translation files to a new locize project.").action(async t=>{const e=await n();await y(e,t)}),h.parse(process.argv);
2
+ import{Command as t}from"commander";import e from"chokidar";import{glob as o}from"glob";import i from"chalk";import{ensureConfig as n,loadConfig as a}from"./config.js";import{detectConfig as r}from"./heuristic-config.js";import{runExtractor as c}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 p}from"./migrator.js";import{runInit as m}from"./init.js";import{runLinter as d}from"./linter.js";import{runStatus as u}from"./status.js";import{runLocizeSync as f,runLocizeDownload as g,runLocizeMigrate as y}from"./locize.js";const h=new t;h.name("i18next-cli").description("A unified, high-performance i18next CLI.").version("1.11.2"),h.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=await n(),o=await c(e,{isWatchMode:!!t.watch,isDryRun:!!t.dryRun,syncPrimaryWithDefaults:!!t.syncPrimary});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))}catch(t){console.error("Error running extractor:",t),process.exit(1)}}),h.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,e)=>{let o=await a();if(!o){console.log(i.blue("No config file found. Attempting to detect project structure..."));const t=await r();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!")),o=t}await u(o,{detail:t,namespace:e.namespace})}),h.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 i=await n(),a=()=>s(i);if(await a(),t.watch){console.log("\nWatching for changes...");e.watch(await o(i.types?.input||[]),{persistent:!0}).on("change",t=>{console.log(`\nFile changed: ${t}`),a()})}}),h.command("sync").description("Synchronize secondary language files with the primary language file.").action(async()=>{const t=await n();await l(t)}),h.command("migrate-config [configPath]").description("Migrate a legacy i18next-parser.config.js to the new format.").action(async t=>{await p(t)}),h.command("init").description("Create a new i18next.config.ts/js file with an interactive setup wizard.").action(m),h.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 n=async()=>{let t=await a();if(!t){console.log(i.blue("No config file found. Attempting to detect project structure..."));const e=await r();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!")),t=e}await d(t)};if(await n(),t.watch){console.log("\nWatching for changes...");const t=await a();if(t?.extract?.input){e.watch(await o(t.extract.input),{ignored:/node_modules/,persistent:!0}).on("change",t=>{console.log(`\nFile changed: ${t}`),n()})}}}),h.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 e=await n();await f(e,t)}),h.command("locize-download").description("Download all translations from your locize project.").action(async t=>{const e=await n();await g(e,t)}),h.command("locize-migrate").description("Migrate local translation files to a new locize project.").action(async t=>{const e=await n();await y(e,t)}),h.parse(process.argv);
@@ -1 +1 @@
1
- import{resolve as t,basename as e,extname as r}from"node:path";import{glob as o}from"glob";import{getNestedKeys as n,getNestedValue as s,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 r={},o=e?.extract?.pluralSeparator??"_",n=["zero","one","two","few","many","other"],s=n.map(t=>`ordinal_${t}`),a=Object.keys(t).sort((t,e)=>{const r=t=>{for(const e of s)if(t.endsWith(`${o}${e}`)){return{base:t.slice(0,-(o.length+e.length)),form:e,isOrdinal:!0,isPlural:!0,fullKey:t}}for(const e of n)if(t.endsWith(`${o}${e}`)){return{base:t.slice(0,-(o.length+e.length)),form:e,isOrdinal:!1,isPlural:!0,fullKey:t}}return{base:t,form:"",isOrdinal:!1,isPlural:!1,fullKey:t}},a=r(t),i=r(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?s:n,r=e.indexOf(a.form),o=e.indexOf(i.form);return-1!==r&&-1!==o?r-o: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 o of a)r[o]=u(t[o],e);return r}function p(t,e,r,o,i,l,f,p=!1){const{keySeparator:d=".",sort:y=!0,removeUnusedKeys:g=!0,primaryLanguage:m,defaultValue:h="",pluralSeparator:x="_"}=r.extract,O=new Set;try{const t=new Intl.PluralRules(o,{type:"cardinal"}),e=new Intl.PluralRules(o,{type:"ordinal"});t.resolvedOptions().pluralCategories.forEach(t=>O.add(t)),e.resolvedOptions().pluralCategories.forEach(t=>O.add(`ordinal_${t}`))}catch(t){const e=new Intl.PluralRules(m||"en",{type:"cardinal"}),r=new Intl.PluralRules(m||"en",{type:"ordinal"});e.resolvedOptions().pluralCategories.forEach(t=>O.add(t)),r.resolvedOptions().pluralCategories.forEach(t=>O.add(`ordinal_${t}`))}const w=t.filter(({key:t,hasCount:e,isOrdinal:r})=>{if(l.some(e=>e.test(t)))return!1;if(!e)return!0;const o=t.split(x);if(r&&o.includes("ordinal")){const t=o[o.length-1];return O.has(`ordinal_${t}`)}if(e){const t=o[o.length-1];return O.has(t)}return!0});let v=g?{}:JSON.parse(JSON.stringify(e));const b=n(e,d??".");for(const t of b)if(l.some(e=>e.test(t))){const r=s(e,t,d??".");a(v,t,r,d??".")}if(g){const t=n(e,d??".");for(const r of t){const t=r.split(x);if("zero"===t[t.length-1]){const o=t.slice(0,-1).join(x);if(w.some(({key:t})=>t.split(x).slice(0,-1).join(x)===o)){const t=s(e,r,d??".");a(v,r,t,d??".")}}}}for(const{key:t,defaultValue:r}of w){const n=s(e,t,d??"."),l=!w.some(e=>e.key.startsWith(`${t}${d}`)&&e.key!==t),u="object"==typeof n&&null!==n&&(f.has(t)||!r||r===t),y="object"==typeof n&&null!==n&&l&&!f.has(t)&&!u;if(u){a(v,t,n,d??".");continue}let g;g=void 0===n||y?o===m?r||t:c(h,t,i,o):o===m&&p&&r&&r!==t?r:n,a(v,t,g,d??".")}if(!0===y)return u(v,r);if("function"==typeof y){const t={},e=Object.keys(v),o=new Map;for(const t of w){const e=!1===d?t.key:t.key.split(d)[0];o.has(e)||o.set(e,t)}e.sort((t,e)=>{if("function"==typeof y){const r=o.get(t),n=o.get(e);if(r&&n)return y(r,n)}return t.localeCompare(e,void 0,{sensitivity:"base"})});for(const o of e)t[o]=u(v[o],r);v=t}return v}async function d(n,s,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.defaultNS??"translation",d=[...a.extract.preservePatterns||[]],y=a.extract.indentation??2;for(const t of s)d.push(`${t}.*`);const g=d.map(f),m=new Map;for(const t of n.values()){const e=t.ns||u;m.has(e)||m.set(e,[]),m.get(e).push(t)}const h=[],x=Array.isArray(a.extract.ignore)?a.extract.ignore:a.extract.ignore?[a.extract.ignore]:[];for(const n of a.locales){if(a.extract.mergeNamespaces||!a.extract.output.includes("{{namespace}}")){const e={},r=i(a.extract.output,n),o=t(process.cwd(),r),f=await l(o)||{},u=new Set([...m.keys(),...Object.keys(f)]);for(const t of u){const r=m.get(t)||[],o=f[t]||{};e[t]=p(r,o,a,n,t,g,s,c)}const d=JSON.stringify(f,null,y),x=JSON.stringify(e,null,y);h.push({path:o,updated:x!==d,newTranslations:e,existingTranslations:f})}else{const f=new Set(m.keys()),u=i(a.extract.output,n,"*"),d=await o(u,{ignore:x});for(const t of d)f.add(e(t,r(t)));for(const e of f){const r=m.get(e)||[],o=i(a.extract.output,n,e),f=t(process.cwd(),o),u=await l(f)||{},d=p(r,u,a,n,e,g,s,c),x=JSON.stringify(u,null,y),O=JSON.stringify(d,null,y);h.push({path:f,updated:O!==x,newTranslations:d,existingTranslations:u})}}}return h}export{d as getTranslations};
1
+ import{resolve as t,basename as e,extname as r}from"node:path";import{glob as s}from"glob";import{getNestedKeys as o,getNestedValue as n,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 r={},s=e?.extract?.pluralSeparator??"_",o=["zero","one","two","few","many","other"],n=o.map(t=>`ordinal${s}${t}`),a=Object.keys(t).sort((t,e)=>{const r=t=>{for(const e of n)if(t.endsWith(`${s}${e}`)){return{base:t.slice(0,-(s.length+e.length)),form:e,isOrdinal:!0,isPlural:!0,fullKey:t}}for(const e of o)if(t.endsWith(`${s}${e}`)){return{base:t.slice(0,-(s.length+e.length)),form:e,isOrdinal:!1,isPlural:!0,fullKey:t}}return{base:t,form:"",isOrdinal:!1,isPlural:!1,fullKey:t}},a=r(t),i=r(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?n:o,r=e.indexOf(a.form),s=e.indexOf(i.form);return-1!==r&&-1!==s?r-s: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 s of a)r[s]=u(t[s],e);return r}function p(t,e,r,s,i,l,f,p=!1){const{keySeparator:d=".",sort:y=!0,removeUnusedKeys:g=!0,primaryLanguage:m,defaultValue:h="",pluralSeparator:x="_",contextSeparator:O="_"}=r.extract,w=new Set;try{const t=new Intl.PluralRules(s,{type:"cardinal"}),e=new Intl.PluralRules(s,{type:"ordinal"});t.resolvedOptions().pluralCategories.forEach(t=>w.add(t)),e.resolvedOptions().pluralCategories.forEach(t=>w.add(`ordinal_${t}`))}catch(t){const e=new Intl.PluralRules(m||"en",{type:"cardinal"}),r=new Intl.PluralRules(m||"en",{type:"ordinal"});e.resolvedOptions().pluralCategories.forEach(t=>w.add(t)),r.resolvedOptions().pluralCategories.forEach(t=>w.add(`ordinal_${t}`))}const v=t.filter(({key:t,hasCount:e,isOrdinal:r})=>{if(l.some(e=>e.test(t)))return!1;if(!e)return!0;const s=t.split(x);if(r&&s.includes("ordinal")){const t=s[s.length-1];return w.has(`ordinal_${t}`)}if(e){const t=s[s.length-1];return w.has(t)}return!0});let b=g?{}:JSON.parse(JSON.stringify(e));const $=o(e,d??".");for(const t of $)if(l.some(e=>e.test(t))){const r=n(e,t,d??".");a(b,t,r,d??".")}if(g){const t=o(e,d??".");for(const r of t){const t=r.split(x);if("zero"===t[t.length-1]){const s=t.slice(0,-1).join(x);if(v.some(({key:t})=>t.split(x).slice(0,-1).join(x)===s)){const t=n(e,r,d??".");a(b,r,t,d??".")}}}}for(const{key:t,defaultValue:r}of v){const o=n(e,t,d??"."),l=!v.some(e=>e.key.startsWith(`${t}${d}`)&&e.key!==t),u="object"==typeof o&&null!==o&&(f.has(t)||!r||r===t),y="object"==typeof o&&null!==o&&l&&!f.has(t)&&!u;if(u){a(b,t,o,d??".");continue}let g;if(void 0===o||y)if(s===m)if(p){const e=r&&(r===t||t!==r&&(t.startsWith(r+x)||t.startsWith(r+O)));g=r&&!e?r:c(h,t,i,s)}else g=r||t;else g=c(h,t,i,s);else if(s===m&&p){const e=r&&(r===t||t!==r&&(t.startsWith(r+x)||t.startsWith(r+O)));g=r&&!e?r:o}else g=o;a(b,t,g,d??".")}if(!0===y)return u(b,r);if("function"==typeof y){const t={},e=Object.keys(b),s=new Map;for(const t of v){const e=!1===d?t.key:t.key.split(d)[0];s.has(e)||s.set(e,t)}e.sort((t,e)=>{if("function"==typeof y){const r=s.get(t),o=s.get(e);if(r&&o)return y(r,o)}return t.localeCompare(e,void 0,{sensitivity:"base"})});for(const s of e)t[s]=u(b[s],r);b=t}return b}async function d(o,n,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.defaultNS??"translation",d=[...a.extract.preservePatterns||[]],y=a.extract.indentation??2;for(const t of n)d.push(`${t}.*`);const g=d.map(f),m=new Map;for(const t of o.values()){const e=t.ns||u;m.has(e)||m.set(e,[]),m.get(e).push(t)}const h=[],x=Array.isArray(a.extract.ignore)?a.extract.ignore:a.extract.ignore?[a.extract.ignore]:[];for(const o of a.locales){if(a.extract.mergeNamespaces||!a.extract.output.includes("{{namespace}}")){const e={},r=i(a.extract.output,o),s=t(process.cwd(),r),f=await l(s)||{},u=new Set([...m.keys(),...Object.keys(f)]);for(const t of u){const r=m.get(t)||[],s=f[t]||{};e[t]=p(r,s,a,o,t,g,n,c)}const d=JSON.stringify(f,null,y),x=JSON.stringify(e,null,y);h.push({path:s,updated:x!==d,newTranslations:e,existingTranslations:f})}else{const f=new Set(m.keys()),u=i(a.extract.output,o,"*"),d=await s(u,{ignore:x});for(const t of d)f.add(e(t,r(t)));for(const e of f){const r=m.get(e)||[],s=i(a.extract.output,o,e),f=t(process.cwd(),s),u=await l(f)||{},d=p(r,u,a,o,e,g,n,c),x=JSON.stringify(u,null,y),O=JSON.stringify(d,null,y);h.push({path:f,updated:O!==x,newTranslations:d,existingTranslations:u})}}}return h}export{d as getTranslations};
@@ -1 +1 @@
1
- import{getObjectPropValue as e,getObjectProperty as t}from"./ast-utils.js";class n{pluginContext;config;logger;expressionResolver;objectKeys=new Set;constructor(e,t,n,s){this.config=e,this.pluginContext=t,this.logger=n,this.expressionResolver=s}handleCallExpression(n,s){const r=this.getFunctionName(n.callee);if(!r)return;const i=s(r),o=this.config.extract.functions||["t","*.t"];let l=void 0!==i;if(!l)for(const e of o)if(e.startsWith("*.")){if(r.endsWith(e.substring(1))){l=!0;break}}else if(e===r){l=!0;break}if(!l||0===n.arguments.length)return;const{keysToProcess:a,isSelectorAPI:u}=this.handleCallExpressionArgument(n,0);if(0===a.length)return;let c=!1;const f=this.config.extract.pluralSeparator??"_";for(let e=0;e<a.length;e++)a[e].endsWith(`${f}ordinal`)&&(c=!0,a[e]=a[e].slice(0,-8));let p,h;if(n.arguments.length>1){const e=n.arguments[1].expression;"ObjectExpression"===e.type?h=e:"StringLiteral"===e.type&&(p=e.value)}if(n.arguments.length>2){const e=n.arguments[2].expression;"ObjectExpression"===e.type&&(h=e)}const g=h?e(h,"defaultValue"):void 0,y="string"==typeof g?g:p;for(let n=0;n<a.length;n++){let s,r=a[n];if(h){const t=e(h,"ns");"string"==typeof t&&(s=t)}const o=this.config.extract.nsSeparator??":";if(!s&&o&&r.includes(o)){const e=r.split(o);if(s=e.shift(),r=e.join(o),!r||""===r.trim()){this.logger.warn(`Skipping key that became empty after namespace removal: '${s}${o}'`);continue}}!s&&i?.defaultNs&&(s=i.defaultNs),s||(s=this.config.extract.defaultNS);let l=r;if(i?.keyPrefix){const e=this.config.extract.keySeparator??".";if(l=!1!==e?i.keyPrefix.endsWith(e)?`${i.keyPrefix}${r}`:`${i.keyPrefix}${e}${r}`:`${i.keyPrefix}${r}`,!1!==e){if(l.split(e).some(e=>""===e.trim())){this.logger.warn(`Skipping key with empty segments: '${l}' (keyPrefix: '${i.keyPrefix}', key: '${r}')`);continue}}}const f=n===a.length-1&&y||r;if(h){const n=t(h,"context"),r=[];if("StringLiteral"===n?.value?.type||"NumericLiteral"===n?.value.type||"BooleanLiteral"===n?.value.type){const e=`${n.value.value}`,t=this.config.extract.contextSeparator??"_";""!==e&&r.push({key:`${l}${t}${e}`,ns:s,defaultValue:f})}else if(n?.value){const e=this.expressionResolver.resolvePossibleContextStringValues(n.value),t=this.config.extract.contextSeparator??"_";e.length>0&&(e.forEach(e=>{r.push({key:`${l}${t}${e}`,ns:s,defaultValue:f})}),r.push({key:l,ns:s,defaultValue:f}))}const i=void 0!==e(h,"count"),o=!0===e(h,"ordinal");if(i||c){this.config.extract.disablePlurals?r.length>0?r.forEach(this.pluginContext.addKey):this.pluginContext.addKey({key:l,ns:s,defaultValue:f}):this.handlePluralKeys(l,s,h,o||c,y);continue}if(r.length>0){r.forEach(this.pluginContext.addKey);continue}!0===e(h,"returnObjects")&&this.objectKeys.add(l)}u&&this.objectKeys.add(l),this.pluginContext.addKey({key:l,ns:s,defaultValue:f})}}handleCallExpressionArgument(e,t){const n=e.arguments[t].expression,s=[];let r=!1;if("ArrowFunctionExpression"===n.type){const e=this.extractKeyFromSelector(n);e&&(s.push(e),r=!0)}else if("ArrayExpression"===n.type)for(const e of n.elements)e?.expression&&s.push(...this.expressionResolver.resolvePossibleKeyStringValues(e.expression));else s.push(...this.expressionResolver.resolvePossibleKeyStringValues(n));return{keysToProcess:s.filter(e=>!!e),isSelectorAPI:r}}extractKeyFromSelector(e){let t=e.body;if("BlockStatement"===t.type){const e=t.stmts.find(e=>"ReturnStatement"===e.type);if("ReturnStatement"!==e?.type||!e.argument)return null;t=e.argument}let n=t;const s=[];for(;"MemberExpression"===n.type;){const e=n.property;if("Identifier"===e.type)s.unshift(e.value);else{if("Computed"!==e.type||"StringLiteral"!==e.expression.type)return null;s.unshift(e.expression.value)}n=n.object}if(s.length>0){const e=this.config.extract.keySeparator,t="string"==typeof e?e:".";return s.join(t)}return null}handlePluralKeys(n,s,r,i,o){try{const l=i?"ordinal":"cardinal",a=new Set;for(const e of this.config.locales)try{const t=new Intl.PluralRules(e,{type:l});t.resolvedOptions().pluralCategories.forEach(e=>a.add(e))}catch(e){const t=new Intl.PluralRules("en",{type:l});t.resolvedOptions().pluralCategories.forEach(e=>a.add(e))}const u=Array.from(a).sort(),c=this.config.extract.pluralSeparator??"_",f=e(r,"defaultValue"),p=e(r,`defaultValue${c}other`),h=e(r,`defaultValue${c}ordinal${c}other`),g=e(r,"count");let y;if("number"==typeof g)try{const e=this.config.extract?.primaryLanguage||this.config.locales[0]||"en";y=new Intl.PluralRules(e,{type:l}).select(g)}catch(e){}const d=t(r,"context"),x=[];if(d?.value){const e=this.expressionResolver.resolvePossibleContextStringValues(d.value);if(e.length>0){for(const t of e)t.length>0&&x.push({key:n,context:t});!1!==this.config.extract?.generateBasePluralForms&&x.push({key:n})}else x.push({key:n})}else x.push({key:n});for(const{key:t,context:n}of x)for(const l of u){const a=e(r,i?`defaultValue${c}ordinal${c}${l}`:`defaultValue${c}${l}`);let u,g;if(u="string"==typeof a?a:"one"===l&&"string"==typeof f?f:i&&"string"==typeof h?h:i||"string"!=typeof p?"string"==typeof f?f:o&&y===l?o:t:p,n){const e=this.config.extract.contextSeparator??"_";g=i?`${t}${e}${n}${c}ordinal${c}${l}`:`${t}${e}${n}${c}${l}`}else g=i?`${t}${c}ordinal${c}${l}`:`${t}${c}${l}`;this.pluginContext.addKey({key:g,ns:s,defaultValue:u,hasCount:!0,isOrdinal:i})}}catch(t){this.logger.warn(`Could not determine plural rules for language "${this.config.extract?.primaryLanguage}". Falling back to simple key extraction.`);const i=o||e(r,"defaultValue");this.pluginContext.addKey({key:n,ns:s,defaultValue:"string"==typeof i?i:n})}}getFunctionName(e){if("Identifier"===e.type)return e.value;if("MemberExpression"===e.type){const t=[];let n=e;for(;"MemberExpression"===n.type;){if("Identifier"!==n.property.type)return null;t.unshift(n.property.value),n=n.object}if("ThisExpression"===n.type)t.unshift("this");else{if("Identifier"!==n.type)return null;t.unshift(n.value)}return t.join(".")}return null}}export{n as CallExpressionHandler};
1
+ import{getObjectPropValue as e,getObjectProperty as t}from"./ast-utils.js";class n{pluginContext;config;logger;expressionResolver;objectKeys=new Set;constructor(e,t,n,s){this.config=e,this.pluginContext=t,this.logger=n,this.expressionResolver=s}handleCallExpression(n,s){const r=this.getFunctionName(n.callee);if(!r)return;const i=s(r),o=this.config.extract.functions||["t","*.t"];let l=void 0!==i;if(!l)for(const e of o)if(e.startsWith("*.")){if(r.endsWith(e.substring(1))){l=!0;break}}else if(e===r){l=!0;break}if(!l||0===n.arguments.length)return;const{keysToProcess:a,isSelectorAPI:u}=this.handleCallExpressionArgument(n,0);if(0===a.length)return;let f=!1;const c=this.config.extract.pluralSeparator??"_";for(let e=0;e<a.length;e++)a[e].endsWith(`${c}ordinal`)&&(f=!0,a[e]=a[e].slice(0,-8));let p,g;if(n.arguments.length>1){const e=n.arguments[1].expression;"ObjectExpression"===e.type?g=e:"StringLiteral"===e.type&&(p=e.value)}if(n.arguments.length>2){const e=n.arguments[2].expression;"ObjectExpression"===e.type&&(g=e)}const h=g?e(g,"defaultValue"):void 0,y="string"==typeof h?h:p;for(let n=0;n<a.length;n++){let s,r=a[n];if(g){const t=e(g,"ns");"string"==typeof t&&(s=t)}const o=this.config.extract.nsSeparator??":";if(!s&&o&&r.includes(o)){const e=r.split(o);if(s=e.shift(),r=e.join(o),!r||""===r.trim()){this.logger.warn(`Skipping key that became empty after namespace removal: '${s}${o}'`);continue}}!s&&i?.defaultNs&&(s=i.defaultNs),s||(s=this.config.extract.defaultNS);let l=r;if(i?.keyPrefix){const e=this.config.extract.keySeparator??".";if(l=!1!==e?i.keyPrefix.endsWith(e)?`${i.keyPrefix}${r}`:`${i.keyPrefix}${e}${r}`:`${i.keyPrefix}${r}`,!1!==e){if(l.split(e).some(e=>""===e.trim())){this.logger.warn(`Skipping key with empty segments: '${l}' (keyPrefix: '${i.keyPrefix}', key: '${r}')`);continue}}}const c=n===a.length-1&&y||r;if(g){const n=t(g,"context"),r=[];if("StringLiteral"===n?.value?.type||"NumericLiteral"===n?.value.type||"BooleanLiteral"===n?.value.type){const e=`${n.value.value}`,t=this.config.extract.contextSeparator??"_";""!==e&&r.push({key:`${l}${t}${e}`,ns:s,defaultValue:c})}else if(n?.value){const e=this.expressionResolver.resolvePossibleContextStringValues(n.value),t=this.config.extract.contextSeparator??"_";e.length>0&&(e.forEach(e=>{r.push({key:`${l}${t}${e}`,ns:s,defaultValue:c})}),r.push({key:l,ns:s,defaultValue:c}))}const i=void 0!==e(g,"count"),o=!0===e(g,"ordinal");if(i||f){this.config.extract.disablePlurals?r.length>0?r.forEach(this.pluginContext.addKey):this.pluginContext.addKey({key:l,ns:s,defaultValue:c}):this.handlePluralKeys(l,s,g,o||f,y);continue}if(r.length>0){r.forEach(this.pluginContext.addKey);continue}!0===e(g,"returnObjects")&&this.objectKeys.add(l)}u&&this.objectKeys.add(l),this.pluginContext.addKey({key:l,ns:s,defaultValue:c})}}handleCallExpressionArgument(e,t){const n=e.arguments[t].expression,s=[];let r=!1;if("ArrowFunctionExpression"===n.type){const e=this.extractKeyFromSelector(n);e&&(s.push(e),r=!0)}else if("ArrayExpression"===n.type)for(const e of n.elements)e?.expression&&s.push(...this.expressionResolver.resolvePossibleKeyStringValues(e.expression));else s.push(...this.expressionResolver.resolvePossibleKeyStringValues(n));return{keysToProcess:s.filter(e=>!!e),isSelectorAPI:r}}extractKeyFromSelector(e){let t=e.body;if("BlockStatement"===t.type){const e=t.stmts.find(e=>"ReturnStatement"===e.type);if("ReturnStatement"!==e?.type||!e.argument)return null;t=e.argument}let n=t;const s=[];for(;"MemberExpression"===n.type;){const e=n.property;if("Identifier"===e.type)s.unshift(e.value);else{if("Computed"!==e.type||"StringLiteral"!==e.expression.type)return null;s.unshift(e.expression.value)}n=n.object}if(s.length>0){const e=this.config.extract.keySeparator,t="string"==typeof e?e:".";return s.join(t)}return null}handlePluralKeys(n,s,r,i,o){try{const l=i?"ordinal":"cardinal",a=new Set;for(const e of this.config.locales)try{const t=new Intl.PluralRules(e,{type:l});t.resolvedOptions().pluralCategories.forEach(e=>a.add(e))}catch(e){const t=new Intl.PluralRules("en",{type:l});t.resolvedOptions().pluralCategories.forEach(e=>a.add(e))}const u=Array.from(a).sort(),f=this.config.extract.pluralSeparator??"_",c=e(r,"defaultValue"),p=e(r,`defaultValue${f}other`),g=e(r,`defaultValue${f}ordinal${f}other`),h=t(r,"context"),y=[];if(h?.value){const e=this.expressionResolver.resolvePossibleContextStringValues(h.value);if(e.length>0)if("StringLiteral"===h.value.type)for(const t of e)t.length>0&&y.push({key:n,context:t});else{for(const t of e)t.length>0&&y.push({key:n,context:t});!1!==this.config.extract?.generateBasePluralForms&&y.push({key:n})}else y.push({key:n})}else y.push({key:n});for(const{key:t,context:n}of y)for(const l of u){const a=e(r,i?`defaultValue${f}ordinal${f}${l}`:`defaultValue${f}${l}`);let u,h;if(u="string"==typeof a?a:"one"===l&&"string"==typeof c?c:"one"===l&&"string"==typeof o?o:i&&"string"==typeof g?g:i||"string"!=typeof p?"string"==typeof c?c:"string"==typeof o?o:t:p,n){const e=this.config.extract.contextSeparator??"_";h=i?`${t}${e}${n}${f}ordinal${f}${l}`:`${t}${e}${n}${f}${l}`}else h=i?`${t}${f}ordinal${f}${l}`:`${t}${f}${l}`;this.pluginContext.addKey({key:h,ns:s,defaultValue:u,hasCount:!0,isOrdinal:i})}}catch(t){this.logger.warn(`Could not determine plural rules for language "${this.config.extract?.primaryLanguage}". Falling back to simple key extraction.`);const i=o||e(r,"defaultValue");this.pluginContext.addKey({key:n,ns:s,defaultValue:"string"==typeof i?i:n})}}getFunctionName(e){if("Identifier"===e.type)return e.value;if("MemberExpression"===e.type){const t=[];let n=e;for(;"MemberExpression"===n.type;){if("Identifier"!==n.property.type)return null;t.unshift(n.property.value),n=n.object}if("ThisExpression"===n.type)t.unshift("this");else{if("Identifier"!==n.type)return null;t.unshift(n.value)}return t.join(".")}return null}}export{n as CallExpressionHandler};
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "i18next-cli",
3
- "version": "1.11.1",
3
+ "version": "1.11.2",
4
4
  "description": "A unified, high-performance i18next CLI.",
5
5
  "type": "module",
6
6
  "bin": {
package/src/cli.ts CHANGED
@@ -21,7 +21,7 @@ const program = new Command()
21
21
  program
22
22
  .name('i18next-cli')
23
23
  .description('A unified, high-performance i18next CLI.')
24
- .version('1.11.1')
24
+ .version('1.11.2')
25
25
 
26
26
  program
27
27
  .command('extract')
@@ -29,7 +29,7 @@ function sortObject (obj: any, config?: I18nextToolkitConfig): any {
29
29
 
30
30
  // Define the canonical order for plural forms
31
31
  const pluralOrder = ['zero', 'one', 'two', 'few', 'many', 'other']
32
- const ordinalPluralOrder = pluralOrder.map(form => `ordinal_${form}`)
32
+ const ordinalPluralOrder = pluralOrder.map(form => `ordinal${pluralSeparator}${form}`)
33
33
 
34
34
  const keys = Object.keys(obj).sort((a, b) => {
35
35
  // Helper function to extract base key and form info
@@ -118,6 +118,7 @@ function buildNewTranslationsForNs (
118
118
  primaryLanguage,
119
119
  defaultValue: emptyDefaultValue = '',
120
120
  pluralSeparator = '_',
121
+ contextSeparator = '_',
121
122
  } = config.extract
122
123
 
123
124
  // Get the plural categories for the target language
@@ -227,17 +228,45 @@ function buildNewTranslationsForNs (
227
228
 
228
229
  let valueToSet: string
229
230
  if (existingValue === undefined || isStaleObject) {
231
+ // New key or stale object - determine what value to use
230
232
  if (locale === primaryLanguage) {
231
- valueToSet = defaultValue || key
233
+ if (syncPrimaryWithDefaults) {
234
+ // When syncPrimaryWithDefaults is true:
235
+ // - Use defaultValue if it exists and is meaningful (not derived from key pattern)
236
+ // - Otherwise use empty string for new keys
237
+ const isDerivedDefault = defaultValue && (
238
+ defaultValue === key || // Exact match
239
+ // For variant keys (plural/context), check if defaultValue is the base
240
+ (key !== defaultValue &&
241
+ (key.startsWith(defaultValue + pluralSeparator) ||
242
+ key.startsWith(defaultValue + contextSeparator)))
243
+ )
244
+
245
+ valueToSet = (defaultValue && !isDerivedDefault) ? defaultValue : resolveDefaultValue(emptyDefaultValue, key, namespace, locale)
246
+ } else {
247
+ // syncPrimaryWithDefaults is false - use original behavior
248
+ valueToSet = defaultValue || key
249
+ }
232
250
  } else {
233
- // For secondary languages, use the resolved default value
251
+ // For secondary languages, always use empty string
234
252
  valueToSet = resolveDefaultValue(emptyDefaultValue, key, namespace, locale)
235
253
  }
236
254
  } else {
237
- // Check CLI flag for syncing primary language with code defaults
238
- if (locale === primaryLanguage && syncPrimaryWithDefaults && defaultValue && defaultValue !== key) {
239
- valueToSet = defaultValue
255
+ // Existing value exists - decide whether to preserve or sync
256
+ if (locale === primaryLanguage && syncPrimaryWithDefaults) {
257
+ // For primary language with syncPrimaryWithDefaults enabled:
258
+ // Only update if we have a meaningful defaultValue that's not derived from key pattern
259
+ const isDerivedDefault = defaultValue && (
260
+ defaultValue === key || // Exact match
261
+ // For variant keys (plural/context), check if defaultValue is the base
262
+ (key !== defaultValue &&
263
+ (key.startsWith(defaultValue + pluralSeparator) ||
264
+ key.startsWith(defaultValue + contextSeparator)))
265
+ )
266
+
267
+ valueToSet = (defaultValue && !isDerivedDefault) ? defaultValue : existingValue
240
268
  } else {
269
+ // Not primary language or not syncing - always preserve existing
241
270
  valueToSet = existingValue
242
271
  }
243
272
  }
@@ -367,20 +367,6 @@ export class CallExpressionHandler {
367
367
  const otherDefault = getObjectPropValue(options, `defaultValue${pluralSeparator}other`)
368
368
  const ordinalOtherDefault = getObjectPropValue(options, `defaultValue${pluralSeparator}ordinal${pluralSeparator}other`)
369
369
 
370
- // Get the count value and determine target category if available
371
- const countValue = getObjectPropValue(options, 'count')
372
- let targetCategory: string | undefined
373
-
374
- if (typeof countValue === 'number') {
375
- try {
376
- const primaryLanguage = this.config.extract?.primaryLanguage || this.config.locales[0] || 'en'
377
- const pluralRules = new Intl.PluralRules(primaryLanguage, { type })
378
- targetCategory = pluralRules.select(countValue)
379
- } catch (e) {
380
- // If we can't determine the category, continue with normal logic
381
- }
382
- }
383
-
384
370
  // Handle context - both static and dynamic
385
371
  const contextProp = getObjectProperty(options, 'context')
386
372
  const keysToGenerate: Array<{ key: string, context?: string }> = []
@@ -390,17 +376,27 @@ export class CallExpressionHandler {
390
376
  const contextValues = this.expressionResolver.resolvePossibleContextStringValues(contextProp.value)
391
377
 
392
378
  if (contextValues.length > 0) {
393
- // Generate keys for each context value
394
- for (const contextValue of contextValues) {
395
- if (contextValue.length > 0) {
396
- keysToGenerate.push({ key, context: contextValue })
379
+ // For static context (string literal), only generate context variants
380
+ if (contextProp.value.type === 'StringLiteral') {
381
+ // Only generate context-specific plural forms, no base forms
382
+ for (const contextValue of contextValues) {
383
+ if (contextValue.length > 0) {
384
+ keysToGenerate.push({ key, context: contextValue })
385
+ }
386
+ }
387
+ } else {
388
+ // For dynamic context, generate context variants AND base forms
389
+ for (const contextValue of contextValues) {
390
+ if (contextValue.length > 0) {
391
+ keysToGenerate.push({ key, context: contextValue })
392
+ }
397
393
  }
398
- }
399
394
 
400
- // For dynamic context, also generate base plural forms if generateBasePluralForms is not disabled
401
- const shouldGenerateBaseForms = this.config.extract?.generateBasePluralForms !== false
402
- if (shouldGenerateBaseForms) {
403
- keysToGenerate.push({ key })
395
+ // Only generate base plural forms if generateBasePluralForms is not disabled
396
+ const shouldGenerateBaseForms = this.config.extract?.generateBasePluralForms !== false
397
+ if (shouldGenerateBaseForms) {
398
+ keysToGenerate.push({ key })
399
+ }
404
400
  }
405
401
  } else {
406
402
  // Couldn't resolve context, fall back to base key only
@@ -418,21 +414,31 @@ export class CallExpressionHandler {
418
414
  const specificDefaultKey = isOrdinal ? `defaultValue${pluralSeparator}ordinal${pluralSeparator}${category}` : `defaultValue${pluralSeparator}${category}`
419
415
  const specificDefault = getObjectPropValue(options, specificDefaultKey)
420
416
 
421
- // 2. Determine the final default value using a clear fallback chain
417
+ // 2. Determine the final default value using the ORIGINAL fallback chain with corrections
422
418
  let finalDefaultValue: string | undefined
423
419
  if (typeof specificDefault === 'string') {
420
+ // Most specific: defaultValue_one, defaultValue_ordinal_other, etc.
424
421
  finalDefaultValue = specificDefault
425
422
  } else if (category === 'one' && typeof defaultValue === 'string') {
423
+ // For "one" category, prefer the general defaultValue
426
424
  finalDefaultValue = defaultValue
425
+ } else if (category === 'one' && typeof defaultValueFromCall === 'string') {
426
+ // For "one" category, also consider defaultValueFromCall
427
+ finalDefaultValue = defaultValueFromCall
427
428
  } else if (isOrdinal && typeof ordinalOtherDefault === 'string') {
429
+ // For ordinals (non-one categories), fall back to ordinal_other
428
430
  finalDefaultValue = ordinalOtherDefault
429
431
  } else if (!isOrdinal && typeof otherDefault === 'string') {
432
+ // For cardinals (non-one categories), fall back to _other
430
433
  finalDefaultValue = otherDefault
431
434
  } else if (typeof defaultValue === 'string') {
435
+ // General defaultValue as fallback
432
436
  finalDefaultValue = defaultValue
433
- } else if (defaultValueFromCall && targetCategory === category) {
437
+ } else if (typeof defaultValueFromCall === 'string') {
438
+ // defaultValueFromCall as fallback
434
439
  finalDefaultValue = defaultValueFromCall
435
440
  } else {
441
+ // Final fallback to the base key itself
436
442
  finalDefaultValue = baseKey
437
443
  }
438
444
 
@@ -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;AAmSnF;;;;;;;;;;;;;;;;;;;;;;;;;;;;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,CA8E9B"}
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;AAgUnF;;;;;;;;;;;;;;;;;;;;;;;;;;;;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,CA8E9B"}
@@ -1 +1 @@
1
- {"version":3,"file":"call-expression-handler.d.ts","sourceRoot":"","sources":["../../../src/extractor/parsers/call-expression-handler.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,cAAc,EAA6C,MAAM,WAAW,CAAA;AAC1F,OAAO,KAAK,EAAE,aAAa,EAAE,oBAAoB,EAAE,MAAM,EAAgB,SAAS,EAAE,MAAM,aAAa,CAAA;AACvG,OAAO,EAAE,kBAAkB,EAAE,MAAM,uBAAuB,CAAA;AAG1D,qBAAa,qBAAqB;IAChC,OAAO,CAAC,aAAa,CAAe;IACpC,OAAO,CAAC,MAAM,CAAuC;IACrD,OAAO,CAAC,MAAM,CAAQ;IACtB,OAAO,CAAC,kBAAkB,CAAoB;IACvC,UAAU,cAAoB;gBAGnC,MAAM,EAAE,IAAI,CAAC,oBAAoB,EAAE,SAAS,CAAC,EAC7C,aAAa,EAAE,aAAa,EAC5B,MAAM,EAAE,MAAM,EACd,kBAAkB,EAAE,kBAAkB;IAQxC;;;;;;;;;;;;;OAaG;IACH,oBAAoB,CAAE,IAAI,EAAE,cAAc,EAAE,YAAY,EAAE,CAAC,IAAI,EAAE,MAAM,KAAK,SAAS,GAAG,SAAS,GAAG,IAAI;IAqMxG;;;;;;OAMG;IACH,OAAO,CAAC,4BAA4B;IA8BpC;;;;;;;;;;;;;OAaG;IACH,OAAO,CAAC,sBAAsB;IA2C9B;;;;;;;;;;;OAWG;IACH,OAAO,CAAC,gBAAgB;IA+HxB;;;;;;;;;OASG;IACH,OAAO,CAAC,eAAe;CA2BxB"}
1
+ {"version":3,"file":"call-expression-handler.d.ts","sourceRoot":"","sources":["../../../src/extractor/parsers/call-expression-handler.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,cAAc,EAA6C,MAAM,WAAW,CAAA;AAC1F,OAAO,KAAK,EAAE,aAAa,EAAE,oBAAoB,EAAE,MAAM,EAAgB,SAAS,EAAE,MAAM,aAAa,CAAA;AACvG,OAAO,EAAE,kBAAkB,EAAE,MAAM,uBAAuB,CAAA;AAG1D,qBAAa,qBAAqB;IAChC,OAAO,CAAC,aAAa,CAAe;IACpC,OAAO,CAAC,MAAM,CAAuC;IACrD,OAAO,CAAC,MAAM,CAAQ;IACtB,OAAO,CAAC,kBAAkB,CAAoB;IACvC,UAAU,cAAoB;gBAGnC,MAAM,EAAE,IAAI,CAAC,oBAAoB,EAAE,SAAS,CAAC,EAC7C,aAAa,EAAE,aAAa,EAC5B,MAAM,EAAE,MAAM,EACd,kBAAkB,EAAE,kBAAkB;IAQxC;;;;;;;;;;;;;OAaG;IACH,oBAAoB,CAAE,IAAI,EAAE,cAAc,EAAE,YAAY,EAAE,CAAC,IAAI,EAAE,MAAM,KAAK,SAAS,GAAG,SAAS,GAAG,IAAI;IAqMxG;;;;;;OAMG;IACH,OAAO,CAAC,4BAA4B;IA8BpC;;;;;;;;;;;;;OAaG;IACH,OAAO,CAAC,sBAAsB;IA2C9B;;;;;;;;;;;OAWG;IACH,OAAO,CAAC,gBAAgB;IAqIxB;;;;;;;;;OASG;IACH,OAAO,CAAC,eAAe;CA2BxB"}