i18next-cli 0.9.19 → 1.0.0
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 -2
- package/dist/cjs/cli.js +1 -1
- package/dist/cjs/extractor/parsers/ast-utils.js +1 -0
- package/dist/cjs/extractor/parsers/ast-visitors.js +1 -1
- package/dist/cjs/extractor/parsers/jsx-parser.js +1 -1
- package/dist/cjs/locize.js +1 -1
- package/dist/esm/cli.js +1 -1
- package/dist/esm/extractor/parsers/ast-utils.js +1 -0
- package/dist/esm/extractor/parsers/ast-visitors.js +1 -1
- package/dist/esm/extractor/parsers/jsx-parser.js +1 -1
- package/dist/esm/locize.js +1 -1
- package/package.json +6 -6
- package/src/cli.ts +1 -1
- package/src/extractor/parsers/ast-utils.ts +52 -0
- package/src/extractor/parsers/ast-visitors.ts +47 -92
- package/src/extractor/parsers/jsx-parser.ts +34 -9
- package/src/locize.ts +38 -57
- package/src/types.ts +4 -1
- package/types/extractor/parsers/ast-utils.d.ts +31 -0
- package/types/extractor/parsers/ast-utils.d.ts.map +1 -0
- package/types/extractor/parsers/ast-visitors.d.ts +0 -29
- package/types/extractor/parsers/ast-visitors.d.ts.map +1 -1
- package/types/extractor/parsers/jsx-parser.d.ts.map +1 -1
- package/types/locize.d.ts.map +1 -1
- package/types/types.d.ts +3 -1
- package/types/types.d.ts.map +1 -1
- package/vitest.config.ts +4 -0
- package/tryme.js +0 -8
package/CHANGELOG.md
CHANGED
|
@@ -5,9 +5,17 @@ All notable changes to this project will be documented in this file.
|
|
|
5
5
|
The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/),
|
|
6
6
|
and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
|
|
7
7
|
|
|
8
|
-
## [1.0.0](https://github.com/i18next/i18next-cli/compare/v0.9.
|
|
8
|
+
## [1.0.0](https://github.com/i18next/i18next-cli/compare/v0.9.20...v1.0.0) - 2025-10-01
|
|
9
9
|
|
|
10
|
-
|
|
10
|
+
🎉 **Official v1.0.0 Release!**
|
|
11
|
+
|
|
12
|
+
This release marks the official v1.0.0 milestone for `i18next-cli`, signifying that the tool is stable, feature-complete, and ready for production use - any future breaking changes will require a major version bump per SemVer.
|
|
13
|
+
|
|
14
|
+
After extensive development and numerous bug fixes across the v0.9.x series, the core feature set is now considered robust and reliable. Thank you to everyone who contributed by reporting issues and providing valuable feedback!
|
|
15
|
+
|
|
16
|
+
## [0.9.20](https://github.com/i18next/i18next-cli/compare/v0.9.19...v0.9.20) - 2025-09-30
|
|
17
|
+
|
|
18
|
+
- **Extractor (`<Trans>`):** Added support for the `tOptions` prop on the `<Trans>` component. The extractor can now read plural-specific default values (e.g., `defaultValue_other`), namespaces (`ns`), and `context` from this prop, providing parity with the `t()` function's advanced options.
|
|
11
19
|
|
|
12
20
|
## [0.9.19](https://github.com/i18next/i18next-cli/compare/v0.9.18...v0.9.19) - 2025-09-30
|
|
13
21
|
|
package/dist/cjs/cli.js
CHANGED
|
@@ -1,2 +1,2 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
|
-
"use strict";var e=require("commander"),t=require("chokidar"),o=require("glob"),n=require("chalk"),i=require("./config.js"),a=require("./heuristic-config.js"),r=require("./extractor/core/extractor.js");require("node:path"),require("node:fs/promises"),require("jiti");var c=require("./types-generator.js"),s=require("./syncer.js"),l=require("./migrator.js"),u=require("./init.js"),d=require("./linter.js"),g=require("./status.js"),p=require("./locize.js");const f=new e.Command;f.name("i18next-cli").description("A unified, high-performance i18next CLI.").version("0.
|
|
2
|
+
"use strict";var e=require("commander"),t=require("chokidar"),o=require("glob"),n=require("chalk"),i=require("./config.js"),a=require("./heuristic-config.js"),r=require("./extractor/core/extractor.js");require("node:path"),require("node:fs/promises"),require("jiti");var c=require("./types-generator.js"),s=require("./syncer.js"),l=require("./migrator.js"),u=require("./init.js"),d=require("./linter.js"),g=require("./status.js"),p=require("./locize.js");const f=new e.Command;f.name("i18next-cli").description("A unified, high-performance i18next CLI.").version("1.0.0"),f.command("extract").description("Extract translation keys from source files and update resource files.").option("-w, --watch","Watch for file changes and re-run the extractor.").option("--ci","Exit with a non-zero status code if any files are updated.").action(async e=>{const a=await i.ensureConfig(),c=async()=>{const t=await r.runExtractor(a);e.ci&&t&&(console.error(n.red.bold("\n[CI Mode] Error: Translation files were updated. Please commit the changes.")),console.log(n.yellow("💡 Tip: Tired of committing JSON files? locize syncs your team automatically => https://www.locize.com/docs/getting-started")),console.log(` Learn more: ${n.cyan("npx i18next-cli locize-sync")}`),process.exit(1))};if(await c(),e.watch){console.log("\nWatching for changes...");t.watch(await o.glob(a.extract.input),{ignored:/node_modules/,persistent:!0}).on("change",e=>{console.log(`\nFile changed: ${e}`),c()})}}),f.command("status [locale]").description("Display translation status. Provide a locale for a detailed key-by-key view.").option("-n, --namespace <ns>","Filter the status report by a specific namespace").action(async(e,t)=>{let o=await i.loadConfig();if(!o){console.log(n.blue("No config file found. Attempting to detect project structure..."));const e=await a.detectConfig();e||(console.error(n.red("Could not automatically detect your project structure.")),console.log(`Please create a config file first by running: ${n.cyan("npx i18next-cli init")}`),process.exit(1)),console.log(n.green("Project structure detected successfully!")),o=e}await g.runStatus(o,{detail:e,namespace:t.namespace})}),f.command("types").description("Generate TypeScript definitions from translation resource files.").option("-w, --watch","Watch for file changes and re-run the type generator.").action(async e=>{const n=await i.ensureConfig(),a=()=>c.runTypesGenerator(n);if(await a(),e.watch){console.log("\nWatching for changes...");t.watch(await o.glob(n.types?.input||[]),{persistent:!0}).on("change",e=>{console.log(`\nFile changed: ${e}`),a()})}}),f.command("sync").description("Synchronize secondary language files with the primary language file.").action(async()=>{const e=await i.ensureConfig();await s.runSyncer(e)}),f.command("migrate-config").description("Migrate a legacy i18next-parser.config.js to the new format.").action(async()=>{await l.runMigrator()}),f.command("init").description("Create a new i18next.config.ts/js file with an interactive setup wizard.").action(u.runInit),f.command("lint").description("Find potential issues like hardcoded strings in your codebase.").action(async()=>{let e=await i.loadConfig();if(!e){console.log(n.blue("No config file found. Attempting to detect project structure..."));const t=await a.detectConfig();t||(console.error(n.red("Could not automatically detect your project structure.")),console.log(`Please create a config file first by running: ${n.cyan("npx i1e-toolkit init")}`),process.exit(1)),console.log(n.green("Project structure detected successfully!")),e=t}await d.runLinter(e)}),f.command("locize-sync").description("Synchronize local translations with your locize project.").option("--update-values","Update values of existing translations on locize.").option("--src-lng-only","Check for changes in source language only.").option("--compare-mtime","Compare modification times when syncing.").option("--dry-run","Run the command without making any changes.").action(async e=>{const t=await i.ensureConfig();await p.runLocizeSync(t,e)}),f.command("locize-download").description("Download all translations from your locize project.").action(async e=>{const t=await i.ensureConfig();await p.runLocizeDownload(t,e)}),f.command("locize-migrate").description("Migrate local translation files to a new locize project.").action(async e=>{const t=await i.ensureConfig();await p.runLocizeMigrate(t,e)}),f.parse(process.argv);
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
"use strict";function e(e,t){return e.properties.find(e=>"KeyValueProperty"===e.type&&("Identifier"===e.key?.type&&e.key.value===t||"StringLiteral"===e.key?.type&&e.key.value===t))}exports.getObjectPropValue=function(t,r){const y=e(t,r);if("KeyValueProperty"===y?.type){const e=y.value;return"StringLiteral"===e.type||("BooleanLiteral"===e.type||"NumericLiteral"===e.type)?e.value:""}},exports.getObjectProperty=e;
|
|
@@ -1 +1 @@
|
|
|
1
|
-
"use strict";var e=require("./jsx-parser.js");exports.ASTVisitors=class{pluginContext;config;logger;scopeStack=[];objectKeys=new Set;constructor(e,t,n){this.pluginContext=t,this.config=e,this.logger=n}visit(e){this.enterScope(),this.walk(e),this.exitScope()}walk(e){if(!e)return;let t=!1;switch("Function"!==e.type&&"ArrowFunctionExpression"!==e.type&&"FunctionExpression"!==e.type||(this.enterScope(),t=!0),e.type){case"VariableDeclarator":this.handleVariableDeclarator(e);break;case"CallExpression":this.handleCallExpression(e);break;case"JSXElement":this.handleJSXElement(e)}for(const t in e){if("span"===t)continue;const n=e[t];if(Array.isArray(n))for(const e of n)e&&"object"==typeof e&&this.walk(e);else n&&n.type&&this.walk(n)}t&&this.exitScope()}enterScope(){this.scopeStack.push(new Map)}exitScope(){this.scopeStack.pop()}setVarInScope(e,t){this.scopeStack.length>0&&this.scopeStack[this.scopeStack.length-1].set(e,t)}getVarFromScope(e){for(let t=this.scopeStack.length-1;t>=0;t--)if(this.scopeStack[t].has(e))return this.scopeStack[t].get(e)}handleVariableDeclarator(e){const t=e.init;if(!t)return;const n="AwaitExpression"===t.type&&"CallExpression"===t.argument.type?t.argument:"CallExpression"===t.type?t:null;if(!n)return;const r=n.callee;if("Identifier"===r.type){const t=this.getUseTranslationConfig(r.value);if(t)return void this.handleUseTranslationDeclarator(e,n,t)}"MemberExpression"===r.type&&"Identifier"===r.property.type&&"getFixedT"===r.property.value&&this.handleGetFixedTDeclarator(e,n)}handleUseTranslationDeclarator(e,
|
|
1
|
+
"use strict";var e=require("./jsx-parser.js"),t=require("./ast-utils.js");exports.ASTVisitors=class{pluginContext;config;logger;scopeStack=[];objectKeys=new Set;constructor(e,t,n){this.pluginContext=t,this.config=e,this.logger=n}visit(e){this.enterScope(),this.walk(e),this.exitScope()}walk(e){if(!e)return;let t=!1;switch("Function"!==e.type&&"ArrowFunctionExpression"!==e.type&&"FunctionExpression"!==e.type||(this.enterScope(),t=!0),e.type){case"VariableDeclarator":this.handleVariableDeclarator(e);break;case"CallExpression":this.handleCallExpression(e);break;case"JSXElement":this.handleJSXElement(e)}for(const t in e){if("span"===t)continue;const n=e[t];if(Array.isArray(n))for(const e of n)e&&"object"==typeof e&&this.walk(e);else n&&n.type&&this.walk(n)}t&&this.exitScope()}enterScope(){this.scopeStack.push(new Map)}exitScope(){this.scopeStack.pop()}setVarInScope(e,t){this.scopeStack.length>0&&this.scopeStack[this.scopeStack.length-1].set(e,t)}getVarFromScope(e){for(let t=this.scopeStack.length-1;t>=0;t--)if(this.scopeStack[t].has(e))return this.scopeStack[t].get(e)}handleVariableDeclarator(e){const t=e.init;if(!t)return;const n="AwaitExpression"===t.type&&"CallExpression"===t.argument.type?t.argument:"CallExpression"===t.type?t:null;if(!n)return;const r=n.callee;if("Identifier"===r.type){const t=this.getUseTranslationConfig(r.value);if(t)return void this.handleUseTranslationDeclarator(e,n,t)}"MemberExpression"===r.type&&"Identifier"===r.property.type&&"getFixedT"===r.property.value&&this.handleGetFixedTDeclarator(e,n)}handleUseTranslationDeclarator(e,n,r){let i;if("Identifier"===e.id.type&&(i=e.id.value),"ArrayPattern"===e.id.type){const t=e.id.elements[0];"Identifier"===t?.type&&(i=t.value)}if("ObjectPattern"===e.id.type)for(const t of e.id.properties){if("AssignmentPatternProperty"===t.type&&"Identifier"===t.key.type&&"t"===t.key.value){i="t";break}if("KeyValuePatternProperty"===t.type&&"Identifier"===t.key.type&&"t"===t.key.value&&"Identifier"===t.value.type){i=t.value.value;break}}if(!i)return;const s=n.arguments?.[r.nsArg]?.expression;let a;"StringLiteral"===s?.type?a=s.value:"ArrayExpression"===s?.type&&"StringLiteral"===s.elements[0]?.expression.type&&(a=s.elements[0].expression.value);const o=n.arguments?.[r.keyPrefixArg]?.expression;let l;if("ObjectExpression"===o?.type){const e=t.getObjectPropValue(o,"keyPrefix");l="string"==typeof e?e:void 0}this.setVarInScope(i,{defaultNs:a,keyPrefix:l})}handleGetFixedTDeclarator(e,t){if("Identifier"!==e.id.type||!e.init||"CallExpression"!==e.init.type)return;const n=e.id.value,r=t.arguments,i=r[1]?.expression,s=r[2]?.expression,a="StringLiteral"===i?.type?i.value:void 0,o="StringLiteral"===s?.type?s.value:void 0;(a||o)&&this.setVarInScope(n,{defaultNs:a,keyPrefix:o})}handleCallExpression(e){const n=this.getFunctionName(e.callee);if(!n)return;const r=this.getVarFromScope(n);if(!((this.config.extract.functions||["t"]).includes(n)||void 0!==r)||0===e.arguments.length)return;const i=e.arguments[0].expression;let s=[];if("StringLiteral"===i.type)s.push(i.value);else if("ArrowFunctionExpression"===i.type){const e=this.extractKeyFromSelector(i);e&&s.push(e)}else if("ArrayExpression"===i.type)for(const e of i.elements)"StringLiteral"===e?.expression.type&&s.push(e.expression.value);if(s=s.filter(e=>!!e),0===s.length)return;let a=!1;const o=this.config.extract.pluralSeparator??"_";for(let e=0;e<s.length;e++)s[e].endsWith(`${o}ordinal`)&&(a=!0,s[e]=s[e].slice(0,-8));let l,p;if(e.arguments.length>1){const t=e.arguments[1].expression;"ObjectExpression"===t.type?p=t:"StringLiteral"===t.type&&(l=t.value)}if(e.arguments.length>2){const t=e.arguments[2].expression;"ObjectExpression"===t.type&&(p=t)}const u=p?t.getObjectPropValue(p,"defaultValue"):void 0,c="string"==typeof u?u:l;for(let e=0;e<s.length;e++){let n,i=s[e];if(p){const e=t.getObjectPropValue(p,"ns");"string"==typeof e&&(n=e)}!n&&r?.defaultNs&&(n=r.defaultNs);const o=this.config.extract.nsSeparator??":";if(!n&&o&&i.includes(o)){const e=i.split(o);n=e.shift(),i=e.join(o)}n||(n=this.config.extract.defaultNS);let l=i;if(r?.keyPrefix){const e=this.config.extract.keySeparator??".";l=`${r.keyPrefix}${e}${i}`}const u=e===s.length-1&&c||i;if(p){const e=t.getObjectProperty(p,"context");if("ConditionalExpression"===e?.value?.type){const t=this.resolvePossibleStringValues(e.value),r=this.config.extract.contextSeparator??"_";if(t.length>0){t.forEach(e=>{this.pluginContext.addKey({key:`${l}${r}${e}`,ns:n,defaultValue:u})}),this.pluginContext.addKey({key:l,ns:n,defaultValue:u});continue}}const r=t.getObjectPropValue(p,"context");if("string"==typeof r&&r){const e=this.config.extract.contextSeparator??"_";this.pluginContext.addKey({key:`${l}${e}${r}`,ns:n,defaultValue:u});continue}const i=void 0!==t.getObjectPropValue(p,"count"),s=!0===t.getObjectPropValue(p,"ordinal");if(i||a){this.handlePluralKeys(l,n,p,s||a);continue}!0===t.getObjectPropValue(p,"returnObjects")&&this.objectKeys.add(l)}this.pluginContext.addKey({key:l,ns:n,defaultValue:u})}}handlePluralKeys(e,n,r,i){try{const s=i?"ordinal":"cardinal",a=new Intl.PluralRules(this.config.extract?.primaryLanguage,{type:s}).resolvedOptions().pluralCategories,o=this.config.extract.pluralSeparator??"_",l=t.getObjectPropValue(r,"defaultValue"),p=t.getObjectPropValue(r,`defaultValue${o}other`),u=t.getObjectPropValue(r,`defaultValue${o}ordinal${o}other`);for(const s of a){const a=i?`defaultValue${o}ordinal${o}${s}`:`defaultValue${o}${s}`,c=t.getObjectPropValue(r,a);let f;f="string"==typeof c?c:"one"===s&&"string"==typeof l?l:i&&"string"==typeof u?u:i||"string"!=typeof p?"string"==typeof l?l:e:p;const y=i?`${e}${o}ordinal${o}${s}`:`${e}${o}${s}`;this.pluginContext.addKey({key:y,ns:n,defaultValue:f,hasCount:!0,isOrdinal:i})}}catch(i){this.logger.warn(`Could not determine plural rules for language "${this.config.extract?.primaryLanguage}". Falling back to simple key extraction.`);const s=t.getObjectPropValue(r,"defaultValue");this.pluginContext.addKey({key:e,ns:n,defaultValue:"string"==typeof s?s:e})}}handleSimplePluralKeys(e,t,n){try{const r=new Intl.PluralRules(this.config.extract?.primaryLanguage).resolvedOptions().pluralCategories,i=this.config.extract.pluralSeparator??"_";for(const s of r)this.pluginContext.addKey({key:`${e}${i}${s}`,ns:n,defaultValue:t,hasCount:!0})}catch(r){this.logger.warn(`Could not determine plural rules for language "${this.config.extract?.primaryLanguage}".`),this.pluginContext.addKey({key:e,ns:n,defaultValue:t})}}handleJSXElement(t){const n=this.getElementName(t);if(n&&(this.config.extract.transComponents||["Trans"]).includes(n)){const n=e.extractFromTransComponent(t,this.config);if(n){if(!n.ns){const e=t.opening.attributes?.find(e=>"JSXAttribute"===e.type&&"Identifier"===e.name.type&&"t"===e.name.value);if("JSXAttribute"===e?.type&&"JSXExpressionContainer"===e.value?.type&&"Identifier"===e.value.expression.type){const t=e.value.expression.value,r=this.getVarFromScope(t);r?.defaultNs&&(n.ns=r.defaultNs)}}if(n.ns||(n.ns=this.config.extract.defaultNS),n.contextExpression){const e=this.resolvePossibleStringValues(n.contextExpression),t=this.config.extract.contextSeparator??"_";if(e.length>0){for(const r of e)this.pluginContext.addKey({key:`${n.key}${t}${r}`,ns:n.ns,defaultValue:n.defaultValue});"StringLiteral"!==n.contextExpression.type&&this.pluginContext.addKey(n)}}else if(n.hasCount){const e=t.opening.attributes?.find(e=>"JSXAttribute"===e.type&&"Identifier"===e.name.type&&"ordinal"===e.name.value),r=!!e,i=n.optionsNode??{type:"ObjectExpression",properties:[],span:{start:0,end:0,ctxt:0}};i.properties.push({type:"KeyValueProperty",key:{type:"Identifier",value:"defaultValue",optional:!1,span:{start:0,end:0,ctxt:0}},value:{type:"StringLiteral",value:n.defaultValue,span:{start:0,end:0,ctxt:0}}}),this.handlePluralKeys(n.key,n.ns,i,r)}else this.pluginContext.addKey(n)}}}getElementName(e){if("Identifier"===e.opening.name.type)return e.opening.name.value;if("JSXMemberExpression"===e.opening.name.type){let t=e.opening.name;const n=[];for(;"JSXMemberExpression"===t.type;)"Identifier"===t.property.type&&n.unshift(t.property.value),t=t.object;return"Identifier"===t.type&&n.unshift(t.value),n.join(".")}}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 r=[];for(;"MemberExpression"===n.type;){const e=n.property;if("Identifier"===e.type)r.unshift(e.value);else{if("Computed"!==e.type||"StringLiteral"!==e.expression.type)return null;r.unshift(e.expression.value)}n=n.object}if(r.length>0){const e=this.config.extract.keySeparator,t="string"==typeof e?e:".";return r.join(t)}return null}resolvePossibleStringValues(e){if("StringLiteral"===e.type)return[e.value];if("ConditionalExpression"===e.type){return[...this.resolvePossibleStringValues(e.consequent),...this.resolvePossibleStringValues(e.alternate)]}return"Identifier"===e.type&&e.value,[]}getUseTranslationConfig(e){const t=this.config.extract.useTranslationNames||["useTranslation"];for(const n of t){if("string"==typeof n&&n===e)return{name:e,nsArg:0,keyPrefixArg:1};if("object"==typeof n&&n.name===e)return{name:n.name,nsArg:n.nsArg??0,keyPrefixArg:n.keyPrefixArg??1}}}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}return"Identifier"!==n.type?null:(t.unshift(n.value),t.join("."))}return null}};
|
|
@@ -1 +1 @@
|
|
|
1
|
-
"use strict";
|
|
1
|
+
"use strict";var e=require("./ast-utils.js");function t(e,t){const n=new Set(t.extract.transKeepBasicHtmlNodesFor??["br","strong","i","p"]);return function e(t){let i="";return t.forEach((t,r)=>{if("JSXText"===t.type)i+=t.value;else if("JSXExpressionContainer"===t.type){const e=t.expression;if("StringLiteral"===e.type)i+=e.value;else if("Identifier"===e.type)i+=`{{${e.value}}}`;else if("ObjectExpression"===e.type){const t=e.properties[0];t&&"Identifier"===t.type&&(i+=`{{${t.value}}}`)}}else if("JSXElement"===t.type){let a;"Identifier"===t.opening.name.type&&(a=t.opening.name.value);const u=e(t.children);a&&n.has(a)?i+=`<${a}>${u}</${a}>`:i+=`<${r}>${u}</${r}>`}else"JSXFragment"===t.type&&(i+=e(t.children))}),i}(e).trim().replace(/\s{2,}/g," ")}exports.extractFromTransComponent=function(n,i){const r=n.opening.attributes?.find(e=>"JSXAttribute"===e.type&&"Identifier"===e.name.type&&"i18nKey"===e.name.value),a=n.opening.attributes?.find(e=>"JSXAttribute"===e.type&&"Identifier"===e.name.type&&"defaults"===e.name.value),u=n.opening.attributes?.find(e=>"JSXAttribute"===e.type&&"Identifier"===e.name.type&&"count"===e.name.value),p=!!u,s=n.opening.attributes?.find(e=>"JSXAttribute"===e.type&&"Identifier"===e.name.type&&"tOptions"===e.name.value),l="JSXAttribute"===s?.type&&"JSXExpressionContainer"===s.value?.type&&"ObjectExpression"===s.value.expression.type?s.value.expression:void 0,o=n.opening.attributes?.find(e=>"JSXAttribute"===e.type&&"Identifier"===e.name.type&&"context"===e.name.value);let y,v="JSXAttribute"===o?.type&&"JSXExpressionContainer"===o.value?.type?o.value.expression:void 0;if(y="JSXAttribute"===r?.type&&"StringLiteral"===r.value?.type?r.value.value:t(n.children,i),!y)return null;const f=n.opening.attributes?.find(e=>"JSXAttribute"===e.type&&"Identifier"===e.name.type&&"ns"===e.name.value);let c;if(c="JSXAttribute"===f?.type&&"StringLiteral"===f.value?.type?f.value.value:void 0,l&&(void 0===c&&(c=e.getObjectPropValue(l,"ns")),void 0===v)){const t=e.getObjectProperty(l,"context");t?.value&&(v=t.value)}let d=i.extract.defaultValue||"";return d="JSXAttribute"===a?.type&&"StringLiteral"===a.value?.type?a.value.value:t(n.children,i),{key:y,ns:c,defaultValue:d||y,hasCount:p,contextExpression:v,optionsNode:l}};
|
package/dist/cjs/locize.js
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
"use strict";var e=require("execa"),o=require("chalk"),n=require("ora"),t=require("inquirer"),
|
|
1
|
+
"use strict";var e=require("execa"),o=require("chalk"),n=require("ora"),t=require("inquirer"),i=require("node:path");function r(e,o,n){const{locize:t={},extract:r}=o,{projectId:s,apiKey:c,version:a}=t,l=[e];if(s&&l.push("--project-id",s),c&&l.push("--api-key",c),a&&l.push("--ver",a),"sync"===e){(n.updateValues??t.updateValues)&&l.push("--update-values","true");(n.srcLngOnly??t.sourceLanguageOnly)&&l.push("--reference-language-only","true");(n.compareMtime??t.compareModificationTime)&&l.push("--compare-modification-time","true");(n.dryRun??t.dryRun)&&l.push("--dry","true")}const u=i.resolve(process.cwd(),r.output.split("/{{language}}/")[0]);return l.push("--path",u),l}async function s(i,s,c={}){await async function(){try{await e.execa("locize",["--version"])}catch(e){"ENOENT"===e.code&&(console.error(o.red("Error: `locize-cli` command not found.")),console.log(o.yellow("Please install it globally to use the locize integration:")),console.log(o.cyan("npm install -g locize-cli")),process.exit(1))}}();const a=n(`Running 'locize ${i}'...\n`).start();let l=s;try{const n=r(i,l,c);console.log(o.cyan(`\nRunning 'locize ${n.join(" ")}'...`));const t=await e.execa("locize",n,{stdio:"pipe"});a.succeed(o.green(`'locize ${i}' completed successfully.`)),t?.stdout&&console.log(t.stdout)}catch(n){const s=n.stderr||"";if(s.includes("missing required argument")){const n=await async function(){console.log(o.yellow("\nLocize configuration is missing or invalid. Let's set it up!"));const e=await t.prompt([{type:"input",name:"projectId",message:"What is your locize Project ID? (Find this in your project settings on www.locize.app)",validate:e=>!!e||"Project ID cannot be empty."},{type:"password",name:"apiKey",message:'What is your locize API key? (Create or use one in your project settings > "API Keys")',validate:e=>!!e||"API Key cannot be empty."},{type:"input",name:"version",message:"What version do you want to sync with?",default:"latest"}]);if(!e.projectId)return void console.error(o.red("Project ID is required to continue."));const{save:n}=await t.prompt([{type:"confirm",name:"save",message:"Would you like to see how to save these credentials for future use?",default:!0}]);if(n){const n=`\n# Add this to your .env file (and ensure .env is in your .gitignore!)\nLOCIZE_API_KEY=${e.apiKey}\n`,t=`\n // Add this to your i18next.config.ts file\n locize: {\n projectId: '${e.projectId}',\n // For security, apiKey is best set via an environment variable\n apiKey: process.env.LOCIZE_API_KEY,\n version: '${e.version}',\n },`;console.log(o.cyan("\nGreat! For the best security, we recommend using environment variables for your API key.")),console.log(o.bold("\nRecommended approach (.env file):")),console.log(o.green(n)),console.log(o.bold("Then, in your i18next.config.ts:")),console.log(o.green(t))}return{projectId:e.projectId,apiKey:e.apiKey,version:e.version}}();if(n){l={...l,locize:n},a.start("Retrying with new credentials...");try{const n=r(i,l,c);console.log(o.cyan(`\nRunning 'locize ${n.join(" ")}'...`));const t=await e.execa("locize",n,{stdio:"pipe"});a.succeed(o.green("Retry successful!")),t?.stdout&&console.log(t.stdout)}catch(e){a.fail(o.red("Error during retry.")),console.error(e.stderr||e.message),process.exit(1)}}else a.fail("Operation cancelled."),process.exit(1)}else a.fail(o.red(`Error executing 'locize ${i}'.`)),console.error(s||n.message),process.exit(1)}console.log(o.green(`\n✅ 'locize ${i}' completed successfully.`))}exports.runLocizeDownload=(e,o)=>s("download",e,o),exports.runLocizeMigrate=(e,o)=>s("migrate",e,o),exports.runLocizeSync=(e,o)=>s("sync",e,o);
|
package/dist/esm/cli.js
CHANGED
|
@@ -1,2 +1,2 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
|
-
import{Command as o}from"commander";import t from"chokidar";import{glob as e}from"glob";import i from"chalk";import{ensureConfig as n,loadConfig as a}from"./config.js";import{detectConfig as c}from"./heuristic-config.js";import{runExtractor as r}from"./extractor/core/extractor.js";import"node:path";import"node:fs/promises";import"jiti";import{runTypesGenerator as s}from"./types-generator.js";import{runSyncer as l}from"./syncer.js";import{runMigrator as m}from"./migrator.js";import{runInit as p}from"./init.js";import{runLinter as d}from"./linter.js";import{runStatus as f}from"./status.js";import{runLocizeSync as g,runLocizeDownload as u,runLocizeMigrate as y}from"./locize.js";const w=new o;w.name("i18next-cli").description("A unified, high-performance i18next CLI.").version("0.
|
|
2
|
+
import{Command as o}from"commander";import t from"chokidar";import{glob as e}from"glob";import i from"chalk";import{ensureConfig as n,loadConfig as a}from"./config.js";import{detectConfig as c}from"./heuristic-config.js";import{runExtractor as r}from"./extractor/core/extractor.js";import"node:path";import"node:fs/promises";import"jiti";import{runTypesGenerator as s}from"./types-generator.js";import{runSyncer as l}from"./syncer.js";import{runMigrator as m}from"./migrator.js";import{runInit as p}from"./init.js";import{runLinter as d}from"./linter.js";import{runStatus as f}from"./status.js";import{runLocizeSync as g,runLocizeDownload as u,runLocizeMigrate as y}from"./locize.js";const w=new o;w.name("i18next-cli").description("A unified, high-performance i18next CLI.").version("1.0.0"),w.command("extract").description("Extract translation keys from source files and update resource files.").option("-w, --watch","Watch for file changes and re-run the extractor.").option("--ci","Exit with a non-zero status code if any files are updated.").action(async o=>{const a=await n(),c=async()=>{const t=await r(a);o.ci&&t&&(console.error(i.red.bold("\n[CI Mode] Error: Translation files were updated. Please commit the changes.")),console.log(i.yellow("💡 Tip: Tired of committing JSON files? locize syncs your team automatically => https://www.locize.com/docs/getting-started")),console.log(` Learn more: ${i.cyan("npx i18next-cli locize-sync")}`),process.exit(1))};if(await c(),o.watch){console.log("\nWatching for changes...");t.watch(await e(a.extract.input),{ignored:/node_modules/,persistent:!0}).on("change",o=>{console.log(`\nFile changed: ${o}`),c()})}}),w.command("status [locale]").description("Display translation status. Provide a locale for a detailed key-by-key view.").option("-n, --namespace <ns>","Filter the status report by a specific namespace").action(async(o,t)=>{let e=await a();if(!e){console.log(i.blue("No config file found. Attempting to detect project structure..."));const o=await c();o||(console.error(i.red("Could not automatically detect your project structure.")),console.log(`Please create a config file first by running: ${i.cyan("npx i18next-cli init")}`),process.exit(1)),console.log(i.green("Project structure detected successfully!")),e=o}await f(e,{detail:o,namespace:t.namespace})}),w.command("types").description("Generate TypeScript definitions from translation resource files.").option("-w, --watch","Watch for file changes and re-run the type generator.").action(async o=>{const i=await n(),a=()=>s(i);if(await a(),o.watch){console.log("\nWatching for changes...");t.watch(await e(i.types?.input||[]),{persistent:!0}).on("change",o=>{console.log(`\nFile changed: ${o}`),a()})}}),w.command("sync").description("Synchronize secondary language files with the primary language file.").action(async()=>{const o=await n();await l(o)}),w.command("migrate-config").description("Migrate a legacy i18next-parser.config.js to the new format.").action(async()=>{await m()}),w.command("init").description("Create a new i18next.config.ts/js file with an interactive setup wizard.").action(p),w.command("lint").description("Find potential issues like hardcoded strings in your codebase.").action(async()=>{let o=await a();if(!o){console.log(i.blue("No config file found. Attempting to detect project structure..."));const t=await c();t||(console.error(i.red("Could not automatically detect your project structure.")),console.log(`Please create a config file first by running: ${i.cyan("npx i1e-toolkit init")}`),process.exit(1)),console.log(i.green("Project structure detected successfully!")),o=t}await d(o)}),w.command("locize-sync").description("Synchronize local translations with your locize project.").option("--update-values","Update values of existing translations on locize.").option("--src-lng-only","Check for changes in source language only.").option("--compare-mtime","Compare modification times when syncing.").option("--dry-run","Run the command without making any changes.").action(async o=>{const t=await n();await g(t,o)}),w.command("locize-download").description("Download all translations from your locize project.").action(async o=>{const t=await n();await u(t,o)}),w.command("locize-migrate").description("Migrate local translation files to a new locize project.").action(async o=>{const t=await n();await y(t,o)}),w.parse(process.argv);
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
function e(e,t){return e.properties.find(e=>"KeyValueProperty"===e.type&&("Identifier"===e.key?.type&&e.key.value===t||"StringLiteral"===e.key?.type&&e.key.value===t))}function t(t,r){const y=e(t,r);if("KeyValueProperty"===y?.type){const e=y.value;return"StringLiteral"===e.type||("BooleanLiteral"===e.type||"NumericLiteral"===e.type)?e.value:""}}export{t as getObjectPropValue,e as getObjectProperty};
|
|
@@ -1 +1 @@
|
|
|
1
|
-
import{extractFromTransComponent as e}from"./jsx-parser.js";class
|
|
1
|
+
import{extractFromTransComponent as e}from"./jsx-parser.js";import{getObjectPropValue as t,getObjectProperty as n}from"./ast-utils.js";class i{pluginContext;config;logger;scopeStack=[];objectKeys=new Set;constructor(e,t,n){this.pluginContext=t,this.config=e,this.logger=n}visit(e){this.enterScope(),this.walk(e),this.exitScope()}walk(e){if(!e)return;let t=!1;switch("Function"!==e.type&&"ArrowFunctionExpression"!==e.type&&"FunctionExpression"!==e.type||(this.enterScope(),t=!0),e.type){case"VariableDeclarator":this.handleVariableDeclarator(e);break;case"CallExpression":this.handleCallExpression(e);break;case"JSXElement":this.handleJSXElement(e)}for(const t in e){if("span"===t)continue;const n=e[t];if(Array.isArray(n))for(const e of n)e&&"object"==typeof e&&this.walk(e);else n&&n.type&&this.walk(n)}t&&this.exitScope()}enterScope(){this.scopeStack.push(new Map)}exitScope(){this.scopeStack.pop()}setVarInScope(e,t){this.scopeStack.length>0&&this.scopeStack[this.scopeStack.length-1].set(e,t)}getVarFromScope(e){for(let t=this.scopeStack.length-1;t>=0;t--)if(this.scopeStack[t].has(e))return this.scopeStack[t].get(e)}handleVariableDeclarator(e){const t=e.init;if(!t)return;const n="AwaitExpression"===t.type&&"CallExpression"===t.argument.type?t.argument:"CallExpression"===t.type?t:null;if(!n)return;const i=n.callee;if("Identifier"===i.type){const t=this.getUseTranslationConfig(i.value);if(t)return void this.handleUseTranslationDeclarator(e,n,t)}"MemberExpression"===i.type&&"Identifier"===i.property.type&&"getFixedT"===i.property.value&&this.handleGetFixedTDeclarator(e,n)}handleUseTranslationDeclarator(e,n,i){let r;if("Identifier"===e.id.type&&(r=e.id.value),"ArrayPattern"===e.id.type){const t=e.id.elements[0];"Identifier"===t?.type&&(r=t.value)}if("ObjectPattern"===e.id.type)for(const t of e.id.properties){if("AssignmentPatternProperty"===t.type&&"Identifier"===t.key.type&&"t"===t.key.value){r="t";break}if("KeyValuePatternProperty"===t.type&&"Identifier"===t.key.type&&"t"===t.key.value&&"Identifier"===t.value.type){r=t.value.value;break}}if(!r)return;const s=n.arguments?.[i.nsArg]?.expression;let a;"StringLiteral"===s?.type?a=s.value:"ArrayExpression"===s?.type&&"StringLiteral"===s.elements[0]?.expression.type&&(a=s.elements[0].expression.value);const o=n.arguments?.[i.keyPrefixArg]?.expression;let l;if("ObjectExpression"===o?.type){const e=t(o,"keyPrefix");l="string"==typeof e?e:void 0}this.setVarInScope(r,{defaultNs:a,keyPrefix:l})}handleGetFixedTDeclarator(e,t){if("Identifier"!==e.id.type||!e.init||"CallExpression"!==e.init.type)return;const n=e.id.value,i=t.arguments,r=i[1]?.expression,s=i[2]?.expression,a="StringLiteral"===r?.type?r.value:void 0,o="StringLiteral"===s?.type?s.value:void 0;(a||o)&&this.setVarInScope(n,{defaultNs:a,keyPrefix:o})}handleCallExpression(e){const i=this.getFunctionName(e.callee);if(!i)return;const r=this.getVarFromScope(i);if(!((this.config.extract.functions||["t"]).includes(i)||void 0!==r)||0===e.arguments.length)return;const s=e.arguments[0].expression;let a=[];if("StringLiteral"===s.type)a.push(s.value);else if("ArrowFunctionExpression"===s.type){const e=this.extractKeyFromSelector(s);e&&a.push(e)}else if("ArrayExpression"===s.type)for(const e of s.elements)"StringLiteral"===e?.expression.type&&a.push(e.expression.value);if(a=a.filter(e=>!!e),0===a.length)return;let o=!1;const l=this.config.extract.pluralSeparator??"_";for(let e=0;e<a.length;e++)a[e].endsWith(`${l}ordinal`)&&(o=!0,a[e]=a[e].slice(0,-8));let p,u;if(e.arguments.length>1){const t=e.arguments[1].expression;"ObjectExpression"===t.type?u=t:"StringLiteral"===t.type&&(p=t.value)}if(e.arguments.length>2){const t=e.arguments[2].expression;"ObjectExpression"===t.type&&(u=t)}const c=u?t(u,"defaultValue"):void 0,f="string"==typeof c?c:p;for(let e=0;e<a.length;e++){let i,s=a[e];if(u){const e=t(u,"ns");"string"==typeof e&&(i=e)}!i&&r?.defaultNs&&(i=r.defaultNs);const l=this.config.extract.nsSeparator??":";if(!i&&l&&s.includes(l)){const e=s.split(l);i=e.shift(),s=e.join(l)}i||(i=this.config.extract.defaultNS);let p=s;if(r?.keyPrefix){const e=this.config.extract.keySeparator??".";p=`${r.keyPrefix}${e}${s}`}const c=e===a.length-1&&f||s;if(u){const e=n(u,"context");if("ConditionalExpression"===e?.value?.type){const t=this.resolvePossibleStringValues(e.value),n=this.config.extract.contextSeparator??"_";if(t.length>0){t.forEach(e=>{this.pluginContext.addKey({key:`${p}${n}${e}`,ns:i,defaultValue:c})}),this.pluginContext.addKey({key:p,ns:i,defaultValue:c});continue}}const r=t(u,"context");if("string"==typeof r&&r){const e=this.config.extract.contextSeparator??"_";this.pluginContext.addKey({key:`${p}${e}${r}`,ns:i,defaultValue:c});continue}const s=void 0!==t(u,"count"),a=!0===t(u,"ordinal");if(s||o){this.handlePluralKeys(p,i,u,a||o);continue}!0===t(u,"returnObjects")&&this.objectKeys.add(p)}this.pluginContext.addKey({key:p,ns:i,defaultValue:c})}}handlePluralKeys(e,n,i,r){try{const s=r?"ordinal":"cardinal",a=new Intl.PluralRules(this.config.extract?.primaryLanguage,{type:s}).resolvedOptions().pluralCategories,o=this.config.extract.pluralSeparator??"_",l=t(i,"defaultValue"),p=t(i,`defaultValue${o}other`),u=t(i,`defaultValue${o}ordinal${o}other`);for(const s of a){const a=t(i,r?`defaultValue${o}ordinal${o}${s}`:`defaultValue${o}${s}`);let c;c="string"==typeof a?a:"one"===s&&"string"==typeof l?l:r&&"string"==typeof u?u:r||"string"!=typeof p?"string"==typeof l?l:e:p;const f=r?`${e}${o}ordinal${o}${s}`:`${e}${o}${s}`;this.pluginContext.addKey({key:f,ns:n,defaultValue:c,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 s=t(i,"defaultValue");this.pluginContext.addKey({key:e,ns:n,defaultValue:"string"==typeof s?s:e})}}handleSimplePluralKeys(e,t,n){try{const i=new Intl.PluralRules(this.config.extract?.primaryLanguage).resolvedOptions().pluralCategories,r=this.config.extract.pluralSeparator??"_";for(const s of i)this.pluginContext.addKey({key:`${e}${r}${s}`,ns:n,defaultValue:t,hasCount:!0})}catch(i){this.logger.warn(`Could not determine plural rules for language "${this.config.extract?.primaryLanguage}".`),this.pluginContext.addKey({key:e,ns:n,defaultValue:t})}}handleJSXElement(t){const n=this.getElementName(t);if(n&&(this.config.extract.transComponents||["Trans"]).includes(n)){const n=e(t,this.config);if(n){if(!n.ns){const e=t.opening.attributes?.find(e=>"JSXAttribute"===e.type&&"Identifier"===e.name.type&&"t"===e.name.value);if("JSXAttribute"===e?.type&&"JSXExpressionContainer"===e.value?.type&&"Identifier"===e.value.expression.type){const t=e.value.expression.value,i=this.getVarFromScope(t);i?.defaultNs&&(n.ns=i.defaultNs)}}if(n.ns||(n.ns=this.config.extract.defaultNS),n.contextExpression){const e=this.resolvePossibleStringValues(n.contextExpression),t=this.config.extract.contextSeparator??"_";if(e.length>0){for(const i of e)this.pluginContext.addKey({key:`${n.key}${t}${i}`,ns:n.ns,defaultValue:n.defaultValue});"StringLiteral"!==n.contextExpression.type&&this.pluginContext.addKey(n)}}else if(n.hasCount){const e=t.opening.attributes?.find(e=>"JSXAttribute"===e.type&&"Identifier"===e.name.type&&"ordinal"===e.name.value),i=!!e,r=n.optionsNode??{type:"ObjectExpression",properties:[],span:{start:0,end:0,ctxt:0}};r.properties.push({type:"KeyValueProperty",key:{type:"Identifier",value:"defaultValue",optional:!1,span:{start:0,end:0,ctxt:0}},value:{type:"StringLiteral",value:n.defaultValue,span:{start:0,end:0,ctxt:0}}}),this.handlePluralKeys(n.key,n.ns,r,i)}else this.pluginContext.addKey(n)}}}getElementName(e){if("Identifier"===e.opening.name.type)return e.opening.name.value;if("JSXMemberExpression"===e.opening.name.type){let t=e.opening.name;const n=[];for(;"JSXMemberExpression"===t.type;)"Identifier"===t.property.type&&n.unshift(t.property.value),t=t.object;return"Identifier"===t.type&&n.unshift(t.value),n.join(".")}}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 i=[];for(;"MemberExpression"===n.type;){const e=n.property;if("Identifier"===e.type)i.unshift(e.value);else{if("Computed"!==e.type||"StringLiteral"!==e.expression.type)return null;i.unshift(e.expression.value)}n=n.object}if(i.length>0){const e=this.config.extract.keySeparator,t="string"==typeof e?e:".";return i.join(t)}return null}resolvePossibleStringValues(e){if("StringLiteral"===e.type)return[e.value];if("ConditionalExpression"===e.type){return[...this.resolvePossibleStringValues(e.consequent),...this.resolvePossibleStringValues(e.alternate)]}return"Identifier"===e.type&&e.value,[]}getUseTranslationConfig(e){const t=this.config.extract.useTranslationNames||["useTranslation"];for(const n of t){if("string"==typeof n&&n===e)return{name:e,nsArg:0,keyPrefixArg:1};if("object"==typeof n&&n.name===e)return{name:n.name,nsArg:n.nsArg??0,keyPrefixArg:n.keyPrefixArg??1}}}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}return"Identifier"!==n.type?null:(t.unshift(n.value),t.join("."))}return null}}export{i as ASTVisitors};
|
|
@@ -1 +1 @@
|
|
|
1
|
-
function
|
|
1
|
+
import{getObjectPropValue as e,getObjectProperty as t}from"./ast-utils.js";function n(n,r){const a=n.opening.attributes?.find(e=>"JSXAttribute"===e.type&&"Identifier"===e.name.type&&"i18nKey"===e.name.value),u=n.opening.attributes?.find(e=>"JSXAttribute"===e.type&&"Identifier"===e.name.type&&"defaults"===e.name.value),p=n.opening.attributes?.find(e=>"JSXAttribute"===e.type&&"Identifier"===e.name.type&&"count"===e.name.value),l=!!p,s=n.opening.attributes?.find(e=>"JSXAttribute"===e.type&&"Identifier"===e.name.type&&"tOptions"===e.name.value),o="JSXAttribute"===s?.type&&"JSXExpressionContainer"===s.value?.type&&"ObjectExpression"===s.value.expression.type?s.value.expression:void 0,y=n.opening.attributes?.find(e=>"JSXAttribute"===e.type&&"Identifier"===e.name.type&&"context"===e.name.value);let v,f="JSXAttribute"===y?.type&&"JSXExpressionContainer"===y.value?.type?y.value.expression:void 0;if(v="JSXAttribute"===a?.type&&"StringLiteral"===a.value?.type?a.value.value:i(n.children,r),!v)return null;const d=n.opening.attributes?.find(e=>"JSXAttribute"===e.type&&"Identifier"===e.name.type&&"ns"===e.name.value);let c;if(c="JSXAttribute"===d?.type&&"StringLiteral"===d.value?.type?d.value.value:void 0,o&&(void 0===c&&(c=e(o,"ns")),void 0===f)){const e=t(o,"context");e?.value&&(f=e.value)}let S=r.extract.defaultValue||"";return S="JSXAttribute"===u?.type&&"StringLiteral"===u.value?.type?u.value.value:i(n.children,r),{key:v,ns:c,defaultValue:S||v,hasCount:l,contextExpression:f,optionsNode:o}}function i(e,t){const n=new Set(t.extract.transKeepBasicHtmlNodesFor??["br","strong","i","p"]);return function e(t){let i="";return t.forEach((t,r)=>{if("JSXText"===t.type)i+=t.value;else if("JSXExpressionContainer"===t.type){const e=t.expression;if("StringLiteral"===e.type)i+=e.value;else if("Identifier"===e.type)i+=`{{${e.value}}}`;else if("ObjectExpression"===e.type){const t=e.properties[0];t&&"Identifier"===t.type&&(i+=`{{${t.value}}}`)}}else if("JSXElement"===t.type){let a;"Identifier"===t.opening.name.type&&(a=t.opening.name.value);const u=e(t.children);a&&n.has(a)?i+=`<${a}>${u}</${a}>`:i+=`<${r}>${u}</${r}>`}else"JSXFragment"===t.type&&(i+=e(t.children))}),i}(e).trim().replace(/\s{2,}/g," ")}export{n as extractFromTransComponent};
|
package/dist/esm/locize.js
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
import{execa as e}from"execa";import o from"chalk";import
|
|
1
|
+
import{execa as e}from"execa";import o from"chalk";import n from"ora";import t from"inquirer";import{resolve as i}from"node:path";function s(e,o,n){const{locize:t={},extract:s}=o,{projectId:r,apiKey:c,version:a}=t,l=[e];if(r&&l.push("--project-id",r),c&&l.push("--api-key",c),a&&l.push("--ver",a),"sync"===e){(n.updateValues??t.updateValues)&&l.push("--update-values","true");(n.srcLngOnly??t.sourceLanguageOnly)&&l.push("--reference-language-only","true");(n.compareMtime??t.compareModificationTime)&&l.push("--compare-modification-time","true");(n.dryRun??t.dryRun)&&l.push("--dry","true")}const u=i(process.cwd(),s.output.split("/{{language}}/")[0]);return l.push("--path",u),l}async function r(i,r,c={}){await async function(){try{await e("locize",["--version"])}catch(e){"ENOENT"===e.code&&(console.error(o.red("Error: `locize-cli` command not found.")),console.log(o.yellow("Please install it globally to use the locize integration:")),console.log(o.cyan("npm install -g locize-cli")),process.exit(1))}}();const a=n(`Running 'locize ${i}'...\n`).start();let l=r;try{const n=s(i,l,c);console.log(o.cyan(`\nRunning 'locize ${n.join(" ")}'...`));const t=await e("locize",n,{stdio:"pipe"});a.succeed(o.green(`'locize ${i}' completed successfully.`)),t?.stdout&&console.log(t.stdout)}catch(n){const r=n.stderr||"";if(r.includes("missing required argument")){const n=await async function(){console.log(o.yellow("\nLocize configuration is missing or invalid. Let's set it up!"));const e=await t.prompt([{type:"input",name:"projectId",message:"What is your locize Project ID? (Find this in your project settings on www.locize.app)",validate:e=>!!e||"Project ID cannot be empty."},{type:"password",name:"apiKey",message:'What is your locize API key? (Create or use one in your project settings > "API Keys")',validate:e=>!!e||"API Key cannot be empty."},{type:"input",name:"version",message:"What version do you want to sync with?",default:"latest"}]);if(!e.projectId)return void console.error(o.red("Project ID is required to continue."));const{save:n}=await t.prompt([{type:"confirm",name:"save",message:"Would you like to see how to save these credentials for future use?",default:!0}]);if(n){const n=`\n# Add this to your .env file (and ensure .env is in your .gitignore!)\nLOCIZE_API_KEY=${e.apiKey}\n`,t=`\n // Add this to your i18next.config.ts file\n locize: {\n projectId: '${e.projectId}',\n // For security, apiKey is best set via an environment variable\n apiKey: process.env.LOCIZE_API_KEY,\n version: '${e.version}',\n },`;console.log(o.cyan("\nGreat! For the best security, we recommend using environment variables for your API key.")),console.log(o.bold("\nRecommended approach (.env file):")),console.log(o.green(n)),console.log(o.bold("Then, in your i18next.config.ts:")),console.log(o.green(t))}return{projectId:e.projectId,apiKey:e.apiKey,version:e.version}}();if(n){l={...l,locize:n},a.start("Retrying with new credentials...");try{const n=s(i,l,c);console.log(o.cyan(`\nRunning 'locize ${n.join(" ")}'...`));const t=await e("locize",n,{stdio:"pipe"});a.succeed(o.green("Retry successful!")),t?.stdout&&console.log(t.stdout)}catch(e){a.fail(o.red("Error during retry.")),console.error(e.stderr||e.message),process.exit(1)}}else a.fail("Operation cancelled."),process.exit(1)}else a.fail(o.red(`Error executing 'locize ${i}'.`)),console.error(r||n.message),process.exit(1)}console.log(o.green(`\n✅ 'locize ${i}' completed successfully.`))}const c=(e,o)=>r("sync",e,o),a=(e,o)=>r("download",e,o),l=(e,o)=>r("migrate",e,o);export{a as runLocizeDownload,l as runLocizeMigrate,c as runLocizeSync};
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "i18next-cli",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "1.0.0",
|
|
4
4
|
"description": "A unified, high-performance i18next CLI.",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"bin": {
|
|
@@ -52,16 +52,16 @@
|
|
|
52
52
|
"devDependencies": {
|
|
53
53
|
"@rollup/plugin-terser": "0.4.4",
|
|
54
54
|
"@types/inquirer": "9.0.9",
|
|
55
|
-
"@types/node": "24.
|
|
56
|
-
"@types/react": "19.1.
|
|
55
|
+
"@types/node": "24.6.1",
|
|
56
|
+
"@types/react": "19.1.16",
|
|
57
57
|
"@vitest/coverage-v8": "3.2.4",
|
|
58
58
|
"eslint": "9.36.0",
|
|
59
59
|
"eslint-plugin-import": "2.32.0",
|
|
60
|
-
"memfs": "4.
|
|
60
|
+
"memfs": "4.47.0",
|
|
61
61
|
"neostandard": "0.12.2",
|
|
62
62
|
"rollup-plugin-typescript2": "0.36.0",
|
|
63
63
|
"ts-node": "10.9.2",
|
|
64
|
-
"typescript": "5.9.
|
|
64
|
+
"typescript": "5.9.3",
|
|
65
65
|
"unplugin-swc": "1.5.7",
|
|
66
66
|
"vitest": "3.2.4"
|
|
67
67
|
},
|
|
@@ -74,7 +74,7 @@
|
|
|
74
74
|
"glob": "11.0.3",
|
|
75
75
|
"i18next-resources-for-ts": "1.7.4",
|
|
76
76
|
"inquirer": "12.9.6",
|
|
77
|
-
"jiti": "2.6.
|
|
77
|
+
"jiti": "2.6.1",
|
|
78
78
|
"jsonc-parser": "3.3.1",
|
|
79
79
|
"ora": "9.0.0",
|
|
80
80
|
"swc-walk": "1.0.0"
|
package/src/cli.ts
CHANGED
|
@@ -0,0 +1,52 @@
|
|
|
1
|
+
import type { ObjectExpression } from '@swc/core'
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* Finds and returns the full property node (KeyValueProperty) for the given
|
|
5
|
+
* property name from an ObjectExpression.
|
|
6
|
+
*
|
|
7
|
+
* Matches both identifier keys (e.g., { ns: 'value' }) and string literal keys
|
|
8
|
+
* (e.g., { 'ns': 'value' }).
|
|
9
|
+
*
|
|
10
|
+
* This helper returns the full property node rather than just its primitive
|
|
11
|
+
* value so callers can inspect expression types (ConditionalExpression, etc.).
|
|
12
|
+
*
|
|
13
|
+
* @private
|
|
14
|
+
* @param object - The SWC ObjectExpression to search
|
|
15
|
+
* @param propName - The property name to locate
|
|
16
|
+
* @returns The matching KeyValueProperty node if found, otherwise undefined.
|
|
17
|
+
*/
|
|
18
|
+
export function getObjectProperty (object: ObjectExpression, propName: string): any {
|
|
19
|
+
return (object.properties).find(
|
|
20
|
+
(p) =>
|
|
21
|
+
p.type === 'KeyValueProperty' &&
|
|
22
|
+
(
|
|
23
|
+
(p.key?.type === 'Identifier' && p.key.value === propName) ||
|
|
24
|
+
(p.key?.type === 'StringLiteral' && p.key.value === propName)
|
|
25
|
+
)
|
|
26
|
+
)
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
/**
|
|
30
|
+
* Extracts string value from object property.
|
|
31
|
+
*
|
|
32
|
+
* Looks for properties by name and returns their string values.
|
|
33
|
+
* Used for extracting options like 'ns', 'defaultValue', 'context', etc.
|
|
34
|
+
*
|
|
35
|
+
* @param object - Object expression to search
|
|
36
|
+
* @param propName - Property name to find
|
|
37
|
+
* @returns String value if found, empty string if property exists but isn't a string, undefined if not found
|
|
38
|
+
*
|
|
39
|
+
* @private
|
|
40
|
+
*/
|
|
41
|
+
export function getObjectPropValue (object: ObjectExpression, propName: string): string | boolean | number | undefined {
|
|
42
|
+
const prop = getObjectProperty(object, propName)
|
|
43
|
+
|
|
44
|
+
if (prop?.type === 'KeyValueProperty') {
|
|
45
|
+
const val = prop.value
|
|
46
|
+
if (val.type === 'StringLiteral') return val.value
|
|
47
|
+
if (val.type === 'BooleanLiteral') return val.value
|
|
48
|
+
if (val.type === 'NumericLiteral') return val.value
|
|
49
|
+
return '' // Indicate presence for other types
|
|
50
|
+
}
|
|
51
|
+
return undefined
|
|
52
|
+
}
|
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
import type { Module, Node, CallExpression, VariableDeclarator, JSXElement, ArrowFunctionExpression, ObjectExpression, Expression } from '@swc/core'
|
|
2
2
|
import type { PluginContext, I18nextToolkitConfig, Logger } from '../../types'
|
|
3
3
|
import { extractFromTransComponent } from './jsx-parser'
|
|
4
|
+
import { getObjectProperty, getObjectPropValue } from './ast-utils'
|
|
4
5
|
|
|
5
6
|
/**
|
|
6
7
|
* Represents variable scope information tracked during AST traversal.
|
|
@@ -308,7 +309,7 @@ export class ASTVisitors {
|
|
|
308
309
|
const optionsArg = callExpr.arguments?.[hookConfig.keyPrefixArg]?.expression
|
|
309
310
|
let keyPrefix: string | undefined
|
|
310
311
|
if (optionsArg?.type === 'ObjectExpression') {
|
|
311
|
-
const kp =
|
|
312
|
+
const kp = getObjectPropValue(optionsArg, 'keyPrefix')
|
|
312
313
|
keyPrefix = typeof kp === 'string' ? kp : undefined
|
|
313
314
|
}
|
|
314
315
|
|
|
@@ -374,7 +375,7 @@ export class ASTVisitors {
|
|
|
374
375
|
if (!isFunctionToParse || node.arguments.length === 0) return
|
|
375
376
|
|
|
376
377
|
const firstArg = node.arguments[0].expression
|
|
377
|
-
|
|
378
|
+
let keysToProcess: string[] = []
|
|
378
379
|
|
|
379
380
|
if (firstArg.type === 'StringLiteral') {
|
|
380
381
|
keysToProcess.push(firstArg.value)
|
|
@@ -390,6 +391,7 @@ export class ASTVisitors {
|
|
|
390
391
|
}
|
|
391
392
|
}
|
|
392
393
|
|
|
394
|
+
keysToProcess = keysToProcess.filter(key => !!key)
|
|
393
395
|
if (keysToProcess.length === 0) return
|
|
394
396
|
|
|
395
397
|
let isOrdinalByKey = false
|
|
@@ -420,7 +422,7 @@ export class ASTVisitors {
|
|
|
420
422
|
options = arg3
|
|
421
423
|
}
|
|
422
424
|
}
|
|
423
|
-
const defaultValueFromOptions = options ?
|
|
425
|
+
const defaultValueFromOptions = options ? getObjectPropValue(options, 'defaultValue') : undefined
|
|
424
426
|
const finalDefaultValue = (typeof defaultValueFromOptions === 'string' ? defaultValueFromOptions : defaultValue)
|
|
425
427
|
|
|
426
428
|
// Loop through each key found (could be one or more) and process it
|
|
@@ -430,7 +432,7 @@ export class ASTVisitors {
|
|
|
430
432
|
|
|
431
433
|
// Determine namespace (explicit ns > scope ns > ns:key > default)
|
|
432
434
|
if (options) {
|
|
433
|
-
const nsVal =
|
|
435
|
+
const nsVal = getObjectPropValue(options, 'ns')
|
|
434
436
|
if (typeof nsVal === 'string') ns = nsVal
|
|
435
437
|
}
|
|
436
438
|
if (!ns && scopeInfo?.defaultNs) ns = scopeInfo.defaultNs
|
|
@@ -454,7 +456,7 @@ export class ASTVisitors {
|
|
|
454
456
|
|
|
455
457
|
// Handle plurals, context, and returnObjects
|
|
456
458
|
if (options) {
|
|
457
|
-
const contextProp =
|
|
459
|
+
const contextProp = getObjectProperty(options, 'context')
|
|
458
460
|
|
|
459
461
|
// 1. Handle Dynamic Context (Ternary) first
|
|
460
462
|
if (contextProp?.value?.type === 'ConditionalExpression') {
|
|
@@ -472,7 +474,7 @@ export class ASTVisitors {
|
|
|
472
474
|
}
|
|
473
475
|
|
|
474
476
|
// 2. Handle Static Context
|
|
475
|
-
const contextValue =
|
|
477
|
+
const contextValue = getObjectPropValue(options, 'context')
|
|
476
478
|
if (typeof contextValue === 'string' && contextValue) {
|
|
477
479
|
const contextSeparator = this.config.extract.contextSeparator ?? '_'
|
|
478
480
|
this.pluginContext.addKey({ key: `${finalKey}${contextSeparator}${contextValue}`, ns, defaultValue: dv })
|
|
@@ -480,16 +482,16 @@ export class ASTVisitors {
|
|
|
480
482
|
}
|
|
481
483
|
|
|
482
484
|
// 3. Handle Plurals
|
|
483
|
-
const hasCount =
|
|
484
|
-
const isOrdinalByOption =
|
|
485
|
+
const hasCount = getObjectPropValue(options, 'count') !== undefined
|
|
486
|
+
const isOrdinalByOption = getObjectPropValue(options, 'ordinal') === true
|
|
485
487
|
if (hasCount || isOrdinalByKey) {
|
|
486
|
-
|
|
488
|
+
// Pass the combined ordinal flag to the handler
|
|
487
489
|
this.handlePluralKeys(finalKey, ns, options, isOrdinalByOption || isOrdinalByKey)
|
|
488
490
|
continue
|
|
489
491
|
}
|
|
490
492
|
|
|
491
493
|
// 4. Handle returnObjects
|
|
492
|
-
if (
|
|
494
|
+
if (getObjectPropValue(options, 'returnObjects') === true) {
|
|
493
495
|
this.objectKeys.add(finalKey)
|
|
494
496
|
// Fall through to add the base key itself
|
|
495
497
|
}
|
|
@@ -522,14 +524,14 @@ export class ASTVisitors {
|
|
|
522
524
|
const pluralSeparator = this.config.extract.pluralSeparator ?? '_'
|
|
523
525
|
|
|
524
526
|
// Get all possible default values once at the start
|
|
525
|
-
const defaultValue =
|
|
526
|
-
const otherDefault =
|
|
527
|
-
const ordinalOtherDefault =
|
|
527
|
+
const defaultValue = getObjectPropValue(options, 'defaultValue')
|
|
528
|
+
const otherDefault = getObjectPropValue(options, `defaultValue${pluralSeparator}other`)
|
|
529
|
+
const ordinalOtherDefault = getObjectPropValue(options, `defaultValue${pluralSeparator}ordinal${pluralSeparator}other`)
|
|
528
530
|
|
|
529
531
|
for (const category of pluralCategories) {
|
|
530
532
|
// 1. Look for the most specific default value (e.g., defaultValue_ordinal_one)
|
|
531
|
-
const specificDefaultKey = isOrdinal ? `
|
|
532
|
-
const specificDefault =
|
|
533
|
+
const specificDefaultKey = isOrdinal ? `defaultValue${pluralSeparator}ordinal${pluralSeparator}${category}` : `defaultValue${pluralSeparator}${category}`
|
|
534
|
+
const specificDefault = getObjectPropValue(options, specificDefaultKey)
|
|
533
535
|
|
|
534
536
|
// 2. Determine the final default value using a clear fallback chain
|
|
535
537
|
let finalDefaultValue: string | undefined
|
|
@@ -569,7 +571,7 @@ export class ASTVisitors {
|
|
|
569
571
|
} catch (e) {
|
|
570
572
|
this.logger.warn(`Could not determine plural rules for language "${this.config.extract?.primaryLanguage}". Falling back to simple key extraction.`)
|
|
571
573
|
// Fallback to a simple key if Intl API fails
|
|
572
|
-
const defaultValue =
|
|
574
|
+
const defaultValue = getObjectPropValue(options, 'defaultValue')
|
|
573
575
|
this.pluginContext.addKey({ key, ns, defaultValue: typeof defaultValue === 'string' ? defaultValue : key })
|
|
574
576
|
}
|
|
575
577
|
}
|
|
@@ -617,20 +619,20 @@ export class ASTVisitors {
|
|
|
617
619
|
if (elementName && (this.config.extract.transComponents || ['Trans']).includes(elementName)) {
|
|
618
620
|
const extractedKey = extractFromTransComponent(node, this.config)
|
|
619
621
|
if (extractedKey) {
|
|
620
|
-
|
|
622
|
+
// If ns is not explicitly set on the component, try to find it from the `t` prop
|
|
621
623
|
if (!extractedKey.ns) {
|
|
622
624
|
const tProp = node.opening.attributes?.find(
|
|
623
625
|
attr =>
|
|
624
626
|
attr.type === 'JSXAttribute' &&
|
|
625
|
-
|
|
626
|
-
|
|
627
|
+
attr.name.type === 'Identifier' &&
|
|
628
|
+
attr.name.value === 't'
|
|
627
629
|
)
|
|
628
630
|
|
|
629
631
|
// Check if the prop value is an identifier (e.g., t={t})
|
|
630
632
|
if (
|
|
631
633
|
tProp?.type === 'JSXAttribute' &&
|
|
632
|
-
|
|
633
|
-
|
|
634
|
+
tProp.value?.type === 'JSXExpressionContainer' &&
|
|
635
|
+
tProp.value.expression.type === 'Identifier'
|
|
634
636
|
) {
|
|
635
637
|
const tIdentifier = tProp.value.expression.value
|
|
636
638
|
const scopeInfo = this.getVarFromScope(tIdentifier)
|
|
@@ -653,15 +655,35 @@ export class ASTVisitors {
|
|
|
653
655
|
for (const context of contextValues) {
|
|
654
656
|
this.pluginContext.addKey({ key: `${extractedKey.key}${contextSeparator}${context}`, ns: extractedKey.ns, defaultValue: extractedKey.defaultValue })
|
|
655
657
|
}
|
|
656
|
-
//
|
|
657
|
-
|
|
658
|
+
// Only add the base key as a fallback if the context is dynamic (i.e., not a simple string).
|
|
659
|
+
if (extractedKey.contextExpression.type !== 'StringLiteral') {
|
|
660
|
+
this.pluginContext.addKey(extractedKey)
|
|
661
|
+
}
|
|
658
662
|
}
|
|
659
663
|
} else if (extractedKey.hasCount) {
|
|
660
|
-
|
|
664
|
+
// Find isOrdinal prop on the <Trans> component
|
|
665
|
+
const ordinalAttr = node.opening.attributes?.find(
|
|
666
|
+
(attr) =>
|
|
667
|
+
attr.type === 'JSXAttribute' &&
|
|
668
|
+
attr.name.type === 'Identifier' &&
|
|
669
|
+
attr.name.value === 'ordinal'
|
|
670
|
+
)
|
|
671
|
+
const isOrdinal = !!ordinalAttr
|
|
672
|
+
|
|
673
|
+
// If tOptions are provided, use the advanced plural handler
|
|
674
|
+
const optionsNode = extractedKey.optionsNode ?? { type: 'ObjectExpression', properties: [], span: { start: 0, end: 0, ctxt: 0 } }
|
|
675
|
+
|
|
676
|
+
// Inject the defaultValue from children into the options object
|
|
677
|
+
optionsNode.properties.push({
|
|
678
|
+
type: 'KeyValueProperty',
|
|
679
|
+
key: { type: 'Identifier', value: 'defaultValue', optional: false, span: { start: 0, end: 0, ctxt: 0 } },
|
|
680
|
+
value: { type: 'StringLiteral', value: extractedKey.defaultValue!, span: { start: 0, end: 0, ctxt: 0 } }
|
|
681
|
+
})
|
|
682
|
+
|
|
683
|
+
this.handlePluralKeys(extractedKey.key, extractedKey.ns, optionsNode, isOrdinal)
|
|
661
684
|
} else {
|
|
662
685
|
this.pluginContext.addKey(extractedKey)
|
|
663
686
|
}
|
|
664
|
-
// The duplicated addKey call has been removed.
|
|
665
687
|
}
|
|
666
688
|
}
|
|
667
689
|
}
|
|
@@ -694,47 +716,6 @@ export class ASTVisitors {
|
|
|
694
716
|
return undefined
|
|
695
717
|
}
|
|
696
718
|
|
|
697
|
-
/**
|
|
698
|
-
* Extracts string value from object property.
|
|
699
|
-
*
|
|
700
|
-
* Looks for properties by name and returns their string values.
|
|
701
|
-
* Used for extracting options like 'ns', 'defaultValue', 'context', etc.
|
|
702
|
-
*
|
|
703
|
-
* @param object - Object expression to search
|
|
704
|
-
* @param propName - Property name to find
|
|
705
|
-
* @returns String value if found, empty string if property exists but isn't a string, undefined if not found
|
|
706
|
-
*
|
|
707
|
-
* @private
|
|
708
|
-
*/
|
|
709
|
-
private getObjectPropValue (object: ObjectExpression, propName: string): string | boolean | number | undefined {
|
|
710
|
-
const prop = (object.properties).find(
|
|
711
|
-
(p) =>
|
|
712
|
-
p.type === 'KeyValueProperty' &&
|
|
713
|
-
(
|
|
714
|
-
(p.key?.type === 'Identifier' && p.key.value === propName) ||
|
|
715
|
-
(p.key?.type === 'StringLiteral' && p.key.value === propName)
|
|
716
|
-
)
|
|
717
|
-
)
|
|
718
|
-
|
|
719
|
-
if (prop?.type === 'KeyValueProperty') {
|
|
720
|
-
const val = prop.value
|
|
721
|
-
// Return concrete literal values when possible
|
|
722
|
-
if (val.type === 'StringLiteral') {
|
|
723
|
-
return val.value
|
|
724
|
-
}
|
|
725
|
-
if (val.type === 'BooleanLiteral') {
|
|
726
|
-
return val.value
|
|
727
|
-
}
|
|
728
|
-
if (val.type === 'NumericLiteral') {
|
|
729
|
-
return val.value
|
|
730
|
-
}
|
|
731
|
-
// For other expression types (identifier, member expr, etc.) we only care that the prop exists.
|
|
732
|
-
// Return an empty string to indicate presence.
|
|
733
|
-
return ''
|
|
734
|
-
}
|
|
735
|
-
return undefined
|
|
736
|
-
}
|
|
737
|
-
|
|
738
719
|
/**
|
|
739
720
|
* Extracts translation key from selector API arrow function.
|
|
740
721
|
*
|
|
@@ -829,32 +810,6 @@ export class ASTVisitors {
|
|
|
829
810
|
return []
|
|
830
811
|
}
|
|
831
812
|
|
|
832
|
-
/**
|
|
833
|
-
* Finds and returns the full property node (KeyValueProperty) for the given
|
|
834
|
-
* property name from an ObjectExpression.
|
|
835
|
-
*
|
|
836
|
-
* Matches both identifier keys (e.g., { ns: 'value' }) and string literal keys
|
|
837
|
-
* (e.g., { 'ns': 'value' }).
|
|
838
|
-
*
|
|
839
|
-
* This helper returns the full property node rather than just its primitive
|
|
840
|
-
* value so callers can inspect expression types (ConditionalExpression, etc.).
|
|
841
|
-
*
|
|
842
|
-
* @private
|
|
843
|
-
* @param object - The SWC ObjectExpression to search
|
|
844
|
-
* @param propName - The property name to locate
|
|
845
|
-
* @returns The matching KeyValueProperty node if found, otherwise undefined.
|
|
846
|
-
*/
|
|
847
|
-
private getObjectProperty (object: ObjectExpression, propName: string): any {
|
|
848
|
-
return (object.properties).find(
|
|
849
|
-
(p) =>
|
|
850
|
-
p.type === 'KeyValueProperty' &&
|
|
851
|
-
(
|
|
852
|
-
(p.key?.type === 'Identifier' && p.key.value === propName) ||
|
|
853
|
-
(p.key?.type === 'StringLiteral' && p.key.value === propName)
|
|
854
|
-
)
|
|
855
|
-
)
|
|
856
|
-
}
|
|
857
|
-
|
|
858
813
|
/**
|
|
859
814
|
* Finds the configuration for a given useTranslation function name.
|
|
860
815
|
* Applies default argument positions if none are specified.
|
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
import type { JSXElement } from '@swc/core'
|
|
2
2
|
import type { ExtractedKey, I18nextToolkitConfig } from '../../types'
|
|
3
|
+
import { getObjectProperty, getObjectPropValue } from './ast-utils'
|
|
3
4
|
|
|
4
5
|
/**
|
|
5
6
|
* Extracts translation keys from JSX Trans components.
|
|
@@ -55,13 +56,23 @@ export function extractFromTransComponent (node: JSXElement, config: I18nextTool
|
|
|
55
56
|
)
|
|
56
57
|
const hasCount = !!countAttr
|
|
57
58
|
|
|
59
|
+
const tOptionsAttr = node.opening.attributes?.find(
|
|
60
|
+
(attr) =>
|
|
61
|
+
attr.type === 'JSXAttribute' &&
|
|
62
|
+
attr.name.type === 'Identifier' &&
|
|
63
|
+
attr.name.value === 'tOptions'
|
|
64
|
+
)
|
|
65
|
+
const optionsNode = (tOptionsAttr?.type === 'JSXAttribute' && tOptionsAttr.value?.type === 'JSXExpressionContainer' && tOptionsAttr.value.expression.type === 'ObjectExpression')
|
|
66
|
+
? tOptionsAttr.value.expression
|
|
67
|
+
: undefined
|
|
68
|
+
|
|
58
69
|
const contextAttr = node.opening.attributes?.find(
|
|
59
70
|
(attr) =>
|
|
60
71
|
attr.type === 'JSXAttribute' &&
|
|
61
72
|
attr.name.type === 'Identifier' &&
|
|
62
73
|
attr.name.value === 'context'
|
|
63
74
|
)
|
|
64
|
-
|
|
75
|
+
let contextExpression = (contextAttr?.type === 'JSXAttribute' && contextAttr.value?.type === 'JSXExpressionContainer')
|
|
65
76
|
? contextAttr.value.expression
|
|
66
77
|
: undefined
|
|
67
78
|
|
|
@@ -76,13 +87,27 @@ export function extractFromTransComponent (node: JSXElement, config: I18nextTool
|
|
|
76
87
|
return null
|
|
77
88
|
}
|
|
78
89
|
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
)
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
90
|
+
// 1. Prioritize direct props for 'ns' and 'context'
|
|
91
|
+
const nsAttr = node.opening.attributes?.find(attr => attr.type === 'JSXAttribute' && attr.name.type === 'Identifier' && attr.name.value === 'ns')
|
|
92
|
+
let ns: string | undefined
|
|
93
|
+
if (nsAttr?.type === 'JSXAttribute' && nsAttr.value?.type === 'StringLiteral') {
|
|
94
|
+
ns = nsAttr.value.value
|
|
95
|
+
} else {
|
|
96
|
+
ns = undefined
|
|
97
|
+
}
|
|
98
|
+
|
|
99
|
+
// 2. If not found, fall back to looking inside tOptions
|
|
100
|
+
if (optionsNode) {
|
|
101
|
+
if (ns === undefined) {
|
|
102
|
+
ns = getObjectPropValue(optionsNode, 'ns') as string | undefined
|
|
103
|
+
}
|
|
104
|
+
if (contextExpression === undefined) {
|
|
105
|
+
const contextPropFromOptions = getObjectProperty(optionsNode, 'context')
|
|
106
|
+
if (contextPropFromOptions?.value) {
|
|
107
|
+
contextExpression = contextPropFromOptions.value
|
|
108
|
+
}
|
|
109
|
+
}
|
|
110
|
+
}
|
|
86
111
|
|
|
87
112
|
let defaultValue = config.extract.defaultValue || ''
|
|
88
113
|
if (defaultsAttr?.type === 'JSXAttribute' && defaultsAttr.value?.type === 'StringLiteral') {
|
|
@@ -91,7 +116,7 @@ export function extractFromTransComponent (node: JSXElement, config: I18nextTool
|
|
|
91
116
|
defaultValue = serializeJSXChildren(node.children, config)
|
|
92
117
|
}
|
|
93
118
|
|
|
94
|
-
return { key, ns, defaultValue: defaultValue || key, hasCount, contextExpression }
|
|
119
|
+
return { key, ns, defaultValue: defaultValue || key, hasCount, contextExpression, optionsNode }
|
|
95
120
|
}
|
|
96
121
|
|
|
97
122
|
/**
|
package/src/locize.ts
CHANGED
|
@@ -81,13 +81,6 @@ async function interactiveCredentialSetup (config: I18nextToolkitConfig): Promis
|
|
|
81
81
|
return undefined
|
|
82
82
|
}
|
|
83
83
|
|
|
84
|
-
// Use the entered credentials for the current run
|
|
85
|
-
config.locize = {
|
|
86
|
-
projectId: answers.projectId,
|
|
87
|
-
apiKey: answers.apiKey,
|
|
88
|
-
version: answers.version,
|
|
89
|
-
}
|
|
90
|
-
|
|
91
84
|
const { save } = await inquirer.prompt([{
|
|
92
85
|
type: 'confirm',
|
|
93
86
|
name: 'save',
|
|
@@ -116,33 +109,29 @@ LOCIZE_API_KEY=${answers.apiKey}
|
|
|
116
109
|
console.log(chalk.green(configSnippet))
|
|
117
110
|
}
|
|
118
111
|
|
|
119
|
-
return
|
|
112
|
+
return {
|
|
113
|
+
projectId: answers.projectId,
|
|
114
|
+
apiKey: answers.apiKey,
|
|
115
|
+
version: answers.version,
|
|
116
|
+
}
|
|
120
117
|
}
|
|
121
118
|
|
|
122
119
|
/**
|
|
123
|
-
*
|
|
124
|
-
*
|
|
125
|
-
* Maps toolkit configuration and CLI flags to the appropriate locize-cli arguments:
|
|
126
|
-
* - `updateValues` → `--update-values`
|
|
127
|
-
* - `sourceLanguageOnly` → `--reference-language-only`
|
|
128
|
-
* - `compareModificationTime` → `--compare-modification-time`
|
|
129
|
-
* - `dryRun` → `--dry`
|
|
130
|
-
*
|
|
131
|
-
* @param command - The locize command being executed
|
|
132
|
-
* @param cliOptions - CLI options passed to the command
|
|
133
|
-
* @param locizeConfig - Locize configuration from the config file
|
|
134
|
-
* @returns Array of command-line arguments
|
|
135
|
-
*
|
|
136
|
-
* @example
|
|
137
|
-
* ```typescript
|
|
138
|
-
* const args = cliOptionsToArgs('sync', { updateValues: true }, { dryRun: false })
|
|
139
|
-
* // Returns: ['--update-values', 'true']
|
|
140
|
-
* ```
|
|
120
|
+
* Helper function to build the array of arguments for the execa call.
|
|
121
|
+
* This ensures the logic is consistent for both the initial run and the retry.
|
|
141
122
|
*/
|
|
142
|
-
function
|
|
143
|
-
const
|
|
123
|
+
function buildArgs (command: string, config: I18nextToolkitConfig, cliOptions: any): string[] {
|
|
124
|
+
const { locize: locizeConfig = {}, extract } = config
|
|
125
|
+
const { projectId, apiKey, version } = locizeConfig
|
|
126
|
+
|
|
127
|
+
const commandArgs: string[] = [command]
|
|
128
|
+
|
|
129
|
+
if (projectId) commandArgs.push('--project-id', projectId)
|
|
130
|
+
if (apiKey) commandArgs.push('--api-key', apiKey)
|
|
131
|
+
if (version) commandArgs.push('--ver', version)
|
|
132
|
+
// TODO: there might be more configurable locize-cli options in future
|
|
144
133
|
|
|
145
|
-
// Pass-through options
|
|
134
|
+
// Pass-through options from the CLI
|
|
146
135
|
if (command === 'sync') {
|
|
147
136
|
const updateValues = cliOptions.updateValues ?? locizeConfig.updateValues
|
|
148
137
|
if (updateValues) commandArgs.push('--update-values', 'true')
|
|
@@ -154,6 +143,9 @@ function cliOptionsToArgs (command: 'sync' | 'download' | 'migrate', cliOptions:
|
|
|
154
143
|
if (dryRun) commandArgs.push('--dry', 'true')
|
|
155
144
|
}
|
|
156
145
|
|
|
146
|
+
const basePath = resolve(process.cwd(), extract.output.split('/{{language}}/')[0])
|
|
147
|
+
commandArgs.push('--path', basePath)
|
|
148
|
+
|
|
157
149
|
return commandArgs
|
|
158
150
|
}
|
|
159
151
|
|
|
@@ -189,44 +181,33 @@ async function runLocizeCommand (command: 'sync' | 'download' | 'migrate', confi
|
|
|
189
181
|
|
|
190
182
|
const spinner = ora(`Running 'locize ${command}'...\n`).start()
|
|
191
183
|
|
|
192
|
-
|
|
193
|
-
const { projectId, apiKey, version } = locizeConfig
|
|
194
|
-
let commandArgs: string[] = [command]
|
|
195
|
-
|
|
196
|
-
if (projectId) commandArgs.push('--project-id', projectId)
|
|
197
|
-
if (apiKey) commandArgs.push('--api-key', apiKey)
|
|
198
|
-
if (version) commandArgs.push('--ver', version)
|
|
199
|
-
// TODO: there might be more configurable locize-cli options in future
|
|
200
|
-
|
|
201
|
-
commandArgs.push(...cliOptionsToArgs(command, cliOptions, locizeConfig))
|
|
202
|
-
|
|
203
|
-
const basePath = resolve(process.cwd(), config.extract.output.split('/{{language}}/')[0])
|
|
204
|
-
commandArgs.push('--path', basePath)
|
|
184
|
+
let effectiveConfig = config
|
|
205
185
|
|
|
206
186
|
try {
|
|
207
|
-
|
|
208
|
-
const
|
|
187
|
+
// 1. First attempt
|
|
188
|
+
const initialArgs = buildArgs(command, effectiveConfig, cliOptions)
|
|
189
|
+
console.log(chalk.cyan(`\nRunning 'locize ${initialArgs.join(' ')}'...`))
|
|
190
|
+
const result = await execa('locize', initialArgs, { stdio: 'pipe' })
|
|
191
|
+
|
|
209
192
|
spinner.succeed(chalk.green(`'locize ${command}' completed successfully.`))
|
|
210
193
|
if (result?.stdout) console.log(result.stdout) // Print captured output on success
|
|
211
194
|
} catch (error: any) {
|
|
212
195
|
const stderr = error.stderr || ''
|
|
213
196
|
if (stderr.includes('missing required argument')) {
|
|
214
|
-
//
|
|
215
|
-
const newCredentials = await interactiveCredentialSetup(
|
|
197
|
+
// 2. Auth failure, trigger interactive setup
|
|
198
|
+
const newCredentials = await interactiveCredentialSetup(effectiveConfig)
|
|
216
199
|
if (newCredentials) {
|
|
217
|
-
|
|
218
|
-
|
|
219
|
-
|
|
220
|
-
if (newCredentials.apiKey) commandArgs.push('--api-key', newCredentials.apiKey)
|
|
221
|
-
if (newCredentials.version) commandArgs.push('--ver', newCredentials.version)
|
|
222
|
-
// TODO: there might be more configurable locize-cli options in future
|
|
223
|
-
commandArgs.push(...cliOptionsToArgs(command, cliOptions, locizeConfig))
|
|
224
|
-
commandArgs.push('--path', basePath)
|
|
200
|
+
effectiveConfig = { ...effectiveConfig, locize: newCredentials }
|
|
201
|
+
|
|
202
|
+
spinner.start('Retrying with new credentials...')
|
|
225
203
|
try {
|
|
226
|
-
|
|
227
|
-
const
|
|
204
|
+
// 3. Retry attempt, rebuilding args with the NOW-UPDATED currentConfig object
|
|
205
|
+
const retryArgs = buildArgs(command, effectiveConfig, cliOptions)
|
|
206
|
+
console.log(chalk.cyan(`\nRunning 'locize ${retryArgs.join(' ')}'...`))
|
|
207
|
+
const result = await execa('locize', retryArgs, { stdio: 'pipe' })
|
|
208
|
+
|
|
228
209
|
spinner.succeed(chalk.green('Retry successful!'))
|
|
229
|
-
if (result?.stdout) console.log(result.stdout)
|
|
210
|
+
if (result?.stdout) console.log(result.stdout)
|
|
230
211
|
} catch (retryError: any) {
|
|
231
212
|
spinner.fail(chalk.red('Error during retry.'))
|
|
232
213
|
console.error(retryError.stderr || retryError.message)
|
package/src/types.ts
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import type { Node, Expression } from '@swc/core'
|
|
1
|
+
import type { Node, Expression, ObjectExpression } from '@swc/core'
|
|
2
2
|
|
|
3
3
|
/**
|
|
4
4
|
* Main configuration interface for the i18next toolkit.
|
|
@@ -254,6 +254,9 @@ export interface ExtractedKey {
|
|
|
254
254
|
/** Whether this key is used with ordinal pluralization */
|
|
255
255
|
isOrdinal?: boolean;
|
|
256
256
|
|
|
257
|
+
/** AST node for options object, used for advanced plural handling in Trans */
|
|
258
|
+
optionsNode?: ObjectExpression;
|
|
259
|
+
|
|
257
260
|
/** hold the raw context expression from the AST */
|
|
258
261
|
contextExpression?: Expression;
|
|
259
262
|
}
|
|
@@ -0,0 +1,31 @@
|
|
|
1
|
+
import type { ObjectExpression } from '@swc/core';
|
|
2
|
+
/**
|
|
3
|
+
* Finds and returns the full property node (KeyValueProperty) for the given
|
|
4
|
+
* property name from an ObjectExpression.
|
|
5
|
+
*
|
|
6
|
+
* Matches both identifier keys (e.g., { ns: 'value' }) and string literal keys
|
|
7
|
+
* (e.g., { 'ns': 'value' }).
|
|
8
|
+
*
|
|
9
|
+
* This helper returns the full property node rather than just its primitive
|
|
10
|
+
* value so callers can inspect expression types (ConditionalExpression, etc.).
|
|
11
|
+
*
|
|
12
|
+
* @private
|
|
13
|
+
* @param object - The SWC ObjectExpression to search
|
|
14
|
+
* @param propName - The property name to locate
|
|
15
|
+
* @returns The matching KeyValueProperty node if found, otherwise undefined.
|
|
16
|
+
*/
|
|
17
|
+
export declare function getObjectProperty(object: ObjectExpression, propName: string): any;
|
|
18
|
+
/**
|
|
19
|
+
* Extracts string value from object property.
|
|
20
|
+
*
|
|
21
|
+
* Looks for properties by name and returns their string values.
|
|
22
|
+
* Used for extracting options like 'ns', 'defaultValue', 'context', etc.
|
|
23
|
+
*
|
|
24
|
+
* @param object - Object expression to search
|
|
25
|
+
* @param propName - Property name to find
|
|
26
|
+
* @returns String value if found, empty string if property exists but isn't a string, undefined if not found
|
|
27
|
+
*
|
|
28
|
+
* @private
|
|
29
|
+
*/
|
|
30
|
+
export declare function getObjectPropValue(object: ObjectExpression, propName: string): string | boolean | number | undefined;
|
|
31
|
+
//# sourceMappingURL=ast-utils.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"ast-utils.d.ts","sourceRoot":"","sources":["../../../src/extractor/parsers/ast-utils.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,gBAAgB,EAAE,MAAM,WAAW,CAAA;AAEjD;;;;;;;;;;;;;;GAcG;AACH,wBAAgB,iBAAiB,CAAE,MAAM,EAAE,gBAAgB,EAAE,QAAQ,EAAE,MAAM,GAAG,GAAG,CASlF;AAED;;;;;;;;;;;GAWG;AACH,wBAAgB,kBAAkB,CAAE,MAAM,EAAE,gBAAgB,EAAE,QAAQ,EAAE,MAAM,GAAG,MAAM,GAAG,OAAO,GAAG,MAAM,GAAG,SAAS,CAWrH"}
|
|
@@ -201,19 +201,6 @@ export declare class ASTVisitors {
|
|
|
201
201
|
* @private
|
|
202
202
|
*/
|
|
203
203
|
private getElementName;
|
|
204
|
-
/**
|
|
205
|
-
* Extracts string value from object property.
|
|
206
|
-
*
|
|
207
|
-
* Looks for properties by name and returns their string values.
|
|
208
|
-
* Used for extracting options like 'ns', 'defaultValue', 'context', etc.
|
|
209
|
-
*
|
|
210
|
-
* @param object - Object expression to search
|
|
211
|
-
* @param propName - Property name to find
|
|
212
|
-
* @returns String value if found, empty string if property exists but isn't a string, undefined if not found
|
|
213
|
-
*
|
|
214
|
-
* @private
|
|
215
|
-
*/
|
|
216
|
-
private getObjectPropValue;
|
|
217
204
|
/**
|
|
218
205
|
* Extracts translation key from selector API arrow function.
|
|
219
206
|
*
|
|
@@ -248,22 +235,6 @@ export declare class ASTVisitors {
|
|
|
248
235
|
* @returns An array of possible string values that the expression may produce.
|
|
249
236
|
*/
|
|
250
237
|
private resolvePossibleStringValues;
|
|
251
|
-
/**
|
|
252
|
-
* Finds and returns the full property node (KeyValueProperty) for the given
|
|
253
|
-
* property name from an ObjectExpression.
|
|
254
|
-
*
|
|
255
|
-
* Matches both identifier keys (e.g., { ns: 'value' }) and string literal keys
|
|
256
|
-
* (e.g., { 'ns': 'value' }).
|
|
257
|
-
*
|
|
258
|
-
* This helper returns the full property node rather than just its primitive
|
|
259
|
-
* value so callers can inspect expression types (ConditionalExpression, etc.).
|
|
260
|
-
*
|
|
261
|
-
* @private
|
|
262
|
-
* @param object - The SWC ObjectExpression to search
|
|
263
|
-
* @param propName - The property name to locate
|
|
264
|
-
* @returns The matching KeyValueProperty node if found, otherwise undefined.
|
|
265
|
-
*/
|
|
266
|
-
private getObjectProperty;
|
|
267
238
|
/**
|
|
268
239
|
* Finds the configuration for a given useTranslation function name.
|
|
269
240
|
* Applies default argument positions if none are specified.
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"ast-visitors.d.ts","sourceRoot":"","sources":["../../../src/extractor/parsers/ast-visitors.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,MAAM,EAA+G,MAAM,WAAW,CAAA;AACpJ,OAAO,KAAK,EAAE,aAAa,EAAE,oBAAoB,EAAE,MAAM,EAAE,MAAM,aAAa,CAAA;
|
|
1
|
+
{"version":3,"file":"ast-visitors.d.ts","sourceRoot":"","sources":["../../../src/extractor/parsers/ast-visitors.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,MAAM,EAA+G,MAAM,WAAW,CAAA;AACpJ,OAAO,KAAK,EAAE,aAAa,EAAE,oBAAoB,EAAE,MAAM,EAAE,MAAM,aAAa,CAAA;AAqB9E;;;;;;;;;;;;;;;;;;;;;GAqBG;AACH,qBAAa,WAAW;IACtB,OAAO,CAAC,QAAQ,CAAC,aAAa,CAAe;IAC7C,OAAO,CAAC,QAAQ,CAAC,MAAM,CAAsB;IAC7C,OAAO,CAAC,QAAQ,CAAC,MAAM,CAAQ;IAC/B,OAAO,CAAC,UAAU,CAAoC;IAE/C,UAAU,cAAoB;IAErC;;;;;;OAMG;gBAED,MAAM,EAAE,oBAAoB,EAC5B,aAAa,EAAE,aAAa,EAC5B,MAAM,EAAE,MAAM;IAOhB;;;;;OAKG;IACI,KAAK,CAAE,IAAI,EAAE,MAAM,GAAG,IAAI;IAMjC;;;;;;;;;;;;OAYG;IACH,OAAO,CAAC,IAAI;IAoDZ;;;;;OAKG;IACH,OAAO,CAAC,UAAU;IAIlB;;;;;OAKG;IACH,OAAO,CAAC,SAAS;IAIjB;;;;;;;;OAQG;IACH,OAAO,CAAC,aAAa;IAMrB;;;;;;;;OAQG;IACH,OAAO,CAAC,eAAe;IASvB;;;;;;;;;;;;OAYG;IACH,OAAO,CAAC,wBAAwB;IAmChC;;;;;;;;;;;;;;;OAeG;IACH,OAAO,CAAC,8BAA8B;IAwDtC;;;;;;;;;;;;OAYG;IACH,OAAO,CAAC,yBAAyB;IAoBjC;;;;;;;;;;;;;;OAcG;IACH,OAAO,CAAC,oBAAoB;IAyI5B;;;;;;;;;;;;;OAaG;IACH,OAAO,CAAC,gBAAgB;IA4DxB;;;;;;;;OAQG;IACH,OAAO,CAAC,sBAAsB;IAkB9B;;;;;;;;;OASG;IACH,OAAO,CAAC,gBAAgB;IA2ExB;;;;;;;;;;;OAWG;IACH,OAAO,CAAC,cAAc;IAgBtB;;;;;;;;;;;;;;;OAeG;IACH,OAAO,CAAC,sBAAsB;IA2C9B;;;;;;;;;;;;;;;OAeG;IACH,OAAO,CAAC,2BAA2B;IAmBnC;;;;;;OAMG;IACH,OAAO,CAAC,uBAAuB;IAoB/B;;;;;;;;;;;;;;;;;;;;;;;;;;;;OA4BG;IACH,OAAO,CAAC,eAAe;CAwBxB"}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"jsx-parser.d.ts","sourceRoot":"","sources":["../../../src/extractor/parsers/jsx-parser.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,UAAU,EAAE,MAAM,WAAW,CAAA;AAC3C,OAAO,KAAK,EAAE,YAAY,EAAE,oBAAoB,EAAE,MAAM,aAAa,CAAA;
|
|
1
|
+
{"version":3,"file":"jsx-parser.d.ts","sourceRoot":"","sources":["../../../src/extractor/parsers/jsx-parser.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,UAAU,EAAE,MAAM,WAAW,CAAA;AAC3C,OAAO,KAAK,EAAE,YAAY,EAAE,oBAAoB,EAAE,MAAM,aAAa,CAAA;AAGrE;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GA8BG;AACH,wBAAgB,yBAAyB,CAAE,IAAI,EAAE,UAAU,EAAE,MAAM,EAAE,oBAAoB,GAAG,YAAY,GAAG,IAAI,CAoF9G"}
|
package/types/locize.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"locize.d.ts","sourceRoot":"","sources":["../src/locize.ts"],"names":[],"mappings":"AAKA,OAAO,KAAK,EAAE,oBAAoB,EAAE,MAAM,SAAS,CAAA;
|
|
1
|
+
{"version":3,"file":"locize.d.ts","sourceRoot":"","sources":["../src/locize.ts"],"names":[],"mappings":"AAKA,OAAO,KAAK,EAAE,oBAAoB,EAAE,MAAM,SAAS,CAAA;AAgOnD,eAAO,MAAM,aAAa,GAAI,QAAQ,oBAAoB,EAAE,aAAa,GAAG,kBAAiD,CAAA;AAC7H,eAAO,MAAM,iBAAiB,GAAI,QAAQ,oBAAoB,EAAE,aAAa,GAAG,kBAAqD,CAAA;AACrI,eAAO,MAAM,gBAAgB,GAAI,QAAQ,oBAAoB,EAAE,aAAa,GAAG,kBAAoD,CAAA"}
|
package/types/types.d.ts
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import type { Node, Expression } from '@swc/core';
|
|
1
|
+
import type { Node, Expression, ObjectExpression } from '@swc/core';
|
|
2
2
|
/**
|
|
3
3
|
* Main configuration interface for the i18next toolkit.
|
|
4
4
|
* Defines all available options for extraction, type generation, synchronization, and integrations.
|
|
@@ -212,6 +212,8 @@ export interface ExtractedKey {
|
|
|
212
212
|
hasCount?: boolean;
|
|
213
213
|
/** Whether this key is used with ordinal pluralization */
|
|
214
214
|
isOrdinal?: boolean;
|
|
215
|
+
/** AST node for options object, used for advanced plural handling in Trans */
|
|
216
|
+
optionsNode?: ObjectExpression;
|
|
215
217
|
/** hold the raw context expression from the AST */
|
|
216
218
|
contextExpression?: Expression;
|
|
217
219
|
}
|
package/types/types.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"types.d.ts","sourceRoot":"","sources":["../src/types.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,IAAI,EAAE,UAAU,EAAE,MAAM,WAAW,CAAA;
|
|
1
|
+
{"version":3,"file":"types.d.ts","sourceRoot":"","sources":["../src/types.ts"],"names":[],"mappings":"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"}
|
package/vitest.config.ts
CHANGED
|
@@ -8,6 +8,10 @@ export default defineConfig({
|
|
|
8
8
|
globals: true,
|
|
9
9
|
// Look for test files in the entire project
|
|
10
10
|
root: './',
|
|
11
|
+
coverage: {
|
|
12
|
+
include: ['src/**/*'],
|
|
13
|
+
exclude: ['src/types.ts', 'src/index.ts', 'src/extractor/index.ts']
|
|
14
|
+
}
|
|
11
15
|
},
|
|
12
16
|
plugins: [swc.vite()]
|
|
13
17
|
})
|