i18next-cli 1.11.1 → 1.11.3
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 +10 -0
- package/dist/cjs/cli.js +1 -1
- package/dist/cjs/extractor/core/translation-manager.js +1 -1
- package/dist/cjs/extractor/parsers/call-expression-handler.js +1 -1
- package/dist/esm/cli.js +1 -1
- package/dist/esm/extractor/core/translation-manager.js +1 -1
- package/dist/esm/extractor/parsers/call-expression-handler.js +1 -1
- package/package.json +1 -1
- package/src/cli.ts +32 -12
- package/src/extractor/core/translation-manager.ts +35 -6
- package/src/extractor/parsers/call-expression-handler.ts +31 -25
- package/types/extractor/core/translation-manager.d.ts.map +1 -1
- package/types/extractor/parsers/call-expression-handler.d.ts.map +1 -1
package/CHANGELOG.md
CHANGED
|
@@ -5,6 +5,16 @@ 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.3](https://github.com/i18next/i18next-cli/compare/v1.11.2...v1.11.3) - 2025-10-13
|
|
9
|
+
|
|
10
|
+
- **Extractor:** Fixed the `--watch` flag being ignored in the `extract` command. The watch mode now properly monitors source files for changes and re-runs extraction automatically, matching the behavior of other commands like `types` and `lint`. [#62](https://github.com/i18next/i18next-cli/issues/62)
|
|
11
|
+
|
|
12
|
+
## [1.11.2](https://github.com/i18next/i18next-cli/compare/v1.11.1...v1.11.2) - 2025-10-13
|
|
13
|
+
|
|
14
|
+
- **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)
|
|
15
|
+
- **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.
|
|
16
|
+
- **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`).
|
|
17
|
+
|
|
8
18
|
## [1.11.1](https://github.com/i18next/i18next-cli/compare/v1.11.0...v1.11.1) - 2025-10-12
|
|
9
19
|
|
|
10
20
|
- **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.
|
|
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.3"),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 o=await i.ensureConfig(),a=async()=>{const t=await r.runExtractor(o,{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 a(),e.watch){console.log("\nWatching for changes...");t.watch(await n.glob(o.extract.input),{ignored:/node_modules/,persistent:!0}).on("change",e=>{console.log(`\nFile changed: ${e}`),a()})}}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=>`
|
|
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.
|
|
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
|
|
2
|
+
import{Command as t}from"commander";import e from"chokidar";import{glob as o}from"glob";import n from"chalk";import{ensureConfig as i,loadConfig as a}from"./config.js";import{detectConfig as c}from"./heuristic-config.js";import{runExtractor as r}from"./extractor/core/extractor.js";import"node:path";import"node:fs/promises";import"jiti";import{runTypesGenerator as s}from"./types-generator.js";import{runSyncer as l}from"./syncer.js";import{runMigrator as 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 h}from"./locize.js";const y=new t;y.name("i18next-cli").description("A unified, high-performance i18next CLI.").version("1.11.3"),y.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 n=await i(),a=async()=>{const e=await r(n,{isWatchMode:!!t.watch,isDryRun:!!t.dryRun,syncPrimaryWithDefaults:!!t.syncPrimary});return t.ci&&!e?(console.log("✅ No files were updated."),process.exit(0)):t.ci&&e&&(console.error("❌ Some files were updated. This should not happen in CI mode."),process.exit(1)),e};if(await a(),t.watch){console.log("\nWatching for changes...");e.watch(await o(n.extract.input),{ignored:/node_modules/,persistent:!0}).on("change",t=>{console.log(`\nFile changed: ${t}`),a()})}}catch(t){console.error("Error running extractor:",t),process.exit(1)}}),y.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(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!")),o=t}await u(o,{detail:t,namespace:e.namespace})}),y.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 n=await i(),a=()=>s(n);if(await a(),t.watch){console.log("\nWatching for changes...");e.watch(await o(n.types?.input||[]),{persistent:!0}).on("change",t=>{console.log(`\nFile changed: ${t}`),a()})}}),y.command("sync").description("Synchronize secondary language files with the primary language file.").action(async()=>{const t=await i();await l(t)}),y.command("migrate-config [configPath]").description("Migrate a legacy i18next-parser.config.js to the new format.").action(async t=>{await p(t)}),y.command("init").description("Create a new i18next.config.ts/js file with an interactive setup wizard.").action(m),y.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 i=async()=>{let t=await a();if(!t){console.log(n.blue("No config file found. Attempting to detect project structure..."));const e=await c();e||(console.error(n.red("Could not automatically detect your project structure.")),console.log(`Please create a config file first by running: ${n.cyan("npx i18next-cli init")}`),process.exit(1)),console.log(n.green("Project structure detected successfully!")),t=e}await d(t)};if(await i(),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}`),i()})}}}),y.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 i();await f(e,t)}),y.command("locize-download").description("Download all translations from your locize project.").action(async t=>{const e=await i();await g(e,t)}),y.command("locize-migrate").description("Migrate local translation files to a new locize project.").action(async t=>{const e=await i();await h(e,t)}),y.parse(process.argv);
|
|
@@ -1 +1 @@
|
|
|
1
|
-
import{resolve as t,basename as e,extname as r}from"node:path";import{glob as
|
|
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
|
|
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
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.
|
|
24
|
+
.version('1.11.3')
|
|
25
25
|
|
|
26
26
|
program
|
|
27
27
|
.command('extract')
|
|
@@ -34,18 +34,38 @@ program
|
|
|
34
34
|
try {
|
|
35
35
|
const config = await ensureConfig()
|
|
36
36
|
|
|
37
|
-
const
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
37
|
+
const runExtract = async () => {
|
|
38
|
+
const success = await runExtractor(config, {
|
|
39
|
+
isWatchMode: !!options.watch,
|
|
40
|
+
isDryRun: !!options.dryRun,
|
|
41
|
+
syncPrimaryWithDefaults: !!options.syncPrimary
|
|
42
|
+
})
|
|
42
43
|
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
44
|
+
if (options.ci && !success) {
|
|
45
|
+
console.log('✅ No files were updated.')
|
|
46
|
+
process.exit(0)
|
|
47
|
+
} else if (options.ci && success) {
|
|
48
|
+
console.error('❌ Some files were updated. This should not happen in CI mode.')
|
|
49
|
+
process.exit(1)
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
return success
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
// Run the extractor once initially
|
|
56
|
+
await runExtract()
|
|
57
|
+
|
|
58
|
+
// If in watch mode, set up the chokidar watcher
|
|
59
|
+
if (options.watch) {
|
|
60
|
+
console.log('\nWatching for changes...')
|
|
61
|
+
const watcher = chokidar.watch(await glob(config.extract.input), {
|
|
62
|
+
ignored: /node_modules/,
|
|
63
|
+
persistent: true,
|
|
64
|
+
})
|
|
65
|
+
watcher.on('change', path => {
|
|
66
|
+
console.log(`\nFile changed: ${path}`)
|
|
67
|
+
runExtract()
|
|
68
|
+
})
|
|
49
69
|
}
|
|
50
70
|
} catch (error) {
|
|
51
71
|
console.error('Error running extractor:', error)
|
|
@@ -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 => `
|
|
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
|
-
|
|
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
|
|
251
|
+
// For secondary languages, always use empty string
|
|
234
252
|
valueToSet = resolveDefaultValue(emptyDefaultValue, key, namespace, locale)
|
|
235
253
|
}
|
|
236
254
|
} else {
|
|
237
|
-
//
|
|
238
|
-
if (locale === primaryLanguage && syncPrimaryWithDefaults
|
|
239
|
-
|
|
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
|
-
//
|
|
394
|
-
|
|
395
|
-
|
|
396
|
-
|
|
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
|
-
|
|
401
|
-
|
|
402
|
-
|
|
403
|
-
|
|
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
|
|
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
|
|
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;
|
|
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;
|
|
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"}
|