i18next-cli 0.9.18 → 0.9.20

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/CHANGELOG.md CHANGED
@@ -5,16 +5,24 @@ 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.18...v1.0.0) - 2025-xx-yy
8
+ ## [1.0.0](https://github.com/i18next/i18next-cli/compare/v0.9.20...v1.0.0) - 2025-xx-yy
9
9
 
10
10
  - not yet released
11
11
 
12
+ ## [0.9.20](https://github.com/i18next/i18next-cli/compare/v0.9.19...v0.9.20) - 2025-09-30
13
+
14
+ - **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.
15
+
16
+ ## [0.9.19](https://github.com/i18next/i18next-cli/compare/v0.9.18...v0.9.19) - 2025-09-30
17
+
18
+ - **Status Command:** Greatly improved the accuracy of the translation status report for plural keys. The command now calculates the total number of required keys for each language based on that specific language's pluralization rules (e.g., 2 forms for English, 6 for Arabic), rather than incorrectly using the primary language's rules for all locales.
19
+ - **Extractor:** Corrected the logic for ordinal plurals and default value fallbacks. The extractor now recognizes keys with an `_ordinal` suffix as ordinal plurals. The fallback hierarchy for all plural default values (e.g., `defaultValue_one`, `defaultValue_other`) now correctly matches i18next's behavior.
20
+
12
21
  ## [0.9.18](https://github.com/i18next/i18next-cli/compare/v0.9.17...v0.9.18) - 2025-09-30
13
22
 
14
23
  - **Extractor:** Fixed a bug where translation keys were not found in custom functions that were part of an object (e.g., `i18n.t(...)`). The `functions` configuration option now correctly handles member expressions in addition to simple function names. [#10](https://github.com/i18next/i18next-cli/issues/10)
15
24
  - **Extractor:** Fixed a critical bug where the `extract` command would incorrectly overwrite existing translations in secondary languages when using the `mergeNamespaces: true` option. The fix also resolves a related issue where unused keys were not being correctly pruned from the primary language file in the same scenario. The translation manager logic is now more robust for both merged and non-merged configurations. [#11](https://github.com/i18next/i18next-cli/issues/11)
16
25
 
17
-
18
26
  ## [0.9.17](https://github.com/i18next/i18next-cli/compare/v0.9.16...v0.9.17) - 2025-09-29
19
27
 
20
28
  - **Extractor:** Fixed a bug where namespace and `keyPrefix` information from custom `useTranslationNames` hooks was ignored when the `t` function was assigned directly to a variable (e.g., `let t = myHook()`). The extractor now correctly handles this pattern in addition to destructuring assignments. [#9](https://github.com/i18next/i18next-cli/issues/9)
package/README.md CHANGED
@@ -244,6 +244,8 @@ npx i18next-cli locize-sync [options]
244
244
 
245
245
  The configuration file supports both TypeScript (`.ts`) and JavaScript (`.js`) formats. Use the `defineConfig` helper for type safety and IntelliSense.
246
246
 
247
+ > **šŸ’” No Installation Required?** If you don't want to install `i18next-cli` as a dependency, you can skip the `defineConfig` helper and return a plain JavaScript object or JSON instead. The `defineConfig` function is purely for TypeScript support and doesn't affect functionality.
248
+
247
249
  ### Basic Configuration
248
250
 
249
251
  ```typescript
@@ -259,6 +261,19 @@ export default defineConfig({
259
261
  });
260
262
  ```
261
263
 
264
+ **Alternative without local installation:**
265
+
266
+ ```javascript
267
+ // i18next.config.js
268
+ export default {
269
+ locales: ['en', 'de', 'fr'],
270
+ extract: {
271
+ input: ['src/**/*.{ts,tsx,js,jsx}'],
272
+ output: 'locales/{{language}}/{{namespace}}.json',
273
+ },
274
+ };
275
+ ```
276
+
262
277
  ### Advanced Configuration
263
278
 
264
279
  ```typescript
package/dist/cjs/cli.js CHANGED
@@ -1,2 +1,2 @@
1
1
  #!/usr/bin/env node
2
- "use strict";var e=require("commander"),t=require("chokidar"),o=require("glob"),n=require("chalk"),i=require("./config.js"),a=require("./heuristic-config.js"),r=require("./extractor/core/extractor.js");require("node:path"),require("node:fs/promises"),require("jiti");var c=require("./types-generator.js"),s=require("./syncer.js"),l=require("./migrator.js"),u=require("./init.js"),d=require("./linter.js"),g=require("./status.js"),p=require("./locize.js");const f=new e.Command;f.name("i18next-cli").description("A unified, high-performance i18next CLI.").version("0.9.18"),f.command("extract").description("Extract translation keys from source files and update resource files.").option("-w, --watch","Watch for file changes and re-run the extractor.").option("--ci","Exit with a non-zero status code if any files are updated.").action(async e=>{const a=await i.ensureConfig(),c=async()=>{const t=await r.runExtractor(a);e.ci&&t&&(console.error(n.red.bold("\n[CI Mode] Error: Translation files were updated. Please commit the changes.")),console.log(n.yellow("šŸ’” Tip: Tired of committing JSON files? locize syncs your team automatically => https://www.locize.com/docs/getting-started")),console.log(` Learn more: ${n.cyan("npx i18next-cli locize-sync")}`),process.exit(1))};if(await c(),e.watch){console.log("\nWatching for changes...");t.watch(await o.glob(a.extract.input),{ignored:/node_modules/,persistent:!0}).on("change",e=>{console.log(`\nFile changed: ${e}`),c()})}}),f.command("status [locale]").description("Display translation status. Provide a locale for a detailed key-by-key view.").option("-n, --namespace <ns>","Filter the status report by a specific namespace").action(async(e,t)=>{let o=await i.loadConfig();if(!o){console.log(n.blue("No config file found. Attempting to detect project structure..."));const e=await a.detectConfig();e||(console.error(n.red("Could not automatically detect your project structure.")),console.log(`Please create a config file first by running: ${n.cyan("npx i18next-cli init")}`),process.exit(1)),console.log(n.green("Project structure detected successfully!")),o=e}await g.runStatus(o,{detail:e,namespace:t.namespace})}),f.command("types").description("Generate TypeScript definitions from translation resource files.").option("-w, --watch","Watch for file changes and re-run the type generator.").action(async e=>{const n=await i.ensureConfig(),a=()=>c.runTypesGenerator(n);if(await a(),e.watch){console.log("\nWatching for changes...");t.watch(await o.glob(n.types?.input||[]),{persistent:!0}).on("change",e=>{console.log(`\nFile changed: ${e}`),a()})}}),f.command("sync").description("Synchronize secondary language files with the primary language file.").action(async()=>{const e=await i.ensureConfig();await s.runSyncer(e)}),f.command("migrate-config").description("Migrate a legacy i18next-parser.config.js to the new format.").action(async()=>{await l.runMigrator()}),f.command("init").description("Create a new i18next.config.ts/js file with an interactive setup wizard.").action(u.runInit),f.command("lint").description("Find potential issues like hardcoded strings in your codebase.").action(async()=>{let e=await i.loadConfig();if(!e){console.log(n.blue("No config file found. Attempting to detect project structure..."));const t=await a.detectConfig();t||(console.error(n.red("Could not automatically detect your project structure.")),console.log(`Please create a config file first by running: ${n.cyan("npx i1e-toolkit init")}`),process.exit(1)),console.log(n.green("Project structure detected successfully!")),e=t}await d.runLinter(e)}),f.command("locize-sync").description("Synchronize local translations with your locize project.").option("--update-values","Update values of existing translations on locize.").option("--src-lng-only","Check for changes in source language only.").option("--compare-mtime","Compare modification times when syncing.").option("--dry-run","Run the command without making any changes.").action(async e=>{const t=await i.ensureConfig();await p.runLocizeSync(t,e)}),f.command("locize-download").description("Download all translations from your locize project.").action(async e=>{const t=await i.ensureConfig();await p.runLocizeDownload(t,e)}),f.command("locize-migrate").description("Migrate local translation files to a new locize project.").action(async e=>{const t=await i.ensureConfig();await p.runLocizeMigrate(t,e)}),f.parse(process.argv);
2
+ "use strict";var e=require("commander"),t=require("chokidar"),o=require("glob"),n=require("chalk"),i=require("./config.js"),a=require("./heuristic-config.js"),r=require("./extractor/core/extractor.js");require("node:path"),require("node:fs/promises"),require("jiti");var c=require("./types-generator.js"),s=require("./syncer.js"),l=require("./migrator.js"),u=require("./init.js"),d=require("./linter.js"),g=require("./status.js"),p=require("./locize.js");const f=new e.Command;f.name("i18next-cli").description("A unified, high-performance i18next CLI.").version("0.9.20"),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,t,n){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 i=t.arguments?.[n.nsArg]?.expression;let s;"StringLiteral"===i?.type?s=i.value:"ArrayExpression"===i?.type&&"StringLiteral"===i.elements[0]?.expression.type&&(s=i.elements[0].expression.value);const a=t.arguments?.[n.keyPrefixArg]?.expression;let o;if("ObjectExpression"===a?.type){const e=this.getObjectPropValue(a,"keyPrefix");o="string"==typeof e?e:void 0}this.setVarInScope(r,{defaultNs:s,keyPrefix:o})}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 t=this.getFunctionName(e.callee);if(!t)return;const n=this.getVarFromScope(t);if(!((this.config.extract.functions||["t"]).includes(t)||void 0!==n)||0===e.arguments.length)return;const r=e.arguments[0].expression,i=[];if("StringLiteral"===r.type)i.push(r.value);else if("ArrowFunctionExpression"===r.type){const e=this.extractKeyFromSelector(r);e&&i.push(e)}else if("ArrayExpression"===r.type)for(const e of r.elements)"StringLiteral"===e?.expression.type&&i.push(e.expression.value);if(0===i.length)return;let s,a;if(e.arguments.length>1){const t=e.arguments[1].expression;"ObjectExpression"===t.type?a=t:"StringLiteral"===t.type&&(s=t.value)}if(e.arguments.length>2){const t=e.arguments[2].expression;"ObjectExpression"===t.type&&(a=t)}const o=a?this.getObjectPropValue(a,"defaultValue"):void 0,l="string"==typeof o?o:s;for(let e=0;e<i.length;e++){let t,r=i[e];if(a){const e=this.getObjectPropValue(a,"ns");"string"==typeof e&&(t=e)}!t&&n?.defaultNs&&(t=n.defaultNs);const s=this.config.extract.nsSeparator??":";if(!t&&s&&r.includes(s)){const e=r.split(s);t=e.shift(),r=e.join(s)}t||(t=this.config.extract.defaultNS);let o=r;if(n?.keyPrefix){const e=this.config.extract.keySeparator??".";o=`${n.keyPrefix}${e}${r}`}const p=e===i.length-1&&l||r;if(a){const e=this.getObjectProperty(a,"context");if("ConditionalExpression"===e?.value?.type){const n=this.resolvePossibleStringValues(e.value),r=this.config.extract.contextSeparator??"_";if(n.length>0){n.forEach(e=>{this.pluginContext.addKey({key:`${o}${r}${e}`,ns:t,defaultValue:p})}),this.pluginContext.addKey({key:o,ns:t,defaultValue:p});continue}}const n=this.getObjectPropValue(a,"context");if("string"==typeof n&&n){const e=this.config.extract.contextSeparator??"_";this.pluginContext.addKey({key:`${o}${e}${n}`,ns:t,defaultValue:p});continue}if(void 0!==this.getObjectPropValue(a,"count")){this.handlePluralKeys(o,t,a);continue}!0===this.getObjectPropValue(a,"returnObjects")&&this.objectKeys.add(o)}this.pluginContext.addKey({key:o,ns:t,defaultValue:p})}}handlePluralKeys(e,t,n){try{const r=!0===this.getObjectPropValue(n,"ordinal"),i=r?"ordinal":"cardinal",s=new Intl.PluralRules(this.config.extract?.primaryLanguage,{type:i}).resolvedOptions().pluralCategories,a=this.config.extract.pluralSeparator??"_",o=this.getObjectPropValue(n,"defaultValue"),l=this.getObjectPropValue(n,"defaultValue_other");for(const i of s){const s=r?`defaultValue_ordinal_${i}`:`defaultValue_${i}`,p=this.getObjectPropValue(n,s);let u;u="string"==typeof p?p:"one"===i&&"string"==typeof o?o:"string"==typeof l?l:"string"==typeof o?o:e;const c=r?`${e}${a}ordinal${a}${i}`:`${e}${a}${i}`;this.pluginContext.addKey({key:c,ns:t,defaultValue:u,hasCount:!0})}}catch(r){this.logger.warn(`Could not determine plural rules for language "${this.config.extract?.primaryLanguage}". Falling back to simple key extraction.`);const i=this.getObjectPropValue(n,"defaultValue");this.pluginContext.addKey({key:e,ns:t,defaultValue:"string"==typeof i?i: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});this.pluginContext.addKey(n)}}else n.hasCount?this.handleSimplePluralKeys(n.key,n.defaultValue,n.ns):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(".")}}getObjectPropValue(e,t){const n=e.properties.find(e=>"KeyValueProperty"===e.type&&("Identifier"===e.key?.type&&e.key.value===t||"StringLiteral"===e.key?.type&&e.key.value===t));if("KeyValueProperty"===n?.type){const e=n.value;return"StringLiteral"===e.type||("BooleanLiteral"===e.type||"NumericLiteral"===e.type)?e.value:""}}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,[]}getObjectProperty(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))}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
+ "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";function e(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(t,n){const i=t.opening.attributes?.find(e=>"JSXAttribute"===e.type&&"Identifier"===e.name.type&&"i18nKey"===e.name.value),r=t.opening.attributes?.find(e=>"JSXAttribute"===e.type&&"Identifier"===e.name.type&&"defaults"===e.name.value),a=t.opening.attributes?.find(e=>"JSXAttribute"===e.type&&"Identifier"===e.name.type&&"count"===e.name.value),u=!!a,l=t.opening.attributes?.find(e=>"JSXAttribute"===e.type&&"Identifier"===e.name.type&&"context"===e.name.value),p="JSXAttribute"===l?.type&&"JSXExpressionContainer"===l.value?.type?l.value.expression:void 0;let s;if(s="JSXAttribute"===i?.type&&"StringLiteral"===i.value?.type?i.value.value:e(t.children,n),!s)return null;const o=t.opening.attributes?.find(e=>"JSXAttribute"===e.type&&"Identifier"===e.name.type&&"ns"===e.name.value),y="JSXAttribute"===o?.type&&"StringLiteral"===o.value?.type?o.value.value:void 0;let f=n.extract.defaultValue||"";return f="JSXAttribute"===r?.type&&"StringLiteral"===r.value?.type?r.value.value:e(t.children,n),{key:s,ns:y,defaultValue:f||s,hasCount:u,contextExpression:p}};
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}};
@@ -1 +1 @@
1
- "use strict";var e=require("execa"),o=require("chalk"),n=require("ora"),t=require("inquirer"),r=require("node:path");function i(e,o={},n={}){const t=[];if("sync"===e){(o.updateValues??n.updateValues)&&t.push("--update-values","true");(o.srcLngOnly??n.sourceLanguageOnly)&&t.push("--reference-language-only","true");(o.compareMtime??n.compareModificationTime)&&t.push("--compare-modification-time","true");(o.dryRun??n.dryRun)&&t.push("--dry","true")}return t}async function s(s,c,a={}){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 l=n(`Running 'locize ${s}'...\n`).start(),u=c.locize||{},{projectId:p,apiKey:d,version:y}=u;let g=[s];p&&g.push("--project-id",p),d&&g.push("--api-key",d),y&&g.push("--ver",y),g.push(...i(s,a,u));const m=r.resolve(process.cwd(),c.extract.output.split("/{{language}}/")[0]);g.push("--path",m);try{console.log(o.cyan(`\nRunning 'locize ${g.join(" ")}'...`));const n=await e.execa("locize",g,{stdio:"pipe"});l.succeed(o.green(`'locize ${s}' completed successfully.`)),n?.stdout&&console.log(n.stdout)}catch(n){const r=n.stderr||"";if(r.includes("missing required argument")){const n=await async function(e){console.log(o.yellow("\nLocize configuration is missing or invalid. Let's set it up!"));const n=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(!n.projectId)return void console.error(o.red("Project ID is required to continue."));e.locize={projectId:n.projectId,apiKey:n.apiKey,version:n.version};const{save:r}=await t.prompt([{type:"confirm",name:"save",message:"Would you like to see how to save these credentials for future use?",default:!0}]);if(r){const e=`\n# Add this to your .env file (and ensure .env is in your .gitignore!)\nLOCIZE_API_KEY=${n.apiKey}\n`,t=`\n // Add this to your i18next.config.ts file\n locize: {\n projectId: '${n.projectId}',\n // For security, apiKey is best set via an environment variable\n apiKey: process.env.LOCIZE_API_KEY,\n version: '${n.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(e)),console.log(o.bold("Then, in your i18next.config.ts:")),console.log(o.green(t))}return e.locize}(c);if(n){g=[s],n.projectId&&g.push("--project-id",n.projectId),n.apiKey&&g.push("--api-key",n.apiKey),n.version&&g.push("--ver",n.version),g.push(...i(s,a,u)),g.push("--path",m);try{l.start("Retrying with new credentials...");const n=await e.execa("locize",g,{stdio:"pipe"});l.succeed(o.green("Retry successful!")),n?.stdout&&console.log(n.stdout)}catch(e){l.fail(o.red("Error during retry.")),console.error(e.stderr||e.message),process.exit(1)}}else l.fail("Operation cancelled."),process.exit(1)}else l.fail(o.red(`Error executing 'locize ${s}'.`)),console.error(r||n.message),process.exit(1)}console.log(o.green(`\nāœ… 'locize ${s}' 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);
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);
@@ -1 +1 @@
1
- "use strict";var e=require("chalk"),o=require("ora"),t=require("node:path"),a=require("./extractor/core/key-finder.js"),s=require("./utils/nested-object.js"),n=require("./utils/file-utils.js");function l(o,t,a){const s=a>0?Math.round(t/a*100):100,n=r(s);console.log(`${e.bold(o)}: ${n} ${s}% (${t}/${a})`)}function r(o){const t=Math.round(o/100*20),a=20-t;return`[${e.green("".padStart(t,"ā– "))}${"".padStart(a,"ā–”")}]`}function c(){console.log(e.yellow.bold("\n✨ Take your localization to the next level!")),console.log("Manage translations with your team in the cloud with locize => https://www.locize.com/docs/getting-started"),console.log(`Run ${e.cyan("npx i18next-cli locize-migrate")} to get started.`)}exports.runStatus=async function(i,u={}){i.extract.primaryLanguage||=i.locales[0]||"en",i.extract.secondaryLanguages||=i.locales.filter(e=>e!==i?.extract?.primaryLanguage);const d=o("Analyzing project localization status...\n").start();try{const o=await async function(e){e.extract.primaryLanguage||=e.locales[0]||"en",e.extract.secondaryLanguages||=e.locales.filter(o=>o!==e?.extract?.primaryLanguage);const{allKeys:o}=await a.findKeys(e),{secondaryLanguages:l,keySeparator:r=".",defaultNS:c="translation",mergeNamespaces:i=!1}=e.extract,u=new Map;for(const e of o.values()){const o=e.ns||c;u.has(o)||u.set(o,[]),u.get(o).push(e)}const d={totalKeys:o.size,keysByNs:u,locales:new Map};for(const o of l){let a=0;const l=new Map,c=i?await n.loadTranslationFile(t.resolve(process.cwd(),n.getOutputPath(e.extract.output,o)))||{}:null;for(const[d,y]of u.entries()){const u=i?c?.[d]||{}:await n.loadTranslationFile(t.resolve(process.cwd(),n.getOutputPath(e.extract.output,o,d)))||{};let g=0;const f=y.map(({key:e})=>{const o=!!s.getNestedValue(u,e,r??".");return o&&g++,{key:e,isTranslated:o}});l.set(d,{totalKeys:y.length,translatedKeys:g,keyDetails:f}),a+=g}d.locales.set(o,{totalTranslated:a,namespaces:l})}return d}(i);d.succeed("Analysis complete."),function(o,t,a){a.detail?function(o,t,a,s){if(a===t.extract.primaryLanguage)return void console.log(e.yellow(`Locale "${a}" is the primary language. All keys are considered present.`));if(!t.locales.includes(a))return void console.error(e.red(`Error: Locale "${a}" is not defined in your configuration.`));const n=o.locales.get(a);if(!n)return void console.error(e.red(`Error: Locale "${a}" is not a valid secondary language.`));console.log(e.bold(`\nKey Status for "${e.cyan(a)}":`));const r=Array.from(o.keysByNs.values()).flat().length;l("Overall",n.totalTranslated,r);const i=s?[s]:Array.from(n.namespaces.keys()).sort();for(const o of i){const t=n.namespaces.get(o);t&&(console.log(e.cyan.bold(`\nNamespace: ${o}`)),l("Namespace Progress",t.translatedKeys,t.totalKeys),t.keyDetails.forEach(({key:o,isTranslated:t})=>{const a=t?e.green("āœ“"):e.red("āœ—");console.log(` ${a} ${o}`)}))}const u=r-n.totalTranslated;u>0?console.log(e.yellow.bold(`\nSummary: Found ${u} missing translations for "${a}".`)):console.log(e.green.bold(`\nSummary: šŸŽ‰ All keys are translated for "${a}".`));c()}(o,t,a.detail,a.namespace):a.namespace?function(o,t,a){const s=o.keysByNs.get(a);if(!s)return void console.error(e.red(`Error: Namespace "${a}" was not found in your source code.`));console.log(e.cyan.bold(`\nStatus for Namespace: "${a}"`)),console.log("------------------------");for(const[e,t]of o.locales.entries()){const o=t.namespaces.get(a);if(o){const t=o.totalKeys>0?Math.round(o.translatedKeys/o.totalKeys*100):100,a=r(t);console.log(`- ${e}: ${a} ${t}% (${o.translatedKeys}/${o.totalKeys} keys)`)}}c()}(o,0,a.namespace):function(o,t){const{primaryLanguage:a}=t.extract;console.log(e.cyan.bold("\ni18next Project Status")),console.log("------------------------"),console.log(`šŸ”‘ Keys Found: ${e.bold(o.totalKeys)}`),console.log(`šŸ“š Namespaces Found: ${e.bold(o.keysByNs.size)}`),console.log(`šŸŒ Locales: ${e.bold(t.locales.join(", "))}`),console.log(`āœ… Primary Language: ${e.bold(a)}`),console.log("\nTranslation Progress:");for(const[e,t]of o.locales.entries()){const a=o.totalKeys>0?Math.round(t.totalTranslated/o.totalKeys*100):100,s=r(a);console.log(`- ${e}: ${s} ${a}% (${t.totalTranslated}/${o.totalKeys} keys)`)}c()}(o,t)}(o,i,u)}catch(e){d.fail("Failed to generate status report."),console.error(e)}};
1
+ "use strict";var e=require("chalk"),o=require("ora"),t=require("node:path"),a=require("./extractor/core/key-finder.js"),s=require("./utils/nested-object.js"),n=require("./utils/file-utils.js");function l(o,t,a){const s=a>0?Math.round(t/a*100):100,n=r(s);console.log(`${e.bold(o)}: ${n} ${s}% (${t}/${a})`)}function r(o){const t=Math.floor(o/100*20),a=20-t;return`[${e.green("".padStart(t,"ā– "))}${"".padStart(a,"ā–”")}]`}function c(){console.log(e.yellow.bold("\n✨ Take your localization to the next level!")),console.log("Manage translations with your team in the cloud with locize => https://www.locize.com/docs/getting-started"),console.log(`Run ${e.cyan("npx i18next-cli locize-migrate")} to get started.`)}exports.runStatus=async function(i,u={}){i.extract.primaryLanguage||=i.locales[0]||"en",i.extract.secondaryLanguages||=i.locales.filter(e=>e!==i?.extract?.primaryLanguage);const d=o("Analyzing project localization status...\n").start();try{const o=await async function(e){e.extract.primaryLanguage||=e.locales[0]||"en",e.extract.secondaryLanguages||=e.locales.filter(o=>o!==e?.extract?.primaryLanguage);const{allKeys:o}=await a.findKeys(e),{secondaryLanguages:l,keySeparator:r=".",defaultNS:c="translation",mergeNamespaces:i=!1,pluralSeparator:u="_"}=e.extract,d=new Map;for(const e of o.values()){const o=e.ns||c;d.has(o)||d.set(o,[]),d.get(o).push(e)}const y={totalBaseKeys:o.size,keysByNs:d,locales:new Map};for(const o of l){let a=0,l=0;const c=new Map,g=i?await n.loadTranslationFile(t.resolve(process.cwd(),n.getOutputPath(e.extract.output,o)))||{}:null;for(const[y,f]of d.entries()){const d=i?g?.[y]||{}:await n.loadTranslationFile(t.resolve(process.cwd(),n.getOutputPath(e.extract.output,o,y)))||{};let p=0,$=0;const m=[];for(const{key:e,hasCount:t,isOrdinal:a}of f)if(t){const t=a?"ordinal":"cardinal",n=new Intl.PluralRules(o,{type:t}).resolvedOptions().pluralCategories;for(const o of n){$++;const t=a?`${e}${u}ordinal${u}${o}`:`${e}${u}${o}`,n=!!s.getNestedValue(d,t,r??".");n&&p++,m.push({key:t,isTranslated:n})}}else{$++;const o=!!s.getNestedValue(d,e,r??".");o&&p++,m.push({key:e,isTranslated:o})}c.set(y,{totalKeys:$,translatedKeys:p,keyDetails:m}),a+=p,l+=$}y.locales.set(o,{totalKeys:l,totalTranslated:a,namespaces:c})}return y}(i);d.succeed("Analysis complete."),function(o,t,a){a.detail?function(o,t,a,s){if(a===t.extract.primaryLanguage)return void console.log(e.yellow(`Locale "${a}" is the primary language. All keys are considered present.`));if(!t.locales.includes(a))return void console.error(e.red(`Error: Locale "${a}" is not defined in your configuration.`));const n=o.locales.get(a);if(!n)return void console.error(e.red(`Error: Locale "${a}" is not a valid secondary language.`));console.log(e.bold(`\nKey Status for "${e.cyan(a)}":`));const r=Array.from(o.keysByNs.values()).flat().length;l("Overall",n.totalTranslated,n.totalKeys);const i=s?[s]:Array.from(n.namespaces.keys()).sort();for(const o of i){const t=n.namespaces.get(o);t&&(console.log(e.cyan.bold(`\nNamespace: ${o}`)),l("Namespace Progress",t.translatedKeys,t.totalKeys),t.keyDetails.forEach(({key:o,isTranslated:t})=>{const a=t?e.green("āœ“"):e.red("āœ—");console.log(` ${a} ${o}`)}))}const u=r-n.totalTranslated;u>0?console.log(e.yellow.bold(`\nSummary: Found ${u} missing translations for "${a}".`)):console.log(e.green.bold(`\nSummary: šŸŽ‰ All keys are translated for "${a}".`));c()}(o,t,a.detail,a.namespace):a.namespace?function(o,t,a){const s=o.keysByNs.get(a);if(!s)return void console.error(e.red(`Error: Namespace "${a}" was not found in your source code.`));console.log(e.cyan.bold(`\nStatus for Namespace: "${a}"`)),console.log("------------------------");for(const[e,t]of o.locales.entries()){const o=t.namespaces.get(a);if(o){const t=o.totalKeys>0?Math.round(o.translatedKeys/o.totalKeys*100):100,a=r(t);console.log(`- ${e}: ${a} ${t}% (${o.translatedKeys}/${o.totalKeys} keys)`)}}c()}(o,0,a.namespace):function(o,t){const{primaryLanguage:a}=t.extract;console.log(e.cyan.bold("\ni18next Project Status")),console.log("------------------------"),console.log(`šŸ”‘ Keys Found: ${e.bold(o.totalBaseKeys)}`),console.log(`šŸ“š Namespaces Found: ${e.bold(o.keysByNs.size)}`),console.log(`šŸŒ Locales: ${e.bold(t.locales.join(", "))}`),console.log(`āœ… Primary Language: ${e.bold(a)}`),console.log("\nTranslation Progress:");for(const[e,t]of o.locales.entries()){const o=t.totalKeys>0?Math.round(t.totalTranslated/t.totalKeys*100):100,a=r(o);console.log(`- ${e}: ${a} ${o}% (${t.totalTranslated}/${t.totalKeys} keys)`)}c()}(o,t)}(o,i,u)}catch(e){d.fail("Failed to generate status report."),console.error(e)}};
package/dist/esm/cli.js CHANGED
@@ -1,2 +1,2 @@
1
1
  #!/usr/bin/env node
2
- import{Command as o}from"commander";import t from"chokidar";import{glob as e}from"glob";import i from"chalk";import{ensureConfig as n,loadConfig as a}from"./config.js";import{detectConfig as c}from"./heuristic-config.js";import{runExtractor as r}from"./extractor/core/extractor.js";import"node:path";import"node:fs/promises";import"jiti";import{runTypesGenerator as s}from"./types-generator.js";import{runSyncer as l}from"./syncer.js";import{runMigrator as m}from"./migrator.js";import{runInit as p}from"./init.js";import{runLinter as d}from"./linter.js";import{runStatus as f}from"./status.js";import{runLocizeSync as g,runLocizeDownload as u,runLocizeMigrate as y}from"./locize.js";const w=new o;w.name("i18next-cli").description("A unified, high-performance i18next CLI.").version("0.9.18"),w.command("extract").description("Extract translation keys from source files and update resource files.").option("-w, --watch","Watch for file changes and re-run the extractor.").option("--ci","Exit with a non-zero status code if any files are updated.").action(async o=>{const a=await n(),c=async()=>{const t=await r(a);o.ci&&t&&(console.error(i.red.bold("\n[CI Mode] Error: Translation files were updated. Please commit the changes.")),console.log(i.yellow("šŸ’” Tip: Tired of committing JSON files? locize syncs your team automatically => https://www.locize.com/docs/getting-started")),console.log(` Learn more: ${i.cyan("npx i18next-cli locize-sync")}`),process.exit(1))};if(await c(),o.watch){console.log("\nWatching for changes...");t.watch(await e(a.extract.input),{ignored:/node_modules/,persistent:!0}).on("change",o=>{console.log(`\nFile changed: ${o}`),c()})}}),w.command("status [locale]").description("Display translation status. Provide a locale for a detailed key-by-key view.").option("-n, --namespace <ns>","Filter the status report by a specific namespace").action(async(o,t)=>{let e=await a();if(!e){console.log(i.blue("No config file found. Attempting to detect project structure..."));const o=await c();o||(console.error(i.red("Could not automatically detect your project structure.")),console.log(`Please create a config file first by running: ${i.cyan("npx i18next-cli init")}`),process.exit(1)),console.log(i.green("Project structure detected successfully!")),e=o}await f(e,{detail:o,namespace:t.namespace})}),w.command("types").description("Generate TypeScript definitions from translation resource files.").option("-w, --watch","Watch for file changes and re-run the type generator.").action(async o=>{const i=await n(),a=()=>s(i);if(await a(),o.watch){console.log("\nWatching for changes...");t.watch(await e(i.types?.input||[]),{persistent:!0}).on("change",o=>{console.log(`\nFile changed: ${o}`),a()})}}),w.command("sync").description("Synchronize secondary language files with the primary language file.").action(async()=>{const o=await n();await l(o)}),w.command("migrate-config").description("Migrate a legacy i18next-parser.config.js to the new format.").action(async()=>{await m()}),w.command("init").description("Create a new i18next.config.ts/js file with an interactive setup wizard.").action(p),w.command("lint").description("Find potential issues like hardcoded strings in your codebase.").action(async()=>{let o=await a();if(!o){console.log(i.blue("No config file found. Attempting to detect project structure..."));const t=await c();t||(console.error(i.red("Could not automatically detect your project structure.")),console.log(`Please create a config file first by running: ${i.cyan("npx i1e-toolkit init")}`),process.exit(1)),console.log(i.green("Project structure detected successfully!")),o=t}await d(o)}),w.command("locize-sync").description("Synchronize local translations with your locize project.").option("--update-values","Update values of existing translations on locize.").option("--src-lng-only","Check for changes in source language only.").option("--compare-mtime","Compare modification times when syncing.").option("--dry-run","Run the command without making any changes.").action(async o=>{const t=await n();await g(t,o)}),w.command("locize-download").description("Download all translations from your locize project.").action(async o=>{const t=await n();await u(t,o)}),w.command("locize-migrate").description("Migrate local translation files to a new locize project.").action(async o=>{const t=await n();await y(t,o)}),w.parse(process.argv);
2
+ import{Command as o}from"commander";import t from"chokidar";import{glob as e}from"glob";import i from"chalk";import{ensureConfig as n,loadConfig as a}from"./config.js";import{detectConfig as c}from"./heuristic-config.js";import{runExtractor as r}from"./extractor/core/extractor.js";import"node:path";import"node:fs/promises";import"jiti";import{runTypesGenerator as s}from"./types-generator.js";import{runSyncer as l}from"./syncer.js";import{runMigrator as m}from"./migrator.js";import{runInit as p}from"./init.js";import{runLinter as d}from"./linter.js";import{runStatus as f}from"./status.js";import{runLocizeSync as g,runLocizeDownload as u,runLocizeMigrate as y}from"./locize.js";const w=new o;w.name("i18next-cli").description("A unified, high-performance i18next CLI.").version("0.9.20"),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 t{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,t,n){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 i=t.arguments?.[n.nsArg]?.expression;let s;"StringLiteral"===i?.type?s=i.value:"ArrayExpression"===i?.type&&"StringLiteral"===i.elements[0]?.expression.type&&(s=i.elements[0].expression.value);const a=t.arguments?.[n.keyPrefixArg]?.expression;let o;if("ObjectExpression"===a?.type){const e=this.getObjectPropValue(a,"keyPrefix");o="string"==typeof e?e:void 0}this.setVarInScope(r,{defaultNs:s,keyPrefix:o})}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 t=this.getFunctionName(e.callee);if(!t)return;const n=this.getVarFromScope(t);if(!((this.config.extract.functions||["t"]).includes(t)||void 0!==n)||0===e.arguments.length)return;const r=e.arguments[0].expression,i=[];if("StringLiteral"===r.type)i.push(r.value);else if("ArrowFunctionExpression"===r.type){const e=this.extractKeyFromSelector(r);e&&i.push(e)}else if("ArrayExpression"===r.type)for(const e of r.elements)"StringLiteral"===e?.expression.type&&i.push(e.expression.value);if(0===i.length)return;let s,a;if(e.arguments.length>1){const t=e.arguments[1].expression;"ObjectExpression"===t.type?a=t:"StringLiteral"===t.type&&(s=t.value)}if(e.arguments.length>2){const t=e.arguments[2].expression;"ObjectExpression"===t.type&&(a=t)}const o=a?this.getObjectPropValue(a,"defaultValue"):void 0,l="string"==typeof o?o:s;for(let e=0;e<i.length;e++){let t,r=i[e];if(a){const e=this.getObjectPropValue(a,"ns");"string"==typeof e&&(t=e)}!t&&n?.defaultNs&&(t=n.defaultNs);const s=this.config.extract.nsSeparator??":";if(!t&&s&&r.includes(s)){const e=r.split(s);t=e.shift(),r=e.join(s)}t||(t=this.config.extract.defaultNS);let o=r;if(n?.keyPrefix){const e=this.config.extract.keySeparator??".";o=`${n.keyPrefix}${e}${r}`}const p=e===i.length-1&&l||r;if(a){const e=this.getObjectProperty(a,"context");if("ConditionalExpression"===e?.value?.type){const n=this.resolvePossibleStringValues(e.value),r=this.config.extract.contextSeparator??"_";if(n.length>0){n.forEach(e=>{this.pluginContext.addKey({key:`${o}${r}${e}`,ns:t,defaultValue:p})}),this.pluginContext.addKey({key:o,ns:t,defaultValue:p});continue}}const n=this.getObjectPropValue(a,"context");if("string"==typeof n&&n){const e=this.config.extract.contextSeparator??"_";this.pluginContext.addKey({key:`${o}${e}${n}`,ns:t,defaultValue:p});continue}if(void 0!==this.getObjectPropValue(a,"count")){this.handlePluralKeys(o,t,a);continue}!0===this.getObjectPropValue(a,"returnObjects")&&this.objectKeys.add(o)}this.pluginContext.addKey({key:o,ns:t,defaultValue:p})}}handlePluralKeys(e,t,n){try{const r=!0===this.getObjectPropValue(n,"ordinal"),i=r?"ordinal":"cardinal",s=new Intl.PluralRules(this.config.extract?.primaryLanguage,{type:i}).resolvedOptions().pluralCategories,a=this.config.extract.pluralSeparator??"_",o=this.getObjectPropValue(n,"defaultValue"),l=this.getObjectPropValue(n,"defaultValue_other");for(const i of s){const s=r?`defaultValue_ordinal_${i}`:`defaultValue_${i}`,p=this.getObjectPropValue(n,s);let u;u="string"==typeof p?p:"one"===i&&"string"==typeof o?o:"string"==typeof l?l:"string"==typeof o?o:e;const c=r?`${e}${a}ordinal${a}${i}`:`${e}${a}${i}`;this.pluginContext.addKey({key:c,ns:t,defaultValue:u,hasCount:!0})}}catch(r){this.logger.warn(`Could not determine plural rules for language "${this.config.extract?.primaryLanguage}". Falling back to simple key extraction.`);const i=this.getObjectPropValue(n,"defaultValue");this.pluginContext.addKey({key:e,ns:t,defaultValue:"string"==typeof i?i: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(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});this.pluginContext.addKey(n)}}else n.hasCount?this.handleSimplePluralKeys(n.key,n.defaultValue,n.ns):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(".")}}getObjectPropValue(e,t){const n=e.properties.find(e=>"KeyValueProperty"===e.type&&("Identifier"===e.key?.type&&e.key.value===t||"StringLiteral"===e.key?.type&&e.key.value===t));if("KeyValueProperty"===n?.type){const e=n.value;return"StringLiteral"===e.type||("BooleanLiteral"===e.type||"NumericLiteral"===e.type)?e.value:""}}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,[]}getObjectProperty(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))}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{t as ASTVisitors};
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 e(e,n){const i=e.opening.attributes?.find(e=>"JSXAttribute"===e.type&&"Identifier"===e.name.type&&"i18nKey"===e.name.value),r=e.opening.attributes?.find(e=>"JSXAttribute"===e.type&&"Identifier"===e.name.type&&"defaults"===e.name.value),a=e.opening.attributes?.find(e=>"JSXAttribute"===e.type&&"Identifier"===e.name.type&&"count"===e.name.value),u=!!a,l=e.opening.attributes?.find(e=>"JSXAttribute"===e.type&&"Identifier"===e.name.type&&"context"===e.name.value),p="JSXAttribute"===l?.type&&"JSXExpressionContainer"===l.value?.type?l.value.expression:void 0;let s;if(s="JSXAttribute"===i?.type&&"StringLiteral"===i.value?.type?i.value.value:t(e.children,n),!s)return null;const o=e.opening.attributes?.find(e=>"JSXAttribute"===e.type&&"Identifier"===e.name.type&&"ns"===e.name.value),y="JSXAttribute"===o?.type&&"StringLiteral"===o.value?.type?o.value.value:void 0;let f=n.extract.defaultValue||"";return f="JSXAttribute"===r?.type&&"StringLiteral"===r.value?.type?r.value.value:t(e.children,n),{key:s,ns:y,defaultValue:f||s,hasCount:u,contextExpression:p}}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," ")}export{e as extractFromTransComponent};
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};
@@ -1 +1 @@
1
- import{execa as e}from"execa";import o from"chalk";import t from"ora";import n from"inquirer";import{resolve as i}from"node:path";function s(e,o={},t={}){const n=[];if("sync"===e){(o.updateValues??t.updateValues)&&n.push("--update-values","true");(o.srcLngOnly??t.sourceLanguageOnly)&&n.push("--reference-language-only","true");(o.compareMtime??t.compareModificationTime)&&n.push("--compare-modification-time","true");(o.dryRun??t.dryRun)&&n.push("--dry","true")}return n}async function r(r,c,a={}){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 l=t(`Running 'locize ${r}'...\n`).start(),u=c.locize||{},{projectId:p,apiKey:d,version:y}=u;let g=[r];p&&g.push("--project-id",p),d&&g.push("--api-key",d),y&&g.push("--ver",y),g.push(...s(r,a,u));const m=i(process.cwd(),c.extract.output.split("/{{language}}/")[0]);g.push("--path",m);try{console.log(o.cyan(`\nRunning 'locize ${g.join(" ")}'...`));const t=await e("locize",g,{stdio:"pipe"});l.succeed(o.green(`'locize ${r}' completed successfully.`)),t?.stdout&&console.log(t.stdout)}catch(t){const i=t.stderr||"";if(i.includes("missing required argument")){const t=await async function(e){console.log(o.yellow("\nLocize configuration is missing or invalid. Let's set it up!"));const t=await n.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(!t.projectId)return void console.error(o.red("Project ID is required to continue."));e.locize={projectId:t.projectId,apiKey:t.apiKey,version:t.version};const{save:i}=await n.prompt([{type:"confirm",name:"save",message:"Would you like to see how to save these credentials for future use?",default:!0}]);if(i){const e=`\n# Add this to your .env file (and ensure .env is in your .gitignore!)\nLOCIZE_API_KEY=${t.apiKey}\n`,n=`\n // Add this to your i18next.config.ts file\n locize: {\n projectId: '${t.projectId}',\n // For security, apiKey is best set via an environment variable\n apiKey: process.env.LOCIZE_API_KEY,\n version: '${t.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(e)),console.log(o.bold("Then, in your i18next.config.ts:")),console.log(o.green(n))}return e.locize}(c);if(t){g=[r],t.projectId&&g.push("--project-id",t.projectId),t.apiKey&&g.push("--api-key",t.apiKey),t.version&&g.push("--ver",t.version),g.push(...s(r,a,u)),g.push("--path",m);try{l.start("Retrying with new credentials...");const t=await e("locize",g,{stdio:"pipe"});l.succeed(o.green("Retry successful!")),t?.stdout&&console.log(t.stdout)}catch(e){l.fail(o.red("Error during retry.")),console.error(e.stderr||e.message),process.exit(1)}}else l.fail("Operation cancelled."),process.exit(1)}else l.fail(o.red(`Error executing 'locize ${r}'.`)),console.error(i||t.message),process.exit(1)}console.log(o.green(`\nāœ… 'locize ${r}' 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};
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};
@@ -1 +1 @@
1
- import o from"chalk";import e from"ora";import{resolve as t}from"node:path";import{findKeys as a}from"./extractor/core/key-finder.js";import{getNestedValue as s}from"./utils/nested-object.js";import{loadTranslationFile as n,getOutputPath as l}from"./utils/file-utils.js";async function r(r,d={}){r.extract.primaryLanguage||=r.locales[0]||"en",r.extract.secondaryLanguages||=r.locales.filter(o=>o!==r?.extract?.primaryLanguage);const g=e("Analyzing project localization status...\n").start();try{const e=await async function(o){o.extract.primaryLanguage||=o.locales[0]||"en",o.extract.secondaryLanguages||=o.locales.filter(e=>e!==o?.extract?.primaryLanguage);const{allKeys:e}=await a(o),{secondaryLanguages:r,keySeparator:c=".",defaultNS:i="translation",mergeNamespaces:y=!1}=o.extract,d=new Map;for(const o of e.values()){const e=o.ns||i;d.has(e)||d.set(e,[]),d.get(e).push(o)}const g={totalKeys:e.size,keysByNs:d,locales:new Map};for(const e of r){let a=0;const r=new Map,i=y?await n(t(process.cwd(),l(o.extract.output,e)))||{}:null;for(const[g,u]of d.entries()){const d=y?i?.[g]||{}:await n(t(process.cwd(),l(o.extract.output,e,g)))||{};let f=0;const m=u.map(({key:o})=>{const e=!!s(d,o,c??".");return e&&f++,{key:o,isTranslated:e}});r.set(g,{totalKeys:u.length,translatedKeys:f,keyDetails:m}),a+=f}g.locales.set(e,{totalTranslated:a,namespaces:r})}return g}(r);g.succeed("Analysis complete."),function(e,t,a){a.detail?function(e,t,a,s){if(a===t.extract.primaryLanguage)return void console.log(o.yellow(`Locale "${a}" is the primary language. All keys are considered present.`));if(!t.locales.includes(a))return void console.error(o.red(`Error: Locale "${a}" is not defined in your configuration.`));const n=e.locales.get(a);if(!n)return void console.error(o.red(`Error: Locale "${a}" is not a valid secondary language.`));console.log(o.bold(`\nKey Status for "${o.cyan(a)}":`));const l=Array.from(e.keysByNs.values()).flat().length;c("Overall",n.totalTranslated,l);const r=s?[s]:Array.from(n.namespaces.keys()).sort();for(const e of r){const t=n.namespaces.get(e);t&&(console.log(o.cyan.bold(`\nNamespace: ${e}`)),c("Namespace Progress",t.translatedKeys,t.totalKeys),t.keyDetails.forEach(({key:e,isTranslated:t})=>{const a=t?o.green("āœ“"):o.red("āœ—");console.log(` ${a} ${e}`)}))}const i=l-n.totalTranslated;i>0?console.log(o.yellow.bold(`\nSummary: Found ${i} missing translations for "${a}".`)):console.log(o.green.bold(`\nSummary: šŸŽ‰ All keys are translated for "${a}".`));y()}(e,t,a.detail,a.namespace):a.namespace?function(e,t,a){const s=e.keysByNs.get(a);if(!s)return void console.error(o.red(`Error: Namespace "${a}" was not found in your source code.`));console.log(o.cyan.bold(`\nStatus for Namespace: "${a}"`)),console.log("------------------------");for(const[o,t]of e.locales.entries()){const e=t.namespaces.get(a);if(e){const t=e.totalKeys>0?Math.round(e.translatedKeys/e.totalKeys*100):100,a=i(t);console.log(`- ${o}: ${a} ${t}% (${e.translatedKeys}/${e.totalKeys} keys)`)}}y()}(e,0,a.namespace):function(e,t){const{primaryLanguage:a}=t.extract;console.log(o.cyan.bold("\ni18next Project Status")),console.log("------------------------"),console.log(`šŸ”‘ Keys Found: ${o.bold(e.totalKeys)}`),console.log(`šŸ“š Namespaces Found: ${o.bold(e.keysByNs.size)}`),console.log(`šŸŒ Locales: ${o.bold(t.locales.join(", "))}`),console.log(`āœ… Primary Language: ${o.bold(a)}`),console.log("\nTranslation Progress:");for(const[o,t]of e.locales.entries()){const a=e.totalKeys>0?Math.round(t.totalTranslated/e.totalKeys*100):100,s=i(a);console.log(`- ${o}: ${s} ${a}% (${t.totalTranslated}/${e.totalKeys} keys)`)}y()}(e,t)}(e,r,d)}catch(o){g.fail("Failed to generate status report."),console.error(o)}}function c(e,t,a){const s=a>0?Math.round(t/a*100):100,n=i(s);console.log(`${o.bold(e)}: ${n} ${s}% (${t}/${a})`)}function i(e){const t=Math.round(e/100*20),a=20-t;return`[${o.green("".padStart(t,"ā– "))}${"".padStart(a,"ā–”")}]`}function y(){console.log(o.yellow.bold("\n✨ Take your localization to the next level!")),console.log("Manage translations with your team in the cloud with locize => https://www.locize.com/docs/getting-started"),console.log(`Run ${o.cyan("npx i18next-cli locize-migrate")} to get started.`)}export{r as runStatus};
1
+ import o from"chalk";import e from"ora";import{resolve as t}from"node:path";import{findKeys as a}from"./extractor/core/key-finder.js";import{getNestedValue as s}from"./utils/nested-object.js";import{loadTranslationFile as n,getOutputPath as l}from"./utils/file-utils.js";async function r(r,d={}){r.extract.primaryLanguage||=r.locales[0]||"en",r.extract.secondaryLanguages||=r.locales.filter(o=>o!==r?.extract?.primaryLanguage);const u=e("Analyzing project localization status...\n").start();try{const e=await async function(o){o.extract.primaryLanguage||=o.locales[0]||"en",o.extract.secondaryLanguages||=o.locales.filter(e=>e!==o?.extract?.primaryLanguage);const{allKeys:e}=await a(o),{secondaryLanguages:r,keySeparator:c=".",defaultNS:i="translation",mergeNamespaces:y=!1,pluralSeparator:d="_"}=o.extract,u=new Map;for(const o of e.values()){const e=o.ns||i;u.has(e)||u.set(e,[]),u.get(e).push(o)}const g={totalBaseKeys:e.size,keysByNs:u,locales:new Map};for(const e of r){let a=0,r=0;const i=new Map,f=y?await n(t(process.cwd(),l(o.extract.output,e)))||{}:null;for(const[g,p]of u.entries()){const u=y?f?.[g]||{}:await n(t(process.cwd(),l(o.extract.output,e,g)))||{};let m=0,$=0;const h=[];for(const{key:o,hasCount:t,isOrdinal:a}of p)if(t){const t=a?"ordinal":"cardinal",n=new Intl.PluralRules(e,{type:t}).resolvedOptions().pluralCategories;for(const e of n){$++;const t=a?`${o}${d}ordinal${d}${e}`:`${o}${d}${e}`,n=!!s(u,t,c??".");n&&m++,h.push({key:t,isTranslated:n})}}else{$++;const e=!!s(u,o,c??".");e&&m++,h.push({key:o,isTranslated:e})}i.set(g,{totalKeys:$,translatedKeys:m,keyDetails:h}),a+=m,r+=$}g.locales.set(e,{totalKeys:r,totalTranslated:a,namespaces:i})}return g}(r);u.succeed("Analysis complete."),function(e,t,a){a.detail?function(e,t,a,s){if(a===t.extract.primaryLanguage)return void console.log(o.yellow(`Locale "${a}" is the primary language. All keys are considered present.`));if(!t.locales.includes(a))return void console.error(o.red(`Error: Locale "${a}" is not defined in your configuration.`));const n=e.locales.get(a);if(!n)return void console.error(o.red(`Error: Locale "${a}" is not a valid secondary language.`));console.log(o.bold(`\nKey Status for "${o.cyan(a)}":`));const l=Array.from(e.keysByNs.values()).flat().length;c("Overall",n.totalTranslated,n.totalKeys);const r=s?[s]:Array.from(n.namespaces.keys()).sort();for(const e of r){const t=n.namespaces.get(e);t&&(console.log(o.cyan.bold(`\nNamespace: ${e}`)),c("Namespace Progress",t.translatedKeys,t.totalKeys),t.keyDetails.forEach(({key:e,isTranslated:t})=>{const a=t?o.green("āœ“"):o.red("āœ—");console.log(` ${a} ${e}`)}))}const i=l-n.totalTranslated;i>0?console.log(o.yellow.bold(`\nSummary: Found ${i} missing translations for "${a}".`)):console.log(o.green.bold(`\nSummary: šŸŽ‰ All keys are translated for "${a}".`));y()}(e,t,a.detail,a.namespace):a.namespace?function(e,t,a){const s=e.keysByNs.get(a);if(!s)return void console.error(o.red(`Error: Namespace "${a}" was not found in your source code.`));console.log(o.cyan.bold(`\nStatus for Namespace: "${a}"`)),console.log("------------------------");for(const[o,t]of e.locales.entries()){const e=t.namespaces.get(a);if(e){const t=e.totalKeys>0?Math.round(e.translatedKeys/e.totalKeys*100):100,a=i(t);console.log(`- ${o}: ${a} ${t}% (${e.translatedKeys}/${e.totalKeys} keys)`)}}y()}(e,0,a.namespace):function(e,t){const{primaryLanguage:a}=t.extract;console.log(o.cyan.bold("\ni18next Project Status")),console.log("------------------------"),console.log(`šŸ”‘ Keys Found: ${o.bold(e.totalBaseKeys)}`),console.log(`šŸ“š Namespaces Found: ${o.bold(e.keysByNs.size)}`),console.log(`šŸŒ Locales: ${o.bold(t.locales.join(", "))}`),console.log(`āœ… Primary Language: ${o.bold(a)}`),console.log("\nTranslation Progress:");for(const[o,t]of e.locales.entries()){const e=t.totalKeys>0?Math.round(t.totalTranslated/t.totalKeys*100):100,a=i(e);console.log(`- ${o}: ${a} ${e}% (${t.totalTranslated}/${t.totalKeys} keys)`)}y()}(e,t)}(e,r,d)}catch(o){u.fail("Failed to generate status report."),console.error(o)}}function c(e,t,a){const s=a>0?Math.round(t/a*100):100,n=i(s);console.log(`${o.bold(e)}: ${n} ${s}% (${t}/${a})`)}function i(e){const t=Math.floor(e/100*20),a=20-t;return`[${o.green("".padStart(t,"ā– "))}${"".padStart(a,"ā–”")}]`}function y(){console.log(o.yellow.bold("\n✨ Take your localization to the next level!")),console.log("Manage translations with your team in the cloud with locize => https://www.locize.com/docs/getting-started"),console.log(`Run ${o.cyan("npx i18next-cli locize-migrate")} to get started.`)}export{r as runStatus};
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "i18next-cli",
3
- "version": "0.9.18",
3
+ "version": "0.9.20",
4
4
  "description": "A unified, high-performance i18next CLI.",
5
5
  "type": "module",
6
6
  "bin": {
package/src/cli.ts CHANGED
@@ -21,7 +21,7 @@ const program = new Command()
21
21
  program
22
22
  .name('i18next-cli')
23
23
  .description('A unified, high-performance i18next CLI.')
24
- .version('0.9.18')
24
+ .version('0.9.20')
25
25
 
26
26
  program
27
27
  .command('extract')
@@ -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
+ }