i18next-cli 1.15.0 → 1.17.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +8 -0
- package/dist/cjs/cli.js +1 -1
- package/dist/cjs/extractor/core/translation-manager.js +1 -1
- package/dist/cjs/extractor/parsers/scope-manager.js +1 -1
- package/dist/cjs/init.js +1 -1
- package/dist/cjs/locize.js +1 -1
- package/dist/cjs/utils/file-utils.js +1 -1
- package/dist/cjs/utils/validation.js +1 -1
- package/dist/esm/cli.js +1 -1
- package/dist/esm/extractor/core/translation-manager.js +1 -1
- package/dist/esm/extractor/parsers/scope-manager.js +1 -1
- package/dist/esm/init.js +1 -1
- package/dist/esm/locize.js +1 -1
- package/dist/esm/utils/file-utils.js +1 -1
- package/dist/esm/utils/validation.js +1 -1
- package/package.json +11 -11
- package/src/cli.ts +2 -2
- package/src/extractor/core/translation-manager.ts +4 -1
- package/src/extractor/parsers/scope-manager.ts +122 -51
- package/src/init.ts +10 -1
- package/src/locize.ts +31 -8
- package/src/types.ts +1 -1
- package/src/utils/file-utils.ts +39 -24
- package/src/utils/validation.ts +5 -2
- package/types/extractor/core/translation-manager.d.ts.map +1 -1
- package/types/extractor/parsers/scope-manager.d.ts +17 -15
- package/types/extractor/parsers/scope-manager.d.ts.map +1 -1
- package/types/init.d.ts.map +1 -1
- package/types/locize.d.ts.map +1 -1
- package/types/types.d.ts +1 -1
- package/types/types.d.ts.map +1 -1
- package/types/utils/file-utils.d.ts +7 -18
- package/types/utils/file-utils.d.ts.map +1 -1
- package/types/utils/validation.d.ts.map +1 -1
package/CHANGELOG.md
CHANGED
|
@@ -5,6 +5,14 @@ All notable changes to this project will be documented in this file.
|
|
|
5
5
|
The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/),
|
|
6
6
|
and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
|
|
7
7
|
|
|
8
|
+
## [1.17.0](https://github.com/i18next/i18next-cli/compare/v1.16.0...v1.17.0) - 2025-10-29
|
|
9
|
+
|
|
10
|
+
- feat: support functional `extract.output` for per-package paths [#80](https://github.com/i18next/i18next-cli/issues/80)
|
|
11
|
+
|
|
12
|
+
## [1.16.0](https://github.com/i18next/i18next-cli/compare/v1.15.0...v1.16.0) - 2025-10-28
|
|
13
|
+
|
|
14
|
+
- feat(extractor): better support for custom translation hooks returning t/getFixedT [#78](https://github.com/i18next/i18next-cli/issues/78)
|
|
15
|
+
|
|
8
16
|
## [1.15.0](https://github.com/i18next/i18next-cli/compare/v1.14.0...v1.15.0) - 2025-10-27
|
|
9
17
|
|
|
10
18
|
- feat(cli): add global `-c, --config <path>` option to override automatic config detection and point the CLI at a specific i18next config file. [#77](https://github.com/i18next/i18next-cli/issues/77)
|
package/dist/cjs/cli.js
CHANGED
|
@@ -1,2 +1,2 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
|
-
"use strict";var e=require("commander"),t=require("chokidar"),o=require("glob"),n=require("minimatch"),i=require("chalk"),a=require("./config.js"),r=require("./heuristic-config.js"),c=require("./extractor/core/extractor.js");require("node:path"),require("node:fs/promises"),require("jiti");var s=require("./types-generator.js"),l=require("./syncer.js"),u=require("./migrator.js"),g=require("./init.js"),d=require("./linter.js"),p=require("./status.js"),f=require("./locize.js");const m=new e.Command;m.name("i18next-cli").description("A unified, high-performance i18next CLI.").version("1.
|
|
2
|
+
"use strict";var e=require("commander"),t=require("chokidar"),o=require("glob"),n=require("minimatch"),i=require("chalk"),a=require("./config.js"),r=require("./heuristic-config.js"),c=require("./extractor/core/extractor.js");require("node:path"),require("node:fs/promises"),require("jiti");var s=require("./types-generator.js"),l=require("./syncer.js"),u=require("./migrator.js"),g=require("./init.js"),d=require("./linter.js"),p=require("./status.js"),f=require("./locize.js");const m=new e.Command;m.name("i18next-cli").description("A unified, high-performance i18next CLI.").version("1.17.0"),m.option("-c, --config <path>","Path to i18next-cli config file (overrides detection)"),m.command("extract").description("Extract translation keys from source files and update resource files.").option("-w, --watch","Watch for file changes and re-run the extractor.").option("--ci","Exit with a non-zero status code if any files are updated.").option("--dry-run","Run the extractor without writing any files to disk.").option("--sync-primary","Sync primary language values with default values from code.").action(async e=>{try{const o=m.opts().config,i=await a.ensureConfig(o),r=async()=>{const t=await c.runExtractor(i,{isWatchMode:!!e.watch,isDryRun:!!e.dryRun,syncPrimaryWithDefaults:!!e.syncPrimary});return e.ci&&!t?(console.log("✅ No files were updated."),process.exit(0)):e.ci&&t&&(console.error("❌ Some files were updated. This should not happen in CI mode."),process.exit(1)),t};if(await r(),e.watch){console.log("\nWatching for changes...");const e=await w(i.extract.input),o=y(i.extract.ignore),a=h(i.extract.output),c=[...o,...a].filter(Boolean),s=e.filter(e=>!c.some(t=>n.minimatch(e,t,{dot:!0})));t.watch(s,{ignored:/node_modules/,persistent:!0}).on("change",e=>{console.log(`\nFile changed: ${e}`),r()})}}catch(e){console.error("Error running extractor:",e),process.exit(1)}}),m.command("status [locale]").description("Display translation status. Provide a locale for a detailed key-by-key view.").option("-n, --namespace <ns>","Filter the status report by a specific namespace").action(async(e,t)=>{const o=m.opts().config;let n=await a.loadConfig(o);if(!n){console.log(i.blue("No config file found. Attempting to detect project structure..."));const e=await r.detectConfig();e||(console.error(i.red("Could not automatically detect your project structure.")),console.log(`Please create a config file first by running: ${i.cyan("npx i18next-cli init")}`),process.exit(1)),console.log(i.green("Project structure detected successfully!")),n=e}await p.runStatus(n,{detail:e,namespace:t.namespace})}),m.command("types").description("Generate TypeScript definitions from translation resource files.").option("-w, --watch","Watch for file changes and re-run the type generator.").action(async e=>{const o=m.opts().config,i=await a.ensureConfig(o),r=()=>s.runTypesGenerator(i);if(await r(),e.watch){console.log("\nWatching for changes...");const e=await w(i.types?.input||[]),o=[...y(i.extract?.ignore)].filter(Boolean),a=e.filter(e=>!o.some(t=>n.minimatch(e,t,{dot:!0})));t.watch(a,{persistent:!0}).on("change",e=>{console.log(`\nFile changed: ${e}`),r()})}}),m.command("sync").description("Synchronize secondary language files with the primary language file.").action(async()=>{const e=m.opts().config,t=await a.ensureConfig(e);await l.runSyncer(t)}),m.command("migrate-config [configPath]").description("Migrate a legacy i18next-parser.config.js to the new format.").action(async e=>{await u.runMigrator(e)}),m.command("init").description("Create a new i18next.config.ts/js file with an interactive setup wizard.").action(g.runInit),m.command("lint").description("Find potential issues like hardcoded strings in your codebase.").option("-w, --watch","Watch for file changes and re-run the linter.").action(async e=>{const o=m.opts().config,c=async()=>{let e=await a.loadConfig(o);if(!e){console.log(i.blue("No config file found. Attempting to detect project structure..."));const t=await r.detectConfig();t||(console.error(i.red("Could not automatically detect your project structure.")),console.log(`Please create a config file first by running: ${i.cyan("npx i18next-cli init")}`),process.exit(1)),console.log(i.green("Project structure detected successfully!")),e=t}await d.runLinter(e)};if(await c(),e.watch){console.log("\nWatching for changes...");const e=await a.loadConfig(o);if(e?.extract?.input){const o=await w(e.extract.input),i=[...y(e.extract.ignore),...h(e.extract.output)].filter(Boolean),a=o.filter(e=>!i.some(t=>n.minimatch(e,t,{dot:!0})));t.watch(a,{ignored:/node_modules/,persistent:!0}).on("change",e=>{console.log(`\nFile changed: ${e}`),c()})}}}),m.command("locize-sync").description("Synchronize local translations with your locize project.").option("--update-values","Update values of existing translations on locize.").option("--src-lng-only","Check for changes in source language only.").option("--compare-mtime","Compare modification times when syncing.").option("--dry-run","Run the command without making any changes.").action(async e=>{const t=m.opts().config,o=await a.ensureConfig(t);await f.runLocizeSync(o,e)}),m.command("locize-download").description("Download all translations from your locize project.").action(async e=>{const t=m.opts().config,o=await a.ensureConfig(t);await f.runLocizeDownload(o,e)}),m.command("locize-migrate").description("Migrate local translation files to a new locize project.").action(async e=>{const t=m.opts().config,o=await a.ensureConfig(t);await f.runLocizeMigrate(o,e)}),m.parse(process.argv);const y=e=>Array.isArray(e)?e:e?[e]:[],h=e=>e&&"string"==typeof e?[e.replace(/\{\{[^}]+\}\}/g,"*")]:[],w=async(e=[])=>{const t=y(e),n=await Promise.all(t.map(e=>o.glob(e||"",{nodir:!0})));return Array.from(new Set(n.flat()))};
|
|
@@ -1 +1 @@
|
|
|
1
|
-
"use strict";var e=require("node:path"),t=require("glob"),s=require("../../utils/nested-object.js"),r=require("../../utils/file-utils.js"),n=require("../../utils/default-value.js");function a(e){const t=`^${e.replace(/[.+?^${}()|[\]\\]/g,"\\$&").replace(/\*/g,".*")}$`;return new RegExp(t)}function o(e,t){if("object"!=typeof e||null===e||Array.isArray(e))return e;const s={},r=t?.extract?.pluralSeparator??"_",n=["zero","one","two","few","many","other"],a=n.map(e=>`ordinal${r}${e}`),l=Object.keys(e).sort((e,t)=>{const s=e=>{for(const t of a)if(e.endsWith(`${r}${t}`)){return{base:e.slice(0,-(r.length+t.length)),form:t,isOrdinal:!0,isPlural:!0,fullKey:e}}for(const t of n)if(e.endsWith(`${r}${t}`)){return{base:e.slice(0,-(r.length+t.length)),form:t,isOrdinal:!1,isPlural:!0,fullKey:e}}return{base:e,form:"",isOrdinal:!1,isPlural:!1,fullKey:e}},o=s(e),l=s(t);if(o.isPlural&&l.isPlural){const e=o.base.localeCompare(l.base,void 0,{sensitivity:"base"});if(0!==e)return e;if(o.isOrdinal!==l.isOrdinal)return o.isOrdinal?1:-1;const t=o.isOrdinal?a:n,s=t.indexOf(o.form),r=t.indexOf(l.form);return-1!==s&&-1!==r?s-r:o.form.localeCompare(l.form)}const i=e.localeCompare(t,void 0,{sensitivity:"base"});return 0===i?e.localeCompare(t,void 0,{sensitivity:"case"}):i});for(const r of l)s[r]=o(e[r],t);return s}function l(e,t,r,a,l,i=[],c=new Set,u=!1){const{keySeparator:f=".",sort:d=!0,removeUnusedKeys:p=!0,primaryLanguage:g,defaultValue:y="",pluralSeparator:h="_",contextSeparator:m="_"}=r.extract,x=new Set;try{const e=new Intl.PluralRules(a,{type:"cardinal"}),t=new Intl.PluralRules(a,{type:"ordinal"});e.resolvedOptions().pluralCategories.forEach(e=>x.add(e)),t.resolvedOptions().pluralCategories.forEach(e=>x.add(`ordinal_${e}`))}catch(e){const t=new Intl.PluralRules(g||"en",{type:"cardinal"}),s=new Intl.PluralRules(g||"en",{type:"ordinal"});t.resolvedOptions().pluralCategories.forEach(e=>x.add(e)),s.resolvedOptions().pluralCategories.forEach(e=>x.add(`ordinal_${e}`))}const O=e.filter(({key:e,hasCount:t,isOrdinal:s})=>{if(i.some(t=>t.test(e)))return!1;if(!t)return!0;const r=e.split(h);if(s&&r.includes("ordinal")){const e=r[r.length-1];return x.has(`ordinal_${e}`)}if(t){const e=r[r.length-1];return x.has(e)}return!0});let v=p?{}:JSON.parse(JSON.stringify(t));const w=s.getNestedKeys(t,f??".");for(const e of w)if(i.some(t=>t.test(e))){const r=s.getNestedValue(t,e,f??".");s.setNestedValue(v,e,r,f??".")}if(p){const e=s.getNestedKeys(t,f??".");for(const r of e){const e=r.split(h);if("zero"===e[e.length-1]){const n=e.slice(0,-1).join(h);if(O.some(({key:e})=>e.split(h).slice(0,-1).join(h)===n)){const e=s.getNestedValue(t,r,f??".");s.setNestedValue(v,r,e,f??".")}}}}for(const{key:e,defaultValue:o,explicitDefault:i}of O){const d=s.getNestedValue(t,e,f??"."),p=!O.some(t=>t.key.startsWith(`${e}${f}`)&&t.key!==e),x="object"==typeof d&&null!==d&&(c.has(e)||!o||o===e),w="object"==typeof d&&null!==d&&p&&!c.has(e)&&!x;if(x){s.setNestedValue(v,e,d,f??".");continue}let N;if(void 0===d||w)if(a===g)if(u){const t=o&&(o===e||e!==o&&(e.startsWith(o+h)||e.startsWith(o+m)));N=o&&!t?o:n.resolveDefaultValue(y,e,l||r?.extract?.defaultNS||"translation",a,o)}else N=o||e;else N=n.resolveDefaultValue(y,e,l||r?.extract?.defaultNS||"translation",a,o);else if(a===g&&u){const t=o&&(o===e||e!==o&&(e.startsWith(o+h)||e.startsWith(o+m)));N=(e.includes(h)||e.includes(m))&&!i?d:o&&!t?o:d}else N=d;s.setNestedValue(v,e,N,f??".")}if(!0===d)return o(v,r);if("function"==typeof d){const e={},t=Object.keys(v),s=new Map;for(const e of O){const t=!1===f?e.key:e.key.split(f)[0];s.has(t)||s.set(t,e)}t.sort((e,t)=>{if("function"==typeof d){const r=s.get(e),n=s.get(t);if(r&&n)return d(r,n)}return e.localeCompare(t,void 0,{sensitivity:"base"})});for(const s of t)e[s]=o(v[s],r);v=e}return v}exports.getTranslations=async function(s,n,o,{syncPrimaryWithDefaults:i=!1}={}){o.extract.primaryLanguage||=o.locales[0]||"en",o.extract.secondaryLanguages||=o.locales.filter(e=>e!==o?.extract?.primaryLanguage);const c=[...o.extract.preservePatterns||[]],u=o.extract.indentation??2;for(const e of n)c.push(`${e}.*`);const f=c.map(a),d="__no_namespace__",p=new Map;for(const e of s.values()){const t=e.nsIsImplicit&&!1===o.extract.defaultNS?d:String(e.ns??o.extract.defaultNS??"translation");p.has(t)||p.set(t,[]),p.get(t).push(e)}const g=[],y=Array.isArray(o.extract.ignore)?o.extract.ignore:o.extract.ignore?[o.extract.ignore]:[];for(const s of o.locales){if(o.extract.mergeNamespaces
|
|
1
|
+
"use strict";var e=require("node:path"),t=require("glob"),s=require("../../utils/nested-object.js"),r=require("../../utils/file-utils.js"),n=require("../../utils/default-value.js");function a(e){const t=`^${e.replace(/[.+?^${}()|[\]\\]/g,"\\$&").replace(/\*/g,".*")}$`;return new RegExp(t)}function o(e,t){if("object"!=typeof e||null===e||Array.isArray(e))return e;const s={},r=t?.extract?.pluralSeparator??"_",n=["zero","one","two","few","many","other"],a=n.map(e=>`ordinal${r}${e}`),l=Object.keys(e).sort((e,t)=>{const s=e=>{for(const t of a)if(e.endsWith(`${r}${t}`)){return{base:e.slice(0,-(r.length+t.length)),form:t,isOrdinal:!0,isPlural:!0,fullKey:e}}for(const t of n)if(e.endsWith(`${r}${t}`)){return{base:e.slice(0,-(r.length+t.length)),form:t,isOrdinal:!1,isPlural:!0,fullKey:e}}return{base:e,form:"",isOrdinal:!1,isPlural:!1,fullKey:e}},o=s(e),l=s(t);if(o.isPlural&&l.isPlural){const e=o.base.localeCompare(l.base,void 0,{sensitivity:"base"});if(0!==e)return e;if(o.isOrdinal!==l.isOrdinal)return o.isOrdinal?1:-1;const t=o.isOrdinal?a:n,s=t.indexOf(o.form),r=t.indexOf(l.form);return-1!==s&&-1!==r?s-r:o.form.localeCompare(l.form)}const i=e.localeCompare(t,void 0,{sensitivity:"base"});return 0===i?e.localeCompare(t,void 0,{sensitivity:"case"}):i});for(const r of l)s[r]=o(e[r],t);return s}function l(e,t,r,a,l,i=[],c=new Set,u=!1){const{keySeparator:f=".",sort:d=!0,removeUnusedKeys:p=!0,primaryLanguage:g,defaultValue:y="",pluralSeparator:h="_",contextSeparator:m="_"}=r.extract,x=new Set;try{const e=new Intl.PluralRules(a,{type:"cardinal"}),t=new Intl.PluralRules(a,{type:"ordinal"});e.resolvedOptions().pluralCategories.forEach(e=>x.add(e)),t.resolvedOptions().pluralCategories.forEach(e=>x.add(`ordinal_${e}`))}catch(e){const t=new Intl.PluralRules(g||"en",{type:"cardinal"}),s=new Intl.PluralRules(g||"en",{type:"ordinal"});t.resolvedOptions().pluralCategories.forEach(e=>x.add(e)),s.resolvedOptions().pluralCategories.forEach(e=>x.add(`ordinal_${e}`))}const O=e.filter(({key:e,hasCount:t,isOrdinal:s})=>{if(i.some(t=>t.test(e)))return!1;if(!t)return!0;const r=e.split(h);if(s&&r.includes("ordinal")){const e=r[r.length-1];return x.has(`ordinal_${e}`)}if(t){const e=r[r.length-1];return x.has(e)}return!0});let v=p?{}:JSON.parse(JSON.stringify(t));const w=s.getNestedKeys(t,f??".");for(const e of w)if(i.some(t=>t.test(e))){const r=s.getNestedValue(t,e,f??".");s.setNestedValue(v,e,r,f??".")}if(p){const e=s.getNestedKeys(t,f??".");for(const r of e){const e=r.split(h);if("zero"===e[e.length-1]){const n=e.slice(0,-1).join(h);if(O.some(({key:e})=>e.split(h).slice(0,-1).join(h)===n)){const e=s.getNestedValue(t,r,f??".");s.setNestedValue(v,r,e,f??".")}}}}for(const{key:e,defaultValue:o,explicitDefault:i}of O){const d=s.getNestedValue(t,e,f??"."),p=!O.some(t=>t.key.startsWith(`${e}${f}`)&&t.key!==e),x="object"==typeof d&&null!==d&&(c.has(e)||!o||o===e),w="object"==typeof d&&null!==d&&p&&!c.has(e)&&!x;if(x){s.setNestedValue(v,e,d,f??".");continue}let N;if(void 0===d||w)if(a===g)if(u){const t=o&&(o===e||e!==o&&(e.startsWith(o+h)||e.startsWith(o+m)));N=o&&!t?o:n.resolveDefaultValue(y,e,l||r?.extract?.defaultNS||"translation",a,o)}else N=o||e;else N=n.resolveDefaultValue(y,e,l||r?.extract?.defaultNS||"translation",a,o);else if(a===g&&u){const t=o&&(o===e||e!==o&&(e.startsWith(o+h)||e.startsWith(o+m)));N=(e.includes(h)||e.includes(m))&&!i?d:o&&!t?o:d}else N=d;s.setNestedValue(v,e,N,f??".")}if(!0===d)return o(v,r);if("function"==typeof d){const e={},t=Object.keys(v),s=new Map;for(const e of O){const t=!1===f?e.key:e.key.split(f)[0];s.has(t)||s.set(t,e)}t.sort((e,t)=>{if("function"==typeof d){const r=s.get(e),n=s.get(t);if(r&&n)return d(r,n)}return e.localeCompare(t,void 0,{sensitivity:"base"})});for(const s of t)e[s]=o(v[s],r);v=e}return v}exports.getTranslations=async function(s,n,o,{syncPrimaryWithDefaults:i=!1}={}){o.extract.primaryLanguage||=o.locales[0]||"en",o.extract.secondaryLanguages||=o.locales.filter(e=>e!==o?.extract?.primaryLanguage);const c=[...o.extract.preservePatterns||[]],u=o.extract.indentation??2;for(const e of n)c.push(`${e}.*`);const f=c.map(a),d="__no_namespace__",p=new Map;for(const e of s.values()){const t=e.nsIsImplicit&&!1===o.extract.defaultNS?d:String(e.ns??o.extract.defaultNS??"translation");p.has(t)||p.set(t,[]),p.get(t).push(e)}const g=[],y=Array.isArray(o.extract.ignore)?o.extract.ignore:o.extract.ignore?[o.extract.ignore]:[];for(const s of o.locales){if(o.extract.mergeNamespaces||"string"==typeof o.extract.output&&!o.extract.output.includes("{{namespace}}")){const t={},a=r.getOutputPath(o.extract.output,s),c=e.resolve(process.cwd(),a),y=await r.loadTranslationFile(c)||{},h=new Set([...p.keys(),...Object.keys(y)]);for(const e of h){const r=p.get(e)||[];if(e===d){const e=l(r,y,o,s,void 0,f,n,i);Object.assign(t,e)}else{const a=y[e]||{};t[e]=l(r,a,o,s,e,f,n,i)}}const m=JSON.stringify(y,null,u),x=JSON.stringify(t,null,u);g.push({path:c,updated:x!==m,newTranslations:t,existingTranslations:y})}else{const a=new Set(p.keys()),c=r.getOutputPath(o.extract.output,s,"*"),d=await t.glob(c,{ignore:y});for(const t of d)a.add(e.basename(t,e.extname(t)));for(const t of a){const a=p.get(t)||[],c=r.getOutputPath(o.extract.output,s,t),d=e.resolve(process.cwd(),c),y=await r.loadTranslationFile(d)||{},h=l(a,y,o,s,t,f,n,i),m=JSON.stringify(y,null,u),x=JSON.stringify(h,null,u);g.push({path:d,updated:x!==m,newTranslations:h,existingTranslations:y})}}}return g};
|
|
@@ -1 +1 @@
|
|
|
1
|
-
"use strict";var e=require("./ast-utils.js");exports.ScopeManager=class{scopeStack=[];config;scope=new Map;constructor(e){this.config=e}reset(){this.scopeStack=[],this.scope=new Map}enterScope(){this.scopeStack.push(new Map)}exitScope(){this.scopeStack.pop()}setVarInScope(e,t){this.scopeStack.length>0
|
|
1
|
+
"use strict";var e=require("./ast-utils.js");exports.ScopeManager=class{scopeStack=[];config;scope=new Map;simpleConstants=new Map;constructor(e){this.config=e}reset(){this.scopeStack=[],this.scope=new Map,this.simpleConstants.clear()}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):this.scope.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)}const t=this.scope.get(e);if(t)return t}getUseTranslationConfig(e){const t=this.config.extract.useTranslationNames||["useTranslation"];for(const i of t){if("string"==typeof i&&i===e)return{name:e,nsArg:0,keyPrefixArg:1};if("object"==typeof i&&i.name===e)return{name:i.name,nsArg:i.nsArg??0,keyPrefixArg:i.keyPrefixArg??1}}}resolveSimpleStringIdentifier(e){return this.simpleConstants.get(e)}handleVariableDeclarator(e){const t=e.init;if(!t)return;"Identifier"===e.id.type&&"StringLiteral"===t.type&&this.simpleConstants.set(e.id.value,t.value);const i="AwaitExpression"===t.type&&"CallExpression"===t.argument.type?t.argument:"CallExpression"===t.type?t:null;if(!i)return;const r=i.callee;if("Identifier"===r.type){const t=this.getUseTranslationConfig(r.value);if(t)return this.handleUseTranslationDeclarator(e,i,t),void this.handleUseTranslationForComments(e,i,t)}if("Identifier"===r.type){if(this.getVarFromScope(r.value))return void this.handleGetFixedTFromVariableDeclarator(e,i,r.value)}"MemberExpression"===r.type&&"Identifier"===r.property.type&&"getFixedT"===r.property.value&&this.handleGetFixedTDeclarator(e,i)}handleUseTranslationForComments(e,t,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||"getFixedT"===t.key.value)){r=t.key.value;break}if("KeyValuePatternProperty"===t.type&&"Identifier"===t.key.type&&("t"===t.key.value||"getFixedT"===t.key.value)&&"Identifier"===t.value.type){r=t.value.value;break}}if(!r)return;const s=-1===i.nsArg?void 0:t.arguments?.[i.nsArg??0]?.expression,n=t.arguments?.[i.keyPrefixArg??1]?.expression;let a,o;if("StringLiteral"===s?.type?a=s.value:"ArrayExpression"===s?.type&&"StringLiteral"===s.elements[0]?.expression.type&&(a=s.elements[0].expression.value),"ObjectExpression"===n?.type){const e=n.properties.find(e=>"KeyValueProperty"===e.type&&"Identifier"===e.key.type&&"keyPrefix"===e.key.value);"KeyValueProperty"===e?.type&&"StringLiteral"===e.value.type&&(o=e.value.value)}else if("StringLiteral"===n?.type)o=n.value;else if("Identifier"===n?.type)o=this.resolveSimpleStringIdentifier(n.value);else if("TemplateLiteral"===n?.type){const e=n;0===(e.expressions||[]).length&&(o=e.quasis?.[0]?.cooked??void 0)}(a||o)&&this.scope.set(r,{defaultNs:a,keyPrefix:o})}handleUseTranslationDeclarator(t,i,r){let s;if("Identifier"===t.id.type&&(s=t.id.value),"ArrayPattern"===t.id.type){const e=t.id.elements[0];"Identifier"===e?.type&&(s=e.value)}if("ObjectPattern"===t.id.type)for(const e of t.id.properties){if("AssignmentPatternProperty"===e.type&&"Identifier"===e.key.type&&("t"===e.key.value||"getFixedT"===e.key.value)){s=e.key.value;break}if("KeyValuePatternProperty"===e.type&&"Identifier"===e.key.type&&("t"===e.key.value||"getFixedT"===e.key.value)&&"Identifier"===e.value.type){s=e.value.value;break}}if(!s)return;const n=-1===r.nsArg?void 0:i.arguments?.[r.nsArg??0]?.expression;let a;"StringLiteral"===n?.type?a=n.value:"ArrayExpression"===n?.type&&"StringLiteral"===n.elements[0]?.expression.type&&(a=n.elements[0].expression.value);const o=i.arguments?.[r.keyPrefixArg??1]?.expression;let l;if("ObjectExpression"===o?.type){const t=e.getObjectPropValue(o,"keyPrefix");l="string"==typeof t?t:void 0}else if("StringLiteral"===o?.type)l=o.value;else if("Identifier"===o?.type)l=this.resolveSimpleStringIdentifier(o.value);else if("TemplateLiteral"===o?.type){const e=o;0===(e.expressions||[]).length&&(l=e.quasis?.[0]?.cooked??void 0)}this.setVarInScope(s,{defaultNs:a,keyPrefix:l})}handleGetFixedTDeclarator(e,t){if("Identifier"!==e.id.type||!e.init||"CallExpression"!==e.init.type)return;const i=e.id.value,r=t.arguments,s=r[1]?.expression,n=r[2]?.expression,a="StringLiteral"===s?.type?s.value:void 0,o="StringLiteral"===n?.type?n.value:void 0;(a||o)&&this.setVarInScope(i,{defaultNs:a,keyPrefix:o})}handleGetFixedTFromVariableDeclarator(e,t,i){if("Identifier"!==e.id.type)return;const r=e.id.value,s=this.getVarFromScope(i);if(!s)return;const n=t.arguments,a=n[1]?.expression,o=n[2]?.expression,l="StringLiteral"===a?.type?a.value:void 0,p="StringLiteral"===o?.type?o.value:void 0,y=l??s.defaultNs,f=p??s.keyPrefix;(y||f)&&this.setVarInScope(r,{defaultNs:y,keyPrefix:f})}};
|
package/dist/cjs/init.js
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
"use strict";var
|
|
1
|
+
"use strict";var t=require("inquirer"),e=require("node:fs/promises"),n=require("node:path"),i=require("./heuristic-config.js");exports.runInit=async function(){console.log("Welcome to the i18next-cli setup wizard!"),console.log("Scanning your project for a recommended configuration...");const o=await i.detectConfig();o?console.log("✅ Found a potential project structure. Using it for suggestions."):console.log("Could not detect a project structure. Using standard defaults."),"string"==typeof o?.extract?.input&&(o.extract.input=[o?.extract?.input]),o&&"function"==typeof o.extract?.output&&delete o.extract.output;const r=await t.prompt([{type:"list",name:"fileType",message:"What kind of configuration file do you want?",choices:["TypeScript (i18next.config.ts)","JavaScript (i18next.config.js)"]},{type:"input",name:"locales",message:"What locales does your project support? (comma-separated)",default:o?.locales?.join(",")||"en,de,fr",filter:t=>t.split(",").map(t=>t.trim())},{type:"input",name:"input",message:"What is the glob pattern for your source files?",default:o?.extract?.input?(o.extract.input||[])[0]:"src/**/*.{js,jsx,ts,tsx}"},{type:"input",name:"output",message:"What is the path for your output resource files?",default:"string"==typeof o?.extract?.output?o.extract.output:"public/locales/{{language}}/{{namespace}}.json"}]),s=r.fileType.includes("TypeScript"),a=await async function(){try{const t=n.resolve(process.cwd(),"package.json"),i=await e.readFile(t,"utf-8");return"module"===JSON.parse(i).type}catch{return!0}}(),c=s?"i18next.config.ts":"i18next.config.js",u={locales:r.locales,extract:{input:r.input,output:r.output}};function p(t,e=2,n=0){const i=t=>" ".repeat(t*e),o=i(n),r=i(n+1);if(null===t||"number"==typeof t||"boolean"==typeof t)return JSON.stringify(t);if("string"==typeof t)return JSON.stringify(t);if(Array.isArray(t)){if(0===t.length)return"[]";return`[\n${t.map(t=>`${r}${p(t,e,n+1)}`).join(",\n")}\n${o}]`}if("object"==typeof t){const i=Object.keys(t);if(0===i.length)return"{}";return`{\n${i.map(i=>{const o=/^[A-Za-z_$][A-Za-z0-9_$]*$/.test(i)?i:JSON.stringify(i);return`${r}${o}: ${p(t[i],e,n+1)}`}).join(",\n")}\n${o}}`}return JSON.stringify(t)}let f="";f=s?`import { defineConfig } from 'i18next-cli';\n\nexport default defineConfig(${p(u)});`:a?`import { defineConfig } from 'i18next-cli';\n\n/** @type {import('i18next-cli').I18nextToolkitConfig} */\nexport default defineConfig(${p(u)});`:`const { defineConfig } = require('i18next-cli');\n\n/** @type {import('i18next-cli').I18nextToolkitConfig} */\nmodule.exports = defineConfig(${p(u)});`;const l=n.resolve(process.cwd(),c);await e.writeFile(l,f.trim()),console.log(`✅ Configuration file created at: ${l}`)};
|
package/dist/cjs/locize.js
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
"use strict";var e=require("execa"),o=require("chalk"),n=require("ora"),t=require("inquirer"),
|
|
1
|
+
"use strict";var e=require("execa"),o=require("chalk"),n=require("ora"),t=require("inquirer"),r=require("node:path");function s(e,o,n){const{locize:t={},extract:s}=o,{projectId:i,apiKey:c,version:a}=t,l=[e];if(i&&l.push("--project-id",i),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")}let u;try{if("string"==typeof s.output){const e=s.output.replace(/\\/g,"/"),o=(e.includes("/{{language}}/")?e.split("/{{language}}/")[0]:e.replace("{{language}}","")).split("/").join(r.sep);u=r.resolve(process.cwd(),o)}else if("function"==typeof s.output)try{const e=s.output(o.extract.primaryLanguage||"en"),n=String(e).replace(/\\/g,"/"),t=n.includes("/"+(o.extract.primaryLanguage||"en")+"/")?n.split("/"+(o.extract.primaryLanguage||"en")+"/")[0]:n.replace(o.extract.primaryLanguage||"en","");u=r.resolve(process.cwd(),t.split("/").join(r.sep))}catch{u=r.resolve(process.cwd(),".")}else u=r.resolve(process.cwd(),".")}catch{u=r.resolve(process.cwd(),".")}return l.push("--path",u),l}async function i(r,i,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 ${r}'...\n`).start();let l=i;try{const n=s(r,l,c);console.log(o.cyan(`\nRunning 'locize ${n.join(" ")}'...`));const t=await e.execa("locize",n,{stdio:"pipe"});a.succeed(o.green(`'locize ${r}' completed successfully.`)),t?.stdout&&console.log(t.stdout)}catch(n){const i=n.stderr||"";if(i.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(r,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 ${r}'.`)),console.error(i||n.message),process.exit(1)}console.log(o.green(`\n✅ 'locize ${r}' completed successfully.`))}exports.runLocizeDownload=(e,o)=>i("download",e,o),exports.runLocizeMigrate=(e,o)=>i("migrate",e,o),exports.runLocizeSync=(e,o)=>i("sync",e,o);
|
|
@@ -1 +1 @@
|
|
|
1
|
-
"use strict";var e=require("node:fs/promises"),t=require("node:path"),r=require("jiti"),
|
|
1
|
+
"use strict";var e=require("node:fs/promises"),t=require("node:path"),r=require("jiti"),n=require("../config.js");exports.getOutputPath=function(e,r,n){if(!e)return t.normalize(`locales/${r}/${n??"translation"}.json`);if("function"==typeof e)try{const a=String(e(r,n));return t.normalize(a.replace(/\/\/+/g,"/"))}catch{return t.normalize(`locales/${r}/${n??"translation"}.json`)}let a=String(e);return a=a.replace(/\{\{language\}\}|\{\{lng\}\}/g,r),a=null!=n?a.replace(/\{\{namespace\}\}/g,n):a.replace(/\/?\{\{namespace\}\}/g,""),a=a.replace(/\/\/+/g,"/"),t.normalize(a)},exports.loadTranslationFile=async function(a){const s=t.resolve(process.cwd(),a);try{await e.access(s)}catch{return null}try{const a=t.extname(s).toLowerCase();if(".json"===a){const t=await e.readFile(s,"utf-8");return JSON.parse(t)}if(".ts"===a||".js"===a){const e=await n.getTsConfigAliases(),t=r.createJiti(process.cwd(),{alias:e,interopDefault:!0});return await t.import(s,{default:!0})}return null}catch(e){return console.warn(`Could not parse translation file ${a}:`,e),null}},exports.serializeTranslationFile=function(e,t="json",r=2){const n=JSON.stringify(e,null,r);switch(t){case"js":case"js-esm":return`export default ${n};\n`;case"js-cjs":return`module.exports = ${n};\n`;case"ts":return`export default ${n} as const;\n`;default:return`${n}\n`}};
|
|
@@ -1 +1 @@
|
|
|
1
|
-
"use strict";class t extends Error{file;cause;constructor(t,e,r){super(e?`${t} in file ${e}`:t),this.file=e,this.cause=r,this.name="ExtractorError"}}exports.ExtractorError=t,exports.validateExtractorConfig=function(e){if(!e.extract.input?.length)throw new t("extract.input must be specified and non-empty");if(!e.extract.output)throw new t("extract.output must be specified");if(!e.locales?.length)throw new t("locales must be specified and non-empty");if(
|
|
1
|
+
"use strict";class t extends Error{file;cause;constructor(t,e,r){super(e?`${t} in file ${e}`:t),this.file=e,this.cause=r,this.name="ExtractorError"}}exports.ExtractorError=t,exports.validateExtractorConfig=function(e){if(!e.extract.input?.length)throw new t("extract.input must be specified and non-empty");if(!e.extract.output)throw new t("extract.output must be specified");if(!e.locales?.length)throw new t("locales must be specified and non-empty");if("string"==typeof e.extract.output&&!e.extract.output.includes("{{language}}")&&!e.extract.output.includes("{{lng}}"))throw new t("extract.output must contain {{language}} placeholder")};
|
package/dist/esm/cli.js
CHANGED
|
@@ -1,2 +1,2 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
|
-
import{Command as t}from"commander";import o from"chokidar";import{glob as e}from"glob";import{minimatch as i}from"minimatch";import n from"chalk";import{ensureConfig as a,loadConfig as r}from"./config.js";import{detectConfig as c}from"./heuristic-config.js";import{runExtractor as s}from"./extractor/core/extractor.js";import"node:path";import"node:fs/promises";import"jiti";import{runTypesGenerator as l}from"./types-generator.js";import{runSyncer as p}from"./syncer.js";import{runMigrator as m}from"./migrator.js";import{runInit as f}from"./init.js";import{runLinter as d}from"./linter.js";import{runStatus as g}from"./status.js";import{runLocizeSync as u,runLocizeDownload as y,runLocizeMigrate as h}from"./locize.js";const w=new t;w.name("i18next-cli").description("A unified, high-performance i18next CLI.").version("1.
|
|
2
|
+
import{Command as t}from"commander";import o from"chokidar";import{glob as e}from"glob";import{minimatch as i}from"minimatch";import n from"chalk";import{ensureConfig as a,loadConfig as r}from"./config.js";import{detectConfig as c}from"./heuristic-config.js";import{runExtractor as s}from"./extractor/core/extractor.js";import"node:path";import"node:fs/promises";import"jiti";import{runTypesGenerator as l}from"./types-generator.js";import{runSyncer as p}from"./syncer.js";import{runMigrator as m}from"./migrator.js";import{runInit as f}from"./init.js";import{runLinter as d}from"./linter.js";import{runStatus as g}from"./status.js";import{runLocizeSync as u,runLocizeDownload as y,runLocizeMigrate as h}from"./locize.js";const w=new t;w.name("i18next-cli").description("A unified, high-performance i18next CLI.").version("1.17.0"),w.option("-c, --config <path>","Path to i18next-cli config file (overrides detection)"),w.command("extract").description("Extract translation keys from source files and update resource files.").option("-w, --watch","Watch for file changes and re-run the extractor.").option("--ci","Exit with a non-zero status code if any files are updated.").option("--dry-run","Run the extractor without writing any files to disk.").option("--sync-primary","Sync primary language values with default values from code.").action(async t=>{try{const e=w.opts().config,n=await a(e),r=async()=>{const o=await s(n,{isWatchMode:!!t.watch,isDryRun:!!t.dryRun,syncPrimaryWithDefaults:!!t.syncPrimary});return t.ci&&!o?(console.log("✅ No files were updated."),process.exit(0)):t.ci&&o&&(console.error("❌ Some files were updated. This should not happen in CI mode."),process.exit(1)),o};if(await r(),t.watch){console.log("\nWatching for changes...");const t=await z(n.extract.input),e=x(n.extract.ignore),a=j(n.extract.output),c=[...e,...a].filter(Boolean),s=t.filter(t=>!c.some(o=>i(t,o,{dot:!0})));o.watch(s,{ignored:/node_modules/,persistent:!0}).on("change",t=>{console.log(`\nFile changed: ${t}`),r()})}}catch(t){console.error("Error running extractor:",t),process.exit(1)}}),w.command("status [locale]").description("Display translation status. Provide a locale for a detailed key-by-key view.").option("-n, --namespace <ns>","Filter the status report by a specific namespace").action(async(t,o)=>{const e=w.opts().config;let i=await r(e);if(!i){console.log(n.blue("No config file found. Attempting to detect project structure..."));const t=await c();t||(console.error(n.red("Could not automatically detect your project structure.")),console.log(`Please create a config file first by running: ${n.cyan("npx i18next-cli init")}`),process.exit(1)),console.log(n.green("Project structure detected successfully!")),i=t}await g(i,{detail:t,namespace:o.namespace})}),w.command("types").description("Generate TypeScript definitions from translation resource files.").option("-w, --watch","Watch for file changes and re-run the type generator.").action(async t=>{const e=w.opts().config,n=await a(e),r=()=>l(n);if(await r(),t.watch){console.log("\nWatching for changes...");const t=await z(n.types?.input||[]),e=[...x(n.extract?.ignore)].filter(Boolean),a=t.filter(t=>!e.some(o=>i(t,o,{dot:!0})));o.watch(a,{persistent:!0}).on("change",t=>{console.log(`\nFile changed: ${t}`),r()})}}),w.command("sync").description("Synchronize secondary language files with the primary language file.").action(async()=>{const t=w.opts().config,o=await a(t);await p(o)}),w.command("migrate-config [configPath]").description("Migrate a legacy i18next-parser.config.js to the new format.").action(async t=>{await m(t)}),w.command("init").description("Create a new i18next.config.ts/js file with an interactive setup wizard.").action(f),w.command("lint").description("Find potential issues like hardcoded strings in your codebase.").option("-w, --watch","Watch for file changes and re-run the linter.").action(async t=>{const e=w.opts().config,a=async()=>{let t=await r(e);if(!t){console.log(n.blue("No config file found. Attempting to detect project structure..."));const o=await c();o||(console.error(n.red("Could not automatically detect your project structure.")),console.log(`Please create a config file first by running: ${n.cyan("npx i18next-cli init")}`),process.exit(1)),console.log(n.green("Project structure detected successfully!")),t=o}await d(t)};if(await a(),t.watch){console.log("\nWatching for changes...");const t=await r(e);if(t?.extract?.input){const e=await z(t.extract.input),n=[...x(t.extract.ignore),...j(t.extract.output)].filter(Boolean),r=e.filter(t=>!n.some(o=>i(t,o,{dot:!0})));o.watch(r,{ignored:/node_modules/,persistent:!0}).on("change",t=>{console.log(`\nFile changed: ${t}`),a()})}}}),w.command("locize-sync").description("Synchronize local translations with your locize project.").option("--update-values","Update values of existing translations on locize.").option("--src-lng-only","Check for changes in source language only.").option("--compare-mtime","Compare modification times when syncing.").option("--dry-run","Run the command without making any changes.").action(async t=>{const o=w.opts().config,e=await a(o);await u(e,t)}),w.command("locize-download").description("Download all translations from your locize project.").action(async t=>{const o=w.opts().config,e=await a(o);await y(e,t)}),w.command("locize-migrate").description("Migrate local translation files to a new locize project.").action(async t=>{const o=w.opts().config,e=await a(o);await h(e,t)}),w.parse(process.argv);const x=t=>Array.isArray(t)?t:t?[t]:[],j=t=>t&&"string"==typeof t?[t.replace(/\{\{[^}]+\}\}/g,"*")]:[],z=async(t=[])=>{const o=x(t),i=await Promise.all(o.map(t=>e(t||"",{nodir:!0})));return Array.from(new Set(i.flat()))};
|
|
@@ -1 +1 @@
|
|
|
1
|
-
import{resolve as t,basename as e,extname as r}from"node:path";import{glob as s}from"glob";import{getNestedKeys as n,getNestedValue as o,setNestedValue as a}from"../../utils/nested-object.js";import{getOutputPath as i,loadTranslationFile as l}from"../../utils/file-utils.js";import{resolveDefaultValue as c}from"../../utils/default-value.js";function f(t){const e=`^${t.replace(/[.+?^${}()|[\]\\]/g,"\\$&").replace(/\*/g,".*")}$`;return new RegExp(e)}function u(t,e){if("object"!=typeof t||null===t||Array.isArray(t))return t;const r={},s=e?.extract?.pluralSeparator??"_",n=["zero","one","two","few","many","other"],o=n.map(t=>`ordinal${s}${t}`),a=Object.keys(t).sort((t,e)=>{const r=t=>{for(const e of o)if(t.endsWith(`${s}${e}`)){return{base:t.slice(0,-(s.length+e.length)),form:e,isOrdinal:!0,isPlural:!0,fullKey:t}}for(const e of n)if(t.endsWith(`${s}${e}`)){return{base:t.slice(0,-(s.length+e.length)),form:e,isOrdinal:!1,isPlural:!0,fullKey:t}}return{base:t,form:"",isOrdinal:!1,isPlural:!1,fullKey:t}},a=r(t),i=r(e);if(a.isPlural&&i.isPlural){const t=a.base.localeCompare(i.base,void 0,{sensitivity:"base"});if(0!==t)return t;if(a.isOrdinal!==i.isOrdinal)return a.isOrdinal?1:-1;const e=a.isOrdinal?o:n,r=e.indexOf(a.form),s=e.indexOf(i.form);return-1!==r&&-1!==s?r-s:a.form.localeCompare(i.form)}const l=t.localeCompare(e,void 0,{sensitivity:"base"});return 0===l?t.localeCompare(e,void 0,{sensitivity:"case"}):l});for(const s of a)r[s]=u(t[s],e);return r}function p(t,e,r,s,i,l=[],f=new Set,p=!1){const{keySeparator:d=".",sort:y=!0,removeUnusedKeys:g=!0,primaryLanguage:m,defaultValue:h="",pluralSeparator:x="_",contextSeparator:O="_"}=r.extract,w=new Set;try{const t=new Intl.PluralRules(s,{type:"cardinal"}),e=new Intl.PluralRules(s,{type:"ordinal"});t.resolvedOptions().pluralCategories.forEach(t=>w.add(t)),e.resolvedOptions().pluralCategories.forEach(t=>w.add(`ordinal_${t}`))}catch(t){const e=new Intl.PluralRules(m||"en",{type:"cardinal"}),r=new Intl.PluralRules(m||"en",{type:"ordinal"});e.resolvedOptions().pluralCategories.forEach(t=>w.add(t)),r.resolvedOptions().pluralCategories.forEach(t=>w.add(`ordinal_${t}`))}const S=t.filter(({key:t,hasCount:e,isOrdinal:r})=>{if(l.some(e=>e.test(t)))return!1;if(!e)return!0;const s=t.split(x);if(r&&s.includes("ordinal")){const t=s[s.length-1];return w.has(`ordinal_${t}`)}if(e){const t=s[s.length-1];return w.has(t)}return!0});let v=g?{}:JSON.parse(JSON.stringify(e));const b=n(e,d??".");for(const t of b)if(l.some(e=>e.test(t))){const r=o(e,t,d??".");a(v,t,r,d??".")}if(g){const t=n(e,d??".");for(const r of t){const t=r.split(x);if("zero"===t[t.length-1]){const s=t.slice(0,-1).join(x);if(S.some(({key:t})=>t.split(x).slice(0,-1).join(x)===s)){const t=o(e,r,d??".");a(v,r,t,d??".")}}}}for(const{key:t,defaultValue:n,explicitDefault:l}of S){const u=o(e,t,d??"."),y=!S.some(e=>e.key.startsWith(`${t}${d}`)&&e.key!==t),g="object"==typeof u&&null!==u&&(f.has(t)||!n||n===t),w="object"==typeof u&&null!==u&&y&&!f.has(t)&&!g;if(g){a(v,t,u,d??".");continue}let b;if(void 0===u||w)if(s===m)if(p){const e=n&&(n===t||t!==n&&(t.startsWith(n+x)||t.startsWith(n+O)));b=n&&!e?n:c(h,t,i||r?.extract?.defaultNS||"translation",s,n)}else b=n||t;else b=c(h,t,i||r?.extract?.defaultNS||"translation",s,n);else if(s===m&&p){const e=n&&(n===t||t!==n&&(t.startsWith(n+x)||t.startsWith(n+O)));b=(t.includes(x)||t.includes(O))&&!l?u:n&&!e?n:u}else b=u;a(v,t,b,d??".")}if(!0===y)return u(v,r);if("function"==typeof y){const t={},e=Object.keys(v),s=new Map;for(const t of S){const e=!1===d?t.key:t.key.split(d)[0];s.has(e)||s.set(e,t)}e.sort((t,e)=>{if("function"==typeof y){const r=s.get(t),n=s.get(e);if(r&&n)return y(r,n)}return t.localeCompare(e,void 0,{sensitivity:"base"})});for(const s of e)t[s]=u(v[s],r);v=t}return v}async function d(n,o,a,{syncPrimaryWithDefaults:c=!1}={}){a.extract.primaryLanguage||=a.locales[0]||"en",a.extract.secondaryLanguages||=a.locales.filter(t=>t!==a?.extract?.primaryLanguage);const u=[...a.extract.preservePatterns||[]],d=a.extract.indentation??2;for(const t of o)u.push(`${t}.*`);const y=u.map(f),g="__no_namespace__",m=new Map;for(const t of n.values()){const e=t.nsIsImplicit&&!1===a.extract.defaultNS?g:String(t.ns??a.extract.defaultNS??"translation");m.has(e)||m.set(e,[]),m.get(e).push(t)}const h=[],x=Array.isArray(a.extract.ignore)?a.extract.ignore:a.extract.ignore?[a.extract.ignore]:[];for(const n of a.locales){if(a.extract.mergeNamespaces
|
|
1
|
+
import{resolve as t,basename as e,extname as r}from"node:path";import{glob as s}from"glob";import{getNestedKeys as n,getNestedValue as o,setNestedValue as a}from"../../utils/nested-object.js";import{getOutputPath as i,loadTranslationFile as l}from"../../utils/file-utils.js";import{resolveDefaultValue as c}from"../../utils/default-value.js";function f(t){const e=`^${t.replace(/[.+?^${}()|[\]\\]/g,"\\$&").replace(/\*/g,".*")}$`;return new RegExp(e)}function u(t,e){if("object"!=typeof t||null===t||Array.isArray(t))return t;const r={},s=e?.extract?.pluralSeparator??"_",n=["zero","one","two","few","many","other"],o=n.map(t=>`ordinal${s}${t}`),a=Object.keys(t).sort((t,e)=>{const r=t=>{for(const e of o)if(t.endsWith(`${s}${e}`)){return{base:t.slice(0,-(s.length+e.length)),form:e,isOrdinal:!0,isPlural:!0,fullKey:t}}for(const e of n)if(t.endsWith(`${s}${e}`)){return{base:t.slice(0,-(s.length+e.length)),form:e,isOrdinal:!1,isPlural:!0,fullKey:t}}return{base:t,form:"",isOrdinal:!1,isPlural:!1,fullKey:t}},a=r(t),i=r(e);if(a.isPlural&&i.isPlural){const t=a.base.localeCompare(i.base,void 0,{sensitivity:"base"});if(0!==t)return t;if(a.isOrdinal!==i.isOrdinal)return a.isOrdinal?1:-1;const e=a.isOrdinal?o:n,r=e.indexOf(a.form),s=e.indexOf(i.form);return-1!==r&&-1!==s?r-s:a.form.localeCompare(i.form)}const l=t.localeCompare(e,void 0,{sensitivity:"base"});return 0===l?t.localeCompare(e,void 0,{sensitivity:"case"}):l});for(const s of a)r[s]=u(t[s],e);return r}function p(t,e,r,s,i,l=[],f=new Set,p=!1){const{keySeparator:d=".",sort:y=!0,removeUnusedKeys:g=!0,primaryLanguage:m,defaultValue:h="",pluralSeparator:x="_",contextSeparator:O="_"}=r.extract,w=new Set;try{const t=new Intl.PluralRules(s,{type:"cardinal"}),e=new Intl.PluralRules(s,{type:"ordinal"});t.resolvedOptions().pluralCategories.forEach(t=>w.add(t)),e.resolvedOptions().pluralCategories.forEach(t=>w.add(`ordinal_${t}`))}catch(t){const e=new Intl.PluralRules(m||"en",{type:"cardinal"}),r=new Intl.PluralRules(m||"en",{type:"ordinal"});e.resolvedOptions().pluralCategories.forEach(t=>w.add(t)),r.resolvedOptions().pluralCategories.forEach(t=>w.add(`ordinal_${t}`))}const S=t.filter(({key:t,hasCount:e,isOrdinal:r})=>{if(l.some(e=>e.test(t)))return!1;if(!e)return!0;const s=t.split(x);if(r&&s.includes("ordinal")){const t=s[s.length-1];return w.has(`ordinal_${t}`)}if(e){const t=s[s.length-1];return w.has(t)}return!0});let v=g?{}:JSON.parse(JSON.stringify(e));const b=n(e,d??".");for(const t of b)if(l.some(e=>e.test(t))){const r=o(e,t,d??".");a(v,t,r,d??".")}if(g){const t=n(e,d??".");for(const r of t){const t=r.split(x);if("zero"===t[t.length-1]){const s=t.slice(0,-1).join(x);if(S.some(({key:t})=>t.split(x).slice(0,-1).join(x)===s)){const t=o(e,r,d??".");a(v,r,t,d??".")}}}}for(const{key:t,defaultValue:n,explicitDefault:l}of S){const u=o(e,t,d??"."),y=!S.some(e=>e.key.startsWith(`${t}${d}`)&&e.key!==t),g="object"==typeof u&&null!==u&&(f.has(t)||!n||n===t),w="object"==typeof u&&null!==u&&y&&!f.has(t)&&!g;if(g){a(v,t,u,d??".");continue}let b;if(void 0===u||w)if(s===m)if(p){const e=n&&(n===t||t!==n&&(t.startsWith(n+x)||t.startsWith(n+O)));b=n&&!e?n:c(h,t,i||r?.extract?.defaultNS||"translation",s,n)}else b=n||t;else b=c(h,t,i||r?.extract?.defaultNS||"translation",s,n);else if(s===m&&p){const e=n&&(n===t||t!==n&&(t.startsWith(n+x)||t.startsWith(n+O)));b=(t.includes(x)||t.includes(O))&&!l?u:n&&!e?n:u}else b=u;a(v,t,b,d??".")}if(!0===y)return u(v,r);if("function"==typeof y){const t={},e=Object.keys(v),s=new Map;for(const t of S){const e=!1===d?t.key:t.key.split(d)[0];s.has(e)||s.set(e,t)}e.sort((t,e)=>{if("function"==typeof y){const r=s.get(t),n=s.get(e);if(r&&n)return y(r,n)}return t.localeCompare(e,void 0,{sensitivity:"base"})});for(const s of e)t[s]=u(v[s],r);v=t}return v}async function d(n,o,a,{syncPrimaryWithDefaults:c=!1}={}){a.extract.primaryLanguage||=a.locales[0]||"en",a.extract.secondaryLanguages||=a.locales.filter(t=>t!==a?.extract?.primaryLanguage);const u=[...a.extract.preservePatterns||[]],d=a.extract.indentation??2;for(const t of o)u.push(`${t}.*`);const y=u.map(f),g="__no_namespace__",m=new Map;for(const t of n.values()){const e=t.nsIsImplicit&&!1===a.extract.defaultNS?g:String(t.ns??a.extract.defaultNS??"translation");m.has(e)||m.set(e,[]),m.get(e).push(t)}const h=[],x=Array.isArray(a.extract.ignore)?a.extract.ignore:a.extract.ignore?[a.extract.ignore]:[];for(const n of a.locales){if(a.extract.mergeNamespaces||"string"==typeof a.extract.output&&!a.extract.output.includes("{{namespace}}")){const e={},r=i(a.extract.output,n),s=t(process.cwd(),r),f=await l(s)||{},u=new Set([...m.keys(),...Object.keys(f)]);for(const t of u){const r=m.get(t)||[];if(t===g){const t=p(r,f,a,n,void 0,y,o,c);Object.assign(e,t)}else{const s=f[t]||{};e[t]=p(r,s,a,n,t,y,o,c)}}const x=JSON.stringify(f,null,d),O=JSON.stringify(e,null,d);h.push({path:s,updated:O!==x,newTranslations:e,existingTranslations:f})}else{const f=new Set(m.keys()),u=i(a.extract.output,n,"*"),g=await s(u,{ignore:x});for(const t of g)f.add(e(t,r(t)));for(const e of f){const r=m.get(e)||[],s=i(a.extract.output,n,e),f=t(process.cwd(),s),u=await l(f)||{},g=p(r,u,a,n,e,y,o,c),x=JSON.stringify(u,null,d),O=JSON.stringify(g,null,d);h.push({path:f,updated:O!==x,newTranslations:g,existingTranslations:u})}}}return h}export{d as getTranslations};
|
|
@@ -1 +1 @@
|
|
|
1
|
-
import{getObjectPropValue as e}from"./ast-utils.js";class t{scopeStack=[];config;scope=new Map;constructor(e){this.config=e}reset(){this.scopeStack=[],this.scope=new Map}enterScope(){this.scopeStack.push(new Map)}exitScope(){this.scopeStack.pop()}setVarInScope(e,t){this.scopeStack.length>0
|
|
1
|
+
import{getObjectPropValue as e}from"./ast-utils.js";class t{scopeStack=[];config;scope=new Map;simpleConstants=new Map;constructor(e){this.config=e}reset(){this.scopeStack=[],this.scope=new Map,this.simpleConstants.clear()}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):this.scope.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)}const t=this.scope.get(e);if(t)return t}getUseTranslationConfig(e){const t=this.config.extract.useTranslationNames||["useTranslation"];for(const i of t){if("string"==typeof i&&i===e)return{name:e,nsArg:0,keyPrefixArg:1};if("object"==typeof i&&i.name===e)return{name:i.name,nsArg:i.nsArg??0,keyPrefixArg:i.keyPrefixArg??1}}}resolveSimpleStringIdentifier(e){return this.simpleConstants.get(e)}handleVariableDeclarator(e){const t=e.init;if(!t)return;"Identifier"===e.id.type&&"StringLiteral"===t.type&&this.simpleConstants.set(e.id.value,t.value);const i="AwaitExpression"===t.type&&"CallExpression"===t.argument.type?t.argument:"CallExpression"===t.type?t:null;if(!i)return;const r=i.callee;if("Identifier"===r.type){const t=this.getUseTranslationConfig(r.value);if(t)return this.handleUseTranslationDeclarator(e,i,t),void this.handleUseTranslationForComments(e,i,t)}if("Identifier"===r.type){if(this.getVarFromScope(r.value))return void this.handleGetFixedTFromVariableDeclarator(e,i,r.value)}"MemberExpression"===r.type&&"Identifier"===r.property.type&&"getFixedT"===r.property.value&&this.handleGetFixedTDeclarator(e,i)}handleUseTranslationForComments(e,t,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||"getFixedT"===t.key.value)){r=t.key.value;break}if("KeyValuePatternProperty"===t.type&&"Identifier"===t.key.type&&("t"===t.key.value||"getFixedT"===t.key.value)&&"Identifier"===t.value.type){r=t.value.value;break}}if(!r)return;const s=-1===i.nsArg?void 0:t.arguments?.[i.nsArg??0]?.expression,n=t.arguments?.[i.keyPrefixArg??1]?.expression;let a,o;if("StringLiteral"===s?.type?a=s.value:"ArrayExpression"===s?.type&&"StringLiteral"===s.elements[0]?.expression.type&&(a=s.elements[0].expression.value),"ObjectExpression"===n?.type){const e=n.properties.find(e=>"KeyValueProperty"===e.type&&"Identifier"===e.key.type&&"keyPrefix"===e.key.value);"KeyValueProperty"===e?.type&&"StringLiteral"===e.value.type&&(o=e.value.value)}else if("StringLiteral"===n?.type)o=n.value;else if("Identifier"===n?.type)o=this.resolveSimpleStringIdentifier(n.value);else if("TemplateLiteral"===n?.type){const e=n;0===(e.expressions||[]).length&&(o=e.quasis?.[0]?.cooked??void 0)}(a||o)&&this.scope.set(r,{defaultNs:a,keyPrefix:o})}handleUseTranslationDeclarator(t,i,r){let s;if("Identifier"===t.id.type&&(s=t.id.value),"ArrayPattern"===t.id.type){const e=t.id.elements[0];"Identifier"===e?.type&&(s=e.value)}if("ObjectPattern"===t.id.type)for(const e of t.id.properties){if("AssignmentPatternProperty"===e.type&&"Identifier"===e.key.type&&("t"===e.key.value||"getFixedT"===e.key.value)){s=e.key.value;break}if("KeyValuePatternProperty"===e.type&&"Identifier"===e.key.type&&("t"===e.key.value||"getFixedT"===e.key.value)&&"Identifier"===e.value.type){s=e.value.value;break}}if(!s)return;const n=-1===r.nsArg?void 0:i.arguments?.[r.nsArg??0]?.expression;let a;"StringLiteral"===n?.type?a=n.value:"ArrayExpression"===n?.type&&"StringLiteral"===n.elements[0]?.expression.type&&(a=n.elements[0].expression.value);const o=i.arguments?.[r.keyPrefixArg??1]?.expression;let l;if("ObjectExpression"===o?.type){const t=e(o,"keyPrefix");l="string"==typeof t?t:void 0}else if("StringLiteral"===o?.type)l=o.value;else if("Identifier"===o?.type)l=this.resolveSimpleStringIdentifier(o.value);else if("TemplateLiteral"===o?.type){const e=o;0===(e.expressions||[]).length&&(l=e.quasis?.[0]?.cooked??void 0)}this.setVarInScope(s,{defaultNs:a,keyPrefix:l})}handleGetFixedTDeclarator(e,t){if("Identifier"!==e.id.type||!e.init||"CallExpression"!==e.init.type)return;const i=e.id.value,r=t.arguments,s=r[1]?.expression,n=r[2]?.expression,a="StringLiteral"===s?.type?s.value:void 0,o="StringLiteral"===n?.type?n.value:void 0;(a||o)&&this.setVarInScope(i,{defaultNs:a,keyPrefix:o})}handleGetFixedTFromVariableDeclarator(e,t,i){if("Identifier"!==e.id.type)return;const r=e.id.value,s=this.getVarFromScope(i);if(!s)return;const n=t.arguments,a=n[1]?.expression,o=n[2]?.expression,l="StringLiteral"===a?.type?a.value:void 0,p="StringLiteral"===o?.type?o.value:void 0,y=l??s.defaultNs,f=p??s.keyPrefix;(y||f)&&this.setVarInScope(r,{defaultNs:y,keyPrefix:f})}}export{t as ScopeManager};
|
package/dist/esm/init.js
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
import t from"inquirer";import{writeFile as e,readFile as n}from"node:fs/promises";import{resolve as o}from"node:path";import{detectConfig as i}from"./heuristic-config.js";async function r(){console.log("Welcome to the i18next-cli setup wizard!"),console.log("Scanning your project for a recommended configuration...");const r=await i();r?console.log("✅ Found a potential project structure. Using it for suggestions."):console.log("Could not detect a project structure. Using standard defaults."),"string"==typeof r?.extract?.input&&(r.extract.input=[r?.extract?.input]);const
|
|
1
|
+
import t from"inquirer";import{writeFile as e,readFile as n}from"node:fs/promises";import{resolve as o}from"node:path";import{detectConfig as i}from"./heuristic-config.js";async function r(){console.log("Welcome to the i18next-cli setup wizard!"),console.log("Scanning your project for a recommended configuration...");const r=await i();r?console.log("✅ Found a potential project structure. Using it for suggestions."):console.log("Could not detect a project structure. Using standard defaults."),"string"==typeof r?.extract?.input&&(r.extract.input=[r?.extract?.input]),r&&"function"==typeof r.extract?.output&&delete r.extract.output;const a=await t.prompt([{type:"list",name:"fileType",message:"What kind of configuration file do you want?",choices:["TypeScript (i18next.config.ts)","JavaScript (i18next.config.js)"]},{type:"input",name:"locales",message:"What locales does your project support? (comma-separated)",default:r?.locales?.join(",")||"en,de,fr",filter:t=>t.split(",").map(t=>t.trim())},{type:"input",name:"input",message:"What is the glob pattern for your source files?",default:r?.extract?.input?(r.extract.input||[])[0]:"src/**/*.{js,jsx,ts,tsx}"},{type:"input",name:"output",message:"What is the path for your output resource files?",default:"string"==typeof r?.extract?.output?r.extract.output:"public/locales/{{language}}/{{namespace}}.json"}]),s=a.fileType.includes("TypeScript"),c=await async function(){try{const t=o(process.cwd(),"package.json"),e=await n(t,"utf-8");return"module"===JSON.parse(e).type}catch{return!0}}(),p=s?"i18next.config.ts":"i18next.config.js",u={locales:a.locales,extract:{input:a.input,output:a.output}};function f(t,e=2,n=0){const o=t=>" ".repeat(t*e),i=o(n),r=o(n+1);if(null===t||"number"==typeof t||"boolean"==typeof t)return JSON.stringify(t);if("string"==typeof t)return JSON.stringify(t);if(Array.isArray(t)){if(0===t.length)return"[]";return`[\n${t.map(t=>`${r}${f(t,e,n+1)}`).join(",\n")}\n${i}]`}if("object"==typeof t){const o=Object.keys(t);if(0===o.length)return"{}";return`{\n${o.map(o=>{const i=/^[A-Za-z_$][A-Za-z0-9_$]*$/.test(o)?o:JSON.stringify(o);return`${r}${i}: ${f(t[o],e,n+1)}`}).join(",\n")}\n${i}}`}return JSON.stringify(t)}let l="";l=s?`import { defineConfig } from 'i18next-cli';\n\nexport default defineConfig(${f(u)});`:c?`import { defineConfig } from 'i18next-cli';\n\n/** @type {import('i18next-cli').I18nextToolkitConfig} */\nexport default defineConfig(${f(u)});`:`const { defineConfig } = require('i18next-cli');\n\n/** @type {import('i18next-cli').I18nextToolkitConfig} */\nmodule.exports = defineConfig(${f(u)});`;const g=o(process.cwd(),p);await e(g,l.trim()),console.log(`✅ Configuration file created at: ${g}`)}export{r as runInit};
|
package/dist/esm/locize.js
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
import{execa as e}from"execa";import o from"chalk";import
|
|
1
|
+
import{execa as e}from"execa";import o from"chalk";import t from"ora";import n from"inquirer";import{sep as r,resolve as i}from"node:path";function s(e,o,t){const{locize:n={},extract:s}=o,{projectId:c,apiKey:a,version:l}=n,u=[e];if(c&&u.push("--project-id",c),a&&u.push("--api-key",a),l&&u.push("--ver",l),"sync"===e){(t.updateValues??n.updateValues)&&u.push("--update-values","true");(t.srcLngOnly??n.sourceLanguageOnly)&&u.push("--reference-language-only","true");(t.compareMtime??n.compareModificationTime)&&u.push("--compare-modification-time","true");(t.dryRun??n.dryRun)&&u.push("--dry","true")}let p;try{if("string"==typeof s.output){const e=s.output.replace(/\\/g,"/"),o=(e.includes("/{{language}}/")?e.split("/{{language}}/")[0]:e.replace("{{language}}","")).split("/").join(r);p=i(process.cwd(),o)}else if("function"==typeof s.output)try{const e=s.output(o.extract.primaryLanguage||"en"),t=String(e).replace(/\\/g,"/"),n=t.includes("/"+(o.extract.primaryLanguage||"en")+"/")?t.split("/"+(o.extract.primaryLanguage||"en")+"/")[0]:t.replace(o.extract.primaryLanguage||"en","");p=i(process.cwd(),n.split("/").join(r))}catch{p=i(process.cwd(),".")}else p=i(process.cwd(),".")}catch{p=i(process.cwd(),".")}return u.push("--path",p),u}async function c(r,i,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=t(`Running 'locize ${r}'...\n`).start();let l=i;try{const t=s(r,l,c);console.log(o.cyan(`\nRunning 'locize ${t.join(" ")}'...`));const n=await e("locize",t,{stdio:"pipe"});a.succeed(o.green(`'locize ${r}' completed successfully.`)),n?.stdout&&console.log(n.stdout)}catch(t){const i=t.stderr||"";if(i.includes("missing required argument")){const t=await async function(){console.log(o.yellow("\nLocize configuration is missing or invalid. Let's set it up!"));const e=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(!e.projectId)return void console.error(o.red("Project ID is required to continue."));const{save:t}=await n.prompt([{type:"confirm",name:"save",message:"Would you like to see how to save these credentials for future use?",default:!0}]);if(t){const t=`\n# Add this to your .env file (and ensure .env is in your .gitignore!)\nLOCIZE_API_KEY=${e.apiKey}\n`,n=`\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(t)),console.log(o.bold("Then, in your i18next.config.ts:")),console.log(o.green(n))}return{projectId:e.projectId,apiKey:e.apiKey,version:e.version}}();if(t){l={...l,locize:t},a.start("Retrying with new credentials...");try{const t=s(r,l,c);console.log(o.cyan(`\nRunning 'locize ${t.join(" ")}'...`));const n=await e("locize",t,{stdio:"pipe"});a.succeed(o.green("Retry successful!")),n?.stdout&&console.log(n.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 ${r}'.`)),console.error(i||t.message),process.exit(1)}console.log(o.green(`\n✅ 'locize ${r}' completed successfully.`))}const a=(e,o)=>c("sync",e,o),l=(e,o)=>c("download",e,o),u=(e,o)=>c("migrate",e,o);export{l as runLocizeDownload,u as runLocizeMigrate,a as runLocizeSync};
|
|
@@ -1 +1 @@
|
|
|
1
|
-
import{access as t,readFile as
|
|
1
|
+
import{access as t,readFile as n}from"node:fs/promises";import{normalize as r,resolve as e,extname as a}from"node:path";import{createJiti as o}from"jiti";import{getTsConfigAliases as s}from"../config.js";function c(t,n,e){if(!t)return r(`locales/${n}/${e??"translation"}.json`);if("function"==typeof t)try{const a=String(t(n,e));return r(a.replace(/\/\/+/g,"/"))}catch{return r(`locales/${n}/${e??"translation"}.json`)}let a=String(t);return a=a.replace(/\{\{language\}\}|\{\{lng\}\}/g,n),a=null!=e?a.replace(/\{\{namespace\}\}/g,e):a.replace(/\/?\{\{namespace\}\}/g,""),a=a.replace(/\/\/+/g,"/"),r(a)}async function l(r){const c=e(process.cwd(),r);try{await t(c)}catch{return null}try{const t=a(c).toLowerCase();if(".json"===t){const t=await n(c,"utf-8");return JSON.parse(t)}if(".ts"===t||".js"===t){const t=await s(),n=o(process.cwd(),{alias:t,interopDefault:!0});return await n.import(c,{default:!0})}return null}catch(t){return console.warn(`Could not parse translation file ${r}:`,t),null}}function i(t,n="json",r=2){const e=JSON.stringify(t,null,r);switch(n){case"js":case"js-esm":return`export default ${e};\n`;case"js-cjs":return`module.exports = ${e};\n`;case"ts":return`export default ${e} as const;\n`;default:return`${e}\n`}}export{c as getOutputPath,l as loadTranslationFile,i as serializeTranslationFile};
|
|
@@ -1 +1 @@
|
|
|
1
|
-
function t(t){if(!t.extract.input?.length)throw new e("extract.input must be specified and non-empty");if(!t.extract.output)throw new e("extract.output must be specified");if(!t.locales?.length)throw new e("locales must be specified and non-empty");if(
|
|
1
|
+
function t(t){if(!t.extract.input?.length)throw new e("extract.input must be specified and non-empty");if(!t.extract.output)throw new e("extract.output must be specified");if(!t.locales?.length)throw new e("locales must be specified and non-empty");if("string"==typeof t.extract.output&&!t.extract.output.includes("{{language}}")&&!t.extract.output.includes("{{lng}}"))throw new e("extract.output must contain {{language}} placeholder")}class e extends Error{file;cause;constructor(t,e,n){super(e?`${t} in file ${e}`:t),this.file=e,this.cause=n,this.name="ExtractorError"}}export{e as ExtractorError,t as validateExtractorConfig};
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "i18next-cli",
|
|
3
|
-
"version": "1.
|
|
3
|
+
"version": "1.17.0",
|
|
4
4
|
"description": "A unified, high-performance i18next CLI.",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"bin": {
|
|
@@ -55,31 +55,31 @@
|
|
|
55
55
|
"devDependencies": {
|
|
56
56
|
"@rollup/plugin-terser": "0.4.4",
|
|
57
57
|
"@types/inquirer": "9.0.9",
|
|
58
|
-
"@types/node": "24.
|
|
59
|
-
"@types/react": "19.
|
|
60
|
-
"@vitest/coverage-v8": "
|
|
61
|
-
"eslint": "9.
|
|
58
|
+
"@types/node": "24.9.2",
|
|
59
|
+
"@types/react": "19.2.2",
|
|
60
|
+
"@vitest/coverage-v8": "4.0.4",
|
|
61
|
+
"eslint": "9.38.0",
|
|
62
62
|
"eslint-plugin-import": "2.32.0",
|
|
63
|
-
"memfs": "4.
|
|
63
|
+
"memfs": "4.50.0",
|
|
64
64
|
"neostandard": "0.12.2",
|
|
65
65
|
"rollup-plugin-typescript2": "0.36.0",
|
|
66
66
|
"ts-node": "10.9.2",
|
|
67
67
|
"typescript": "5.9.3",
|
|
68
|
-
"unplugin-swc": "1.5.
|
|
69
|
-
"vitest": "
|
|
68
|
+
"unplugin-swc": "1.5.8",
|
|
69
|
+
"vitest": "4.0.4"
|
|
70
70
|
},
|
|
71
71
|
"dependencies": {
|
|
72
72
|
"@swc/core": "1.13.19",
|
|
73
73
|
"chalk": "5.6.2",
|
|
74
74
|
"chokidar": "4.0.3",
|
|
75
|
-
"commander": "14.0.
|
|
75
|
+
"commander": "14.0.2",
|
|
76
76
|
"execa": "9.6.0",
|
|
77
77
|
"glob": "11.0.3",
|
|
78
78
|
"i18next-resources-for-ts": "1.7.4",
|
|
79
|
-
"inquirer": "12.
|
|
79
|
+
"inquirer": "12.10.0",
|
|
80
80
|
"jiti": "2.6.1",
|
|
81
81
|
"jsonc-parser": "3.3.1",
|
|
82
|
-
"minimatch": "10.
|
|
82
|
+
"minimatch": "10.1.1",
|
|
83
83
|
"ora": "9.0.0",
|
|
84
84
|
"swc-walk": "1.0.0"
|
|
85
85
|
}
|
package/src/cli.ts
CHANGED
|
@@ -22,7 +22,7 @@ const program = new Command()
|
|
|
22
22
|
program
|
|
23
23
|
.name('i18next-cli')
|
|
24
24
|
.description('A unified, high-performance i18next CLI.')
|
|
25
|
-
.version('1.
|
|
25
|
+
.version('1.17.0')
|
|
26
26
|
|
|
27
27
|
// new: global config override option
|
|
28
28
|
program.option('-c, --config <path>', 'Path to i18next-cli config file (overrides detection)')
|
|
@@ -238,7 +238,7 @@ program
|
|
|
238
238
|
program.parse(process.argv)
|
|
239
239
|
|
|
240
240
|
const toArray = (v: any) => Array.isArray(v) ? v : (v ? [v] : [])
|
|
241
|
-
const deriveOutputIgnore = (output?: string) => {
|
|
241
|
+
const deriveOutputIgnore = (output?: string | ((language: string, namespace?: string) => string)) => {
|
|
242
242
|
if (!output || typeof output !== 'string') return []
|
|
243
243
|
return [output.replace(/\{\{[^}]+\}\}/g, '*')]
|
|
244
244
|
}
|
|
@@ -396,7 +396,10 @@ export async function getTranslations (
|
|
|
396
396
|
|
|
397
397
|
// Process each locale one by one
|
|
398
398
|
for (const locale of config.locales) {
|
|
399
|
-
|
|
399
|
+
// If output is a string we can detect the presence of the namespace placeholder.
|
|
400
|
+
// If it's a function we cannot reliably detect that here — default to not merged
|
|
401
|
+
// unless mergeNamespaces is explicitly true.
|
|
402
|
+
const shouldMerge = config.extract.mergeNamespaces || (typeof config.extract.output === 'string' ? !config.extract.output.includes('{{namespace}}') : false)
|
|
400
403
|
|
|
401
404
|
// LOGIC PATH 1: Merged Namespaces
|
|
402
405
|
if (shouldMerge) {
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import type { VariableDeclarator, CallExpression } from '@swc/core'
|
|
1
|
+
import type { VariableDeclarator, CallExpression, TemplateLiteral } from '@swc/core'
|
|
2
2
|
import type { ScopeInfo, UseTranslationHookConfig, I18nextToolkitConfig } from '../../types'
|
|
3
3
|
import { getObjectPropValue } from './ast-utils'
|
|
4
4
|
|
|
@@ -7,6 +7,9 @@ export class ScopeManager {
|
|
|
7
7
|
private config: Omit<I18nextToolkitConfig, 'plugins'>
|
|
8
8
|
private scope: Map<string, { defaultNs?: string; keyPrefix?: string }> = new Map()
|
|
9
9
|
|
|
10
|
+
// Track simple local constants with string literal values to resolve identifier args
|
|
11
|
+
private simpleConstants: Map<string, string> = new Map()
|
|
12
|
+
|
|
10
13
|
constructor (config: Omit<I18nextToolkitConfig, 'plugins'>) {
|
|
11
14
|
this.config = config
|
|
12
15
|
}
|
|
@@ -21,6 +24,7 @@ export class ScopeManager {
|
|
|
21
24
|
public reset (): void {
|
|
22
25
|
this.scopeStack = []
|
|
23
26
|
this.scope = new Map()
|
|
27
|
+
this.simpleConstants.clear()
|
|
24
28
|
}
|
|
25
29
|
|
|
26
30
|
/**
|
|
@@ -49,6 +53,10 @@ export class ScopeManager {
|
|
|
49
53
|
setVarInScope (name: string, info: ScopeInfo): void {
|
|
50
54
|
if (this.scopeStack.length > 0) {
|
|
51
55
|
this.scopeStack[this.scopeStack.length - 1].set(name, info)
|
|
56
|
+
} else {
|
|
57
|
+
// No active scope (top-level). Preserve in legacy scope map so lookups work
|
|
58
|
+
// for top-level variables (e.g., const { getFixedT } = useTranslate(...))
|
|
59
|
+
this.scope.set(name, info)
|
|
52
60
|
}
|
|
53
61
|
}
|
|
54
62
|
|
|
@@ -77,6 +85,33 @@ export class ScopeManager {
|
|
|
77
85
|
return undefined
|
|
78
86
|
}
|
|
79
87
|
|
|
88
|
+
private getUseTranslationConfig (name: string): UseTranslationHookConfig | undefined {
|
|
89
|
+
const useTranslationNames = this.config.extract.useTranslationNames || ['useTranslation']
|
|
90
|
+
|
|
91
|
+
for (const item of useTranslationNames) {
|
|
92
|
+
if (typeof item === 'string' && item === name) {
|
|
93
|
+
// Default behavior for simple string entries
|
|
94
|
+
return { name, nsArg: 0, keyPrefixArg: 1 }
|
|
95
|
+
}
|
|
96
|
+
if (typeof item === 'object' && item.name === name) {
|
|
97
|
+
// Custom configuration with specified or default argument positions
|
|
98
|
+
return {
|
|
99
|
+
name: item.name,
|
|
100
|
+
nsArg: item.nsArg ?? 0,
|
|
101
|
+
keyPrefixArg: item.keyPrefixArg ?? 1,
|
|
102
|
+
}
|
|
103
|
+
}
|
|
104
|
+
}
|
|
105
|
+
return undefined
|
|
106
|
+
}
|
|
107
|
+
|
|
108
|
+
/**
|
|
109
|
+
* Resolve simple identifier declared in-file to its string literal value, if known.
|
|
110
|
+
*/
|
|
111
|
+
private resolveSimpleStringIdentifier (name: string): string | undefined {
|
|
112
|
+
return this.simpleConstants.get(name)
|
|
113
|
+
}
|
|
114
|
+
|
|
80
115
|
/**
|
|
81
116
|
* Handles variable declarations that might define translation functions.
|
|
82
117
|
*
|
|
@@ -92,6 +127,12 @@ export class ScopeManager {
|
|
|
92
127
|
const init = node.init
|
|
93
128
|
if (!init) return
|
|
94
129
|
|
|
130
|
+
// Record simple const/let string initializers for later resolution
|
|
131
|
+
if (node.id.type === 'Identifier' && init.type === 'StringLiteral') {
|
|
132
|
+
this.simpleConstants.set(node.id.value, init.value)
|
|
133
|
+
// continue processing; still may be a useTranslation/getFixedT call below
|
|
134
|
+
}
|
|
135
|
+
|
|
95
136
|
// Determine the actual call expression, looking inside AwaitExpressions.
|
|
96
137
|
const callExpr =
|
|
97
138
|
init.type === 'AwaitExpression' && init.argument.type === 'CallExpression'
|
|
@@ -116,6 +157,18 @@ export class ScopeManager {
|
|
|
116
157
|
}
|
|
117
158
|
}
|
|
118
159
|
|
|
160
|
+
// Handle: const t = getFixedT(...) where getFixedT is a previously declared variable
|
|
161
|
+
// (e.g., `const { getFixedT } = useTranslate('helloservice')`)
|
|
162
|
+
if (callee.type === 'Identifier') {
|
|
163
|
+
const sourceScope = this.getVarFromScope(callee.value)
|
|
164
|
+
if (sourceScope) {
|
|
165
|
+
// Propagate the source scope (keyPrefix/defaultNs) and augment it with
|
|
166
|
+
// arguments passed to this call (e.g., namespace argument).
|
|
167
|
+
this.handleGetFixedTFromVariableDeclarator(node, callExpr, callee.value)
|
|
168
|
+
return
|
|
169
|
+
}
|
|
170
|
+
}
|
|
171
|
+
|
|
119
172
|
// Handle: const t = i18next.getFixedT(...)
|
|
120
173
|
if (
|
|
121
174
|
callee.type === 'MemberExpression' &&
|
|
@@ -135,40 +188,6 @@ export class ScopeManager {
|
|
|
135
188
|
* @param callExpr - The CallExpression node representing the useTranslation invocation
|
|
136
189
|
* @param hookConfig - Configuration describing argument positions for namespace and keyPrefix
|
|
137
190
|
*/
|
|
138
|
-
private getUseTranslationConfig (name: string): UseTranslationHookConfig | undefined {
|
|
139
|
-
const useTranslationNames = this.config.extract.useTranslationNames || ['useTranslation']
|
|
140
|
-
|
|
141
|
-
for (const item of useTranslationNames) {
|
|
142
|
-
if (typeof item === 'string' && item === name) {
|
|
143
|
-
// Default behavior for simple string entries
|
|
144
|
-
return { name, nsArg: 0, keyPrefixArg: 1 }
|
|
145
|
-
}
|
|
146
|
-
if (typeof item === 'object' && item.name === name) {
|
|
147
|
-
// Custom configuration with specified or default argument positions
|
|
148
|
-
return {
|
|
149
|
-
name: item.name,
|
|
150
|
-
nsArg: item.nsArg ?? 0,
|
|
151
|
-
keyPrefixArg: item.keyPrefixArg ?? 1,
|
|
152
|
-
}
|
|
153
|
-
}
|
|
154
|
-
}
|
|
155
|
-
return undefined
|
|
156
|
-
}
|
|
157
|
-
|
|
158
|
-
/**
|
|
159
|
-
* Processes useTranslation hook declarations to extract scope information.
|
|
160
|
-
*
|
|
161
|
-
* Handles various destructuring patterns:
|
|
162
|
-
* - `const [t] = useTranslation('ns')` - Array destructuring
|
|
163
|
-
* - `const { t } = useTranslation('ns')` - Object destructuring
|
|
164
|
-
* - `const { t: myT } = useTranslation('ns')` - Aliased destructuring
|
|
165
|
-
*
|
|
166
|
-
* Extracts namespace from the first argument and keyPrefix from options.
|
|
167
|
-
*
|
|
168
|
-
* @param node - Variable declarator with useTranslation call
|
|
169
|
-
* @param callExpr - The CallExpression node representing the useTranslation invocation
|
|
170
|
-
* @param hookConfig - Configuration describing argument positions for namespace and keyPrefix
|
|
171
|
-
*/
|
|
172
191
|
private handleUseTranslationForComments (node: VariableDeclarator, callExpr: CallExpression, hookConfig: UseTranslationHookConfig): void {
|
|
173
192
|
let variableName: string | undefined
|
|
174
193
|
|
|
@@ -188,13 +207,12 @@ export class ScopeManager {
|
|
|
188
207
|
// Handle object destructuring: const { t } or { t: t1 } = useTranslation()
|
|
189
208
|
if (node.id.type === 'ObjectPattern') {
|
|
190
209
|
for (const prop of node.id.properties) {
|
|
191
|
-
|
|
192
|
-
|
|
193
|
-
variableName =
|
|
210
|
+
// Support both 't' and 'getFixedT' (and preserve existing behavior for 't').
|
|
211
|
+
if (prop.type === 'AssignmentPatternProperty' && prop.key.type === 'Identifier' && (prop.key.value === 't' || prop.key.value === 'getFixedT')) {
|
|
212
|
+
variableName = prop.key.value
|
|
194
213
|
break
|
|
195
214
|
}
|
|
196
|
-
if (prop.type === 'KeyValuePatternProperty' && prop.key.type === 'Identifier' && prop.key.value === 't' && prop.value.type === 'Identifier') {
|
|
197
|
-
// This handles { t: myT }
|
|
215
|
+
if (prop.type === 'KeyValuePatternProperty' && prop.key.type === 'Identifier' && (prop.key.value === 't' || prop.key.value === 'getFixedT') && prop.value.type === 'Identifier') {
|
|
198
216
|
variableName = prop.value.value
|
|
199
217
|
break
|
|
200
218
|
}
|
|
@@ -205,20 +223,20 @@ export class ScopeManager {
|
|
|
205
223
|
if (!variableName) return
|
|
206
224
|
|
|
207
225
|
// Extract namespace from useTranslation arguments
|
|
208
|
-
const nsArg = callExpr.arguments?.[hookConfig.nsArg]?.expression
|
|
209
|
-
const optionsArg = callExpr.arguments?.[hookConfig.keyPrefixArg]?.expression
|
|
226
|
+
const nsArg = hookConfig.nsArg === -1 ? undefined : callExpr.arguments?.[hookConfig.nsArg ?? 0]?.expression
|
|
227
|
+
const optionsArg = callExpr.arguments?.[hookConfig.keyPrefixArg ?? 1]?.expression
|
|
210
228
|
|
|
211
229
|
let defaultNs: string | undefined
|
|
212
230
|
let keyPrefix: string | undefined
|
|
213
231
|
|
|
214
|
-
// Parse namespace argument
|
|
232
|
+
// Parse namespace argument (only when nsArg wasn't explicitly disabled)
|
|
215
233
|
if (nsArg?.type === 'StringLiteral') {
|
|
216
234
|
defaultNs = nsArg.value
|
|
217
235
|
} else if (nsArg?.type === 'ArrayExpression' && nsArg.elements[0]?.expression.type === 'StringLiteral') {
|
|
218
236
|
defaultNs = nsArg.elements[0].expression.value
|
|
219
237
|
}
|
|
220
238
|
|
|
221
|
-
// Parse keyPrefix
|
|
239
|
+
// Parse keyPrefix: accept either { keyPrefix: 'x' } or a plain string arg or simple identifier/template literal
|
|
222
240
|
if (optionsArg?.type === 'ObjectExpression') {
|
|
223
241
|
const keyPrefixProp = optionsArg.properties.find(
|
|
224
242
|
prop => prop.type === 'KeyValueProperty' &&
|
|
@@ -228,6 +246,16 @@ export class ScopeManager {
|
|
|
228
246
|
if (keyPrefixProp?.type === 'KeyValueProperty' && keyPrefixProp.value.type === 'StringLiteral') {
|
|
229
247
|
keyPrefix = keyPrefixProp.value.value
|
|
230
248
|
}
|
|
249
|
+
} else if (optionsArg?.type === 'StringLiteral') {
|
|
250
|
+
// allow keyPrefix as direct string argument
|
|
251
|
+
keyPrefix = optionsArg.value
|
|
252
|
+
} else if (optionsArg?.type === 'Identifier') {
|
|
253
|
+
keyPrefix = this.resolveSimpleStringIdentifier(optionsArg.value)
|
|
254
|
+
} else if (optionsArg?.type === 'TemplateLiteral') {
|
|
255
|
+
const tpl = optionsArg as TemplateLiteral
|
|
256
|
+
if ((tpl.expressions || []).length === 0) {
|
|
257
|
+
keyPrefix = tpl.quasis?.[0]?.cooked ?? undefined
|
|
258
|
+
}
|
|
231
259
|
}
|
|
232
260
|
|
|
233
261
|
// Store in the legacy scope map for comment parsing
|
|
@@ -269,13 +297,12 @@ export class ScopeManager {
|
|
|
269
297
|
// Handle object destructuring: const { t } or { t: t1 } = useTranslation()
|
|
270
298
|
if (node.id.type === 'ObjectPattern') {
|
|
271
299
|
for (const prop of node.id.properties) {
|
|
272
|
-
|
|
273
|
-
|
|
274
|
-
variableName =
|
|
300
|
+
// Also consider getFixedT so scope info is attached to that identifier
|
|
301
|
+
if (prop.type === 'AssignmentPatternProperty' && prop.key.type === 'Identifier' && (prop.key.value === 't' || prop.key.value === 'getFixedT')) {
|
|
302
|
+
variableName = prop.key.value
|
|
275
303
|
break
|
|
276
304
|
}
|
|
277
|
-
if (prop.type === 'KeyValuePatternProperty' && prop.key.type === 'Identifier' && prop.key.value === 't' && prop.value.type === 'Identifier') {
|
|
278
|
-
// This handles { t: myT }
|
|
305
|
+
if (prop.type === 'KeyValuePatternProperty' && prop.key.type === 'Identifier' && (prop.key.value === 't' || prop.key.value === 'getFixedT') && prop.value.type === 'Identifier') {
|
|
279
306
|
variableName = prop.value.value
|
|
280
307
|
break
|
|
281
308
|
}
|
|
@@ -286,7 +313,7 @@ export class ScopeManager {
|
|
|
286
313
|
if (!variableName) return
|
|
287
314
|
|
|
288
315
|
// Use the configured argument indices from hookConfig
|
|
289
|
-
const nsArg = callExpr.arguments?.[hookConfig.nsArg]?.expression
|
|
316
|
+
const nsArg = hookConfig.nsArg === -1 ? undefined : callExpr.arguments?.[hookConfig.nsArg ?? 0]?.expression
|
|
290
317
|
|
|
291
318
|
let defaultNs: string | undefined
|
|
292
319
|
if (nsArg?.type === 'StringLiteral') {
|
|
@@ -295,11 +322,21 @@ export class ScopeManager {
|
|
|
295
322
|
defaultNs = nsArg.elements[0].expression.value
|
|
296
323
|
}
|
|
297
324
|
|
|
298
|
-
const optionsArg = callExpr.arguments?.[hookConfig.keyPrefixArg]?.expression
|
|
325
|
+
const optionsArg = callExpr.arguments?.[hookConfig.keyPrefixArg ?? 1]?.expression
|
|
299
326
|
let keyPrefix: string | undefined
|
|
300
327
|
if (optionsArg?.type === 'ObjectExpression') {
|
|
301
328
|
const kp = getObjectPropValue(optionsArg, 'keyPrefix')
|
|
302
329
|
keyPrefix = typeof kp === 'string' ? kp : undefined
|
|
330
|
+
} else if (optionsArg?.type === 'StringLiteral') {
|
|
331
|
+
// allow keyPrefix as a direct string argument
|
|
332
|
+
keyPrefix = optionsArg.value
|
|
333
|
+
} else if (optionsArg?.type === 'Identifier') {
|
|
334
|
+
keyPrefix = this.resolveSimpleStringIdentifier(optionsArg.value)
|
|
335
|
+
} else if (optionsArg?.type === 'TemplateLiteral') {
|
|
336
|
+
const tpl = optionsArg as TemplateLiteral
|
|
337
|
+
if ((tpl.expressions || []).length === 0) {
|
|
338
|
+
keyPrefix = tpl.quasis?.[0]?.cooked ?? undefined
|
|
339
|
+
}
|
|
303
340
|
}
|
|
304
341
|
|
|
305
342
|
// Store the scope info for the declared variable
|
|
@@ -336,4 +373,38 @@ export class ScopeManager {
|
|
|
336
373
|
this.setVarInScope(variableName, { defaultNs, keyPrefix })
|
|
337
374
|
}
|
|
338
375
|
}
|
|
376
|
+
|
|
377
|
+
/**
|
|
378
|
+
* Handles cases where a getFixedT-like function is a variable (from a custom hook)
|
|
379
|
+
* and is invoked to produce a bound `t` function, e.g.:
|
|
380
|
+
* const { getFixedT } = useTranslate('prefix')
|
|
381
|
+
* const t = getFixedT('en', 'ns')
|
|
382
|
+
*
|
|
383
|
+
* We combine the original source variable's scope (keyPrefix/defaultNs) with
|
|
384
|
+
* any namespace/keyPrefix arguments provided to this call and attach the
|
|
385
|
+
* resulting scope to the newly declared variable.
|
|
386
|
+
*/
|
|
387
|
+
private handleGetFixedTFromVariableDeclarator (node: VariableDeclarator, callExpr: CallExpression, sourceVarName: string): void {
|
|
388
|
+
if (node.id.type !== 'Identifier') return
|
|
389
|
+
|
|
390
|
+
const targetVarName = node.id.value
|
|
391
|
+
const sourceScope = this.getVarFromScope(sourceVarName)
|
|
392
|
+
if (!sourceScope) return
|
|
393
|
+
|
|
394
|
+
const args = callExpr.arguments
|
|
395
|
+
// getFixedT(lng, ns, keyPrefix)
|
|
396
|
+
const nsArg = args[1]?.expression
|
|
397
|
+
const keyPrefixArg = args[2]?.expression
|
|
398
|
+
|
|
399
|
+
const nsFromCall = (nsArg?.type === 'StringLiteral') ? nsArg.value : undefined
|
|
400
|
+
const keyPrefixFromCall = (keyPrefixArg?.type === 'StringLiteral') ? keyPrefixArg.value : undefined
|
|
401
|
+
|
|
402
|
+
// Merge: call args take precedence over source scope values
|
|
403
|
+
const finalNs = nsFromCall ?? sourceScope.defaultNs
|
|
404
|
+
const finalKeyPrefix = keyPrefixFromCall ?? sourceScope.keyPrefix
|
|
405
|
+
|
|
406
|
+
if (finalNs || finalKeyPrefix) {
|
|
407
|
+
this.setVarInScope(targetVarName, { defaultNs: finalNs, keyPrefix: finalKeyPrefix })
|
|
408
|
+
}
|
|
409
|
+
}
|
|
339
410
|
}
|
package/src/init.ts
CHANGED
|
@@ -69,6 +69,12 @@ export async function runInit () {
|
|
|
69
69
|
}
|
|
70
70
|
if (typeof detectedConfig?.extract?.input === 'string') detectedConfig.extract.input = [detectedConfig?.extract?.input]
|
|
71
71
|
|
|
72
|
+
// If heuristic detection returned a function for extract.output, don't use it as a prompt default.
|
|
73
|
+
// Prompt defaults must be strings; leave undefined so the prompt falls back to a sensible default.
|
|
74
|
+
if (detectedConfig && typeof detectedConfig.extract?.output === 'function') {
|
|
75
|
+
delete (detectedConfig.extract as any).output
|
|
76
|
+
}
|
|
77
|
+
|
|
72
78
|
const answers = await inquirer.prompt([
|
|
73
79
|
{
|
|
74
80
|
type: 'list',
|
|
@@ -93,7 +99,10 @@ export async function runInit () {
|
|
|
93
99
|
type: 'input',
|
|
94
100
|
name: 'output',
|
|
95
101
|
message: 'What is the path for your output resource files?',
|
|
96
|
-
default
|
|
102
|
+
// ensure the default is a string (detectedConfig.extract.output may be a function)
|
|
103
|
+
default: typeof detectedConfig?.extract?.output === 'string'
|
|
104
|
+
? detectedConfig!.extract!.output!
|
|
105
|
+
: 'public/locales/{{language}}/{{namespace}}.json',
|
|
97
106
|
},
|
|
98
107
|
])
|
|
99
108
|
|
package/src/locize.ts
CHANGED
|
@@ -143,14 +143,37 @@ function buildArgs (command: string, config: I18nextToolkitConfig, cliOptions: a
|
|
|
143
143
|
if (dryRun) commandArgs.push('--dry', 'true')
|
|
144
144
|
}
|
|
145
145
|
|
|
146
|
-
//
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
146
|
+
// Derive a sensible base path for locize from the configured output.
|
|
147
|
+
// If output is a string template we can strip the language placeholder.
|
|
148
|
+
// If output is a function we cannot reliably infer the base; fall back to cwd.
|
|
149
|
+
let basePath: string
|
|
150
|
+
try {
|
|
151
|
+
if (typeof extract.output === 'string') {
|
|
152
|
+
const outputNormalized = extract.output.replace(/\\/g, '/')
|
|
153
|
+
const baseCandidate = outputNormalized.includes('/{{language}}/')
|
|
154
|
+
? outputNormalized.split('/{{language}}/')[0]
|
|
155
|
+
: outputNormalized.replace('{{language}}', '')
|
|
156
|
+
const baseCandidateWithSep = baseCandidate.split('/').join(sep)
|
|
157
|
+
basePath = resolve(process.cwd(), baseCandidateWithSep)
|
|
158
|
+
} else if (typeof extract.output === 'function') {
|
|
159
|
+
// Try calling the function with the primary language to get an example path,
|
|
160
|
+
// then strip the language folder if present. If that fails, fallback to cwd.
|
|
161
|
+
try {
|
|
162
|
+
const sample = extract.output(config.extract.primaryLanguage || 'en')
|
|
163
|
+
const sampleNormalized = String(sample).replace(/\\/g, '/')
|
|
164
|
+
const baseCandidate = sampleNormalized.includes('/' + (config.extract.primaryLanguage || 'en') + '/')
|
|
165
|
+
? sampleNormalized.split('/' + (config.extract.primaryLanguage || 'en') + '/')[0]
|
|
166
|
+
: sampleNormalized.replace(config.extract.primaryLanguage || 'en', '')
|
|
167
|
+
basePath = resolve(process.cwd(), baseCandidate.split('/').join(sep))
|
|
168
|
+
} catch {
|
|
169
|
+
basePath = resolve(process.cwd(), '.')
|
|
170
|
+
}
|
|
171
|
+
} else {
|
|
172
|
+
basePath = resolve(process.cwd(), '.')
|
|
173
|
+
}
|
|
174
|
+
} catch {
|
|
175
|
+
basePath = resolve(process.cwd(), '.')
|
|
176
|
+
}
|
|
154
177
|
|
|
155
178
|
commandArgs.push('--path', basePath)
|
|
156
179
|
|
package/src/types.ts
CHANGED
|
@@ -34,7 +34,7 @@ export interface I18nextToolkitConfig {
|
|
|
34
34
|
ignore?: string | string[];
|
|
35
35
|
|
|
36
36
|
/** Output path template with placeholders: {{language}} for locale, {{namespace}} for namespace */
|
|
37
|
-
output: string;
|
|
37
|
+
output: string | ((language: string, namespace?: string) => string);
|
|
38
38
|
|
|
39
39
|
/**
|
|
40
40
|
* Default namespace when none is specified (default: 'translation').
|
package/src/utils/file-utils.ts
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import { mkdir, readFile, writeFile, access } from 'node:fs/promises'
|
|
2
|
-
import { dirname, extname, resolve } from 'node:path'
|
|
2
|
+
import { dirname, extname, resolve, normalize } from 'node:path'
|
|
3
3
|
import { createJiti } from 'jiti'
|
|
4
4
|
import type { I18nextToolkitConfig } from '../types'
|
|
5
5
|
import { getTsConfigAliases } from '../config'
|
|
@@ -54,33 +54,48 @@ export async function writeFileAsync (filePath: string, data: string): Promise<v
|
|
|
54
54
|
}
|
|
55
55
|
|
|
56
56
|
/**
|
|
57
|
-
*
|
|
58
|
-
* Supports both legacy and modern placeholder formats for language and namespace.
|
|
57
|
+
* Resolve an output template (string or function) into an actual path string.
|
|
59
58
|
*
|
|
60
|
-
*
|
|
61
|
-
*
|
|
62
|
-
*
|
|
63
|
-
*
|
|
64
|
-
*
|
|
65
|
-
* @example
|
|
66
|
-
* ```typescript
|
|
67
|
-
* // Modern format
|
|
68
|
-
* getOutputPath('locales/{{language}}/{{namespace}}.json', 'de', 'validation')
|
|
69
|
-
* // Returns: 'locales/de/validation.json'
|
|
70
|
-
*
|
|
71
|
-
* // Legacy format (also supported)
|
|
72
|
-
* getOutputPath('locales/{{lng}}/{{ns}}.json', 'en', 'common')
|
|
73
|
-
* // Returns: 'locales/en/common.json'
|
|
74
|
-
* ```
|
|
59
|
+
* - If `outputTemplate` is a function, call it with (language, namespace)
|
|
60
|
+
* - If it's a string, replace placeholders:
|
|
61
|
+
* - {{language}} or {{lng}} -> language
|
|
62
|
+
* - {{namespace}} -> namespace (or removed if namespace is undefined)
|
|
63
|
+
* - Normalizes duplicate slashes and returns a platform-correct path.
|
|
75
64
|
*/
|
|
76
65
|
export function getOutputPath (
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
namespace
|
|
66
|
+
outputTemplate: string | ((language: string, namespace?: string) => string) | undefined,
|
|
67
|
+
language: string,
|
|
68
|
+
namespace?: string
|
|
80
69
|
): string {
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
70
|
+
if (!outputTemplate) {
|
|
71
|
+
// Fallback to a sensible default
|
|
72
|
+
return normalize(`locales/${language}/${namespace ?? 'translation'}.json`)
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
if (typeof outputTemplate === 'function') {
|
|
76
|
+
try {
|
|
77
|
+
const result = String(outputTemplate(language, namespace))
|
|
78
|
+
return normalize(result.replace(/\/\/+/g, '/'))
|
|
79
|
+
} catch {
|
|
80
|
+
// If user function throws, fallback to default path
|
|
81
|
+
return normalize(`locales/${language}/${namespace ?? 'translation'}.json`)
|
|
82
|
+
}
|
|
83
|
+
}
|
|
84
|
+
|
|
85
|
+
// It's a string template
|
|
86
|
+
let out = String(outputTemplate)
|
|
87
|
+
out = out.replace(/\{\{language\}\}|\{\{lng\}\}/g, language)
|
|
88
|
+
|
|
89
|
+
if (namespace !== undefined && namespace !== null) {
|
|
90
|
+
out = out.replace(/\{\{namespace\}\}/g, namespace)
|
|
91
|
+
} else {
|
|
92
|
+
// remove any occurrences of /{{namespace}} or {{namespace}} (keeping surrounding slashes tidy)
|
|
93
|
+
out = out.replace(/\/?\{\{namespace\}\}/g, '')
|
|
94
|
+
}
|
|
95
|
+
|
|
96
|
+
// collapse duplicate slashes and normalize to platform-specific separators
|
|
97
|
+
out = out.replace(/\/\/+/g, '/')
|
|
98
|
+
return normalize(out)
|
|
84
99
|
}
|
|
85
100
|
|
|
86
101
|
/**
|
package/src/utils/validation.ts
CHANGED
|
@@ -36,8 +36,11 @@ export function validateExtractorConfig (config: I18nextToolkitConfig): void {
|
|
|
36
36
|
throw new ExtractorError('locales must be specified and non-empty')
|
|
37
37
|
}
|
|
38
38
|
|
|
39
|
-
|
|
40
|
-
|
|
39
|
+
// If output is a function, we accept it (user is responsible for producing a valid path).
|
|
40
|
+
if (typeof config.extract.output === 'string') {
|
|
41
|
+
if (!config.extract.output.includes('{{language}}') && !config.extract.output.includes('{{lng}}')) {
|
|
42
|
+
throw new ExtractorError('extract.output must contain {{language}} placeholder')
|
|
43
|
+
}
|
|
41
44
|
}
|
|
42
45
|
}
|
|
43
46
|
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"translation-manager.d.ts","sourceRoot":"","sources":["../../../src/extractor/core/translation-manager.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,iBAAiB,EAAE,YAAY,EAAE,oBAAoB,EAAE,MAAM,aAAa,CAAA;AA2UnF;;;;;;;;;;;;;;;;;;;;;;;;;;;;GA4BG;AACH,wBAAsB,eAAe,CACnC,IAAI,EAAE,GAAG,CAAC,MAAM,EAAE,YAAY,CAAC,EAC/B,UAAU,EAAE,GAAG,CAAC,MAAM,CAAC,EACvB,MAAM,EAAE,oBAAoB,EAC5B,EAAE,uBAA+B,EAAE,GAAE;IAAE,uBAAuB,CAAC,EAAE,OAAO,CAAA;CAAO,GAC9E,OAAO,CAAC,iBAAiB,EAAE,CAAC,
|
|
1
|
+
{"version":3,"file":"translation-manager.d.ts","sourceRoot":"","sources":["../../../src/extractor/core/translation-manager.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,iBAAiB,EAAE,YAAY,EAAE,oBAAoB,EAAE,MAAM,aAAa,CAAA;AA2UnF;;;;;;;;;;;;;;;;;;;;;;;;;;;;GA4BG;AACH,wBAAsB,eAAe,CACnC,IAAI,EAAE,GAAG,CAAC,MAAM,EAAE,YAAY,CAAC,EAC/B,UAAU,EAAE,GAAG,CAAC,MAAM,CAAC,EACvB,MAAM,EAAE,oBAAoB,EAC5B,EAAE,uBAA+B,EAAE,GAAE;IAAE,uBAAuB,CAAC,EAAE,OAAO,CAAA;CAAO,GAC9E,OAAO,CAAC,iBAAiB,EAAE,CAAC,CA2F9B"}
|
|
@@ -4,6 +4,7 @@ export declare class ScopeManager {
|
|
|
4
4
|
private scopeStack;
|
|
5
5
|
private config;
|
|
6
6
|
private scope;
|
|
7
|
+
private simpleConstants;
|
|
7
8
|
constructor(config: Omit<I18nextToolkitConfig, 'plugins'>);
|
|
8
9
|
/**
|
|
9
10
|
* Reset per-file scope state.
|
|
@@ -39,6 +40,11 @@ export declare class ScopeManager {
|
|
|
39
40
|
* @returns Scope information if found, undefined otherwise
|
|
40
41
|
*/
|
|
41
42
|
getVarFromScope(name: string): ScopeInfo | undefined;
|
|
43
|
+
private getUseTranslationConfig;
|
|
44
|
+
/**
|
|
45
|
+
* Resolve simple identifier declared in-file to its string literal value, if known.
|
|
46
|
+
*/
|
|
47
|
+
private resolveSimpleStringIdentifier;
|
|
42
48
|
/**
|
|
43
49
|
* Handles variable declarations that might define translation functions.
|
|
44
50
|
*
|
|
@@ -60,21 +66,6 @@ export declare class ScopeManager {
|
|
|
60
66
|
* @param callExpr - The CallExpression node representing the useTranslation invocation
|
|
61
67
|
* @param hookConfig - Configuration describing argument positions for namespace and keyPrefix
|
|
62
68
|
*/
|
|
63
|
-
private getUseTranslationConfig;
|
|
64
|
-
/**
|
|
65
|
-
* Processes useTranslation hook declarations to extract scope information.
|
|
66
|
-
*
|
|
67
|
-
* Handles various destructuring patterns:
|
|
68
|
-
* - `const [t] = useTranslation('ns')` - Array destructuring
|
|
69
|
-
* - `const { t } = useTranslation('ns')` - Object destructuring
|
|
70
|
-
* - `const { t: myT } = useTranslation('ns')` - Aliased destructuring
|
|
71
|
-
*
|
|
72
|
-
* Extracts namespace from the first argument and keyPrefix from options.
|
|
73
|
-
*
|
|
74
|
-
* @param node - Variable declarator with useTranslation call
|
|
75
|
-
* @param callExpr - The CallExpression node representing the useTranslation invocation
|
|
76
|
-
* @param hookConfig - Configuration describing argument positions for namespace and keyPrefix
|
|
77
|
-
*/
|
|
78
69
|
private handleUseTranslationForComments;
|
|
79
70
|
/**
|
|
80
71
|
* Processes useTranslation hook declarations to extract scope information.
|
|
@@ -103,5 +94,16 @@ export declare class ScopeManager {
|
|
|
103
94
|
* @param callExpr - The CallExpression node representing the getFixedT invocation
|
|
104
95
|
*/
|
|
105
96
|
private handleGetFixedTDeclarator;
|
|
97
|
+
/**
|
|
98
|
+
* Handles cases where a getFixedT-like function is a variable (from a custom hook)
|
|
99
|
+
* and is invoked to produce a bound `t` function, e.g.:
|
|
100
|
+
* const { getFixedT } = useTranslate('prefix')
|
|
101
|
+
* const t = getFixedT('en', 'ns')
|
|
102
|
+
*
|
|
103
|
+
* We combine the original source variable's scope (keyPrefix/defaultNs) with
|
|
104
|
+
* any namespace/keyPrefix arguments provided to this call and attach the
|
|
105
|
+
* resulting scope to the newly declared variable.
|
|
106
|
+
*/
|
|
107
|
+
private handleGetFixedTFromVariableDeclarator;
|
|
106
108
|
}
|
|
107
109
|
//# sourceMappingURL=scope-manager.d.ts.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"scope-manager.d.ts","sourceRoot":"","sources":["../../../src/extractor/parsers/scope-manager.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,kBAAkB,
|
|
1
|
+
{"version":3,"file":"scope-manager.d.ts","sourceRoot":"","sources":["../../../src/extractor/parsers/scope-manager.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,kBAAkB,EAAmC,MAAM,WAAW,CAAA;AACpF,OAAO,KAAK,EAAE,SAAS,EAA4B,oBAAoB,EAAE,MAAM,aAAa,CAAA;AAG5F,qBAAa,YAAY;IACvB,OAAO,CAAC,UAAU,CAAoC;IACtD,OAAO,CAAC,MAAM,CAAuC;IACrD,OAAO,CAAC,KAAK,CAAqE;IAGlF,OAAO,CAAC,eAAe,CAAiC;gBAE3C,MAAM,EAAE,IAAI,CAAC,oBAAoB,EAAE,SAAS,CAAC;IAI1D;;;;;;OAMG;IACI,KAAK,IAAK,IAAI;IAMrB;;;OAGG;IACH,UAAU,IAAK,IAAI;IAInB;;;OAGG;IACH,SAAS,IAAK,IAAI;IAIlB;;;;;;OAMG;IACH,aAAa,CAAE,IAAI,EAAE,MAAM,EAAE,IAAI,EAAE,SAAS,GAAG,IAAI;IAUnD;;;;;;OAMG;IACH,eAAe,CAAE,IAAI,EAAE,MAAM,GAAG,SAAS,GAAG,SAAS;IAkBrD,OAAO,CAAC,uBAAuB;IAoB/B;;OAEG;IACH,OAAO,CAAC,6BAA6B;IAIrC;;;;;;;;;;OAUG;IACH,wBAAwB,CAAE,IAAI,EAAE,kBAAkB,GAAG,IAAI;IAwDzD;;;;;;;;OAQG;IACH,OAAO,CAAC,+BAA+B;IA4EvC;;;;;;;;;;;;;OAaG;IACH,OAAO,CAAC,8BAA8B;IAiEtC;;;;;;;;;;OAUG;IACH,OAAO,CAAC,yBAAyB;IAoBjC;;;;;;;;;OASG;IACH,OAAO,CAAC,qCAAqC;CAuB9C"}
|
package/types/init.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"init.d.ts","sourceRoot":"","sources":["../src/init.ts"],"names":[],"mappings":"AAgCA;;;;;;;;;;;;;;;;;;;;;;;;;;GA0BG;AACH,wBAAsB,OAAO,
|
|
1
|
+
{"version":3,"file":"init.d.ts","sourceRoot":"","sources":["../src/init.ts"],"names":[],"mappings":"AAgCA;;;;;;;;;;;;;;;;;;;;;;;;;;GA0BG;AACH,wBAAsB,OAAO,kBAmH5B"}
|
package/types/locize.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"locize.d.ts","sourceRoot":"","sources":["../src/locize.ts"],"names":[],"mappings":"AAKA,OAAO,KAAK,EAAE,oBAAoB,EAAE,MAAM,SAAS,CAAA;
|
|
1
|
+
{"version":3,"file":"locize.d.ts","sourceRoot":"","sources":["../src/locize.ts"],"names":[],"mappings":"AAKA,OAAO,KAAK,EAAE,oBAAoB,EAAE,MAAM,SAAS,CAAA;AA+PnD,eAAO,MAAM,aAAa,GAAI,QAAQ,oBAAoB,EAAE,aAAa,GAAG,kBAAiD,CAAA;AAC7H,eAAO,MAAM,iBAAiB,GAAI,QAAQ,oBAAoB,EAAE,aAAa,GAAG,kBAAqD,CAAA;AACrI,eAAO,MAAM,gBAAgB,GAAI,QAAQ,oBAAoB,EAAE,aAAa,GAAG,kBAAoD,CAAA"}
|
package/types/types.d.ts
CHANGED
|
@@ -30,7 +30,7 @@ export interface I18nextToolkitConfig {
|
|
|
30
30
|
/** Glob pattern(s) for files to ignore during extraction */
|
|
31
31
|
ignore?: string | string[];
|
|
32
32
|
/** Output path template with placeholders: {{language}} for locale, {{namespace}} for namespace */
|
|
33
|
-
output: string;
|
|
33
|
+
output: string | ((language: string, namespace?: string) => string);
|
|
34
34
|
/**
|
|
35
35
|
* Default namespace when none is specified (default: 'translation').
|
|
36
36
|
* Set to false will not generate any namespace, useful if i.e. the output is a single language json with 1 namespace (and no nesting).
|
package/types/types.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"types.d.ts","sourceRoot":"","sources":["../src/types.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,UAAU,EAAE,IAAI,EAAE,gBAAgB,EAAE,MAAM,WAAW,CAAA;AAEnE;;;;;;;;;;;;;;;;;;;;GAoBG;AACH,MAAM,WAAW,oBAAoB;IACnC,iEAAiE;IACjE,OAAO,EAAE,MAAM,EAAE,CAAC;IAElB,2DAA2D;IAC3D,OAAO,EAAE;QACP,oEAAoE;QACpE,KAAK,EAAE,MAAM,GAAG,MAAM,EAAE,CAAC;QAEzB,4DAA4D;QAC5D,MAAM,CAAC,EAAE,MAAM,GAAG,MAAM,EAAE,CAAC;QAE3B,mGAAmG;QACnG,MAAM,EAAE,MAAM,CAAC;
|
|
1
|
+
{"version":3,"file":"types.d.ts","sourceRoot":"","sources":["../src/types.ts"],"names":[],"mappings":"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"}
|
|
@@ -39,26 +39,15 @@ export declare function readFileAsync(filePath: string): Promise<string>;
|
|
|
39
39
|
*/
|
|
40
40
|
export declare function writeFileAsync(filePath: string, data: string): Promise<void>;
|
|
41
41
|
/**
|
|
42
|
-
*
|
|
43
|
-
* Supports both legacy and modern placeholder formats for language and namespace.
|
|
42
|
+
* Resolve an output template (string or function) into an actual path string.
|
|
44
43
|
*
|
|
45
|
-
*
|
|
46
|
-
*
|
|
47
|
-
*
|
|
48
|
-
*
|
|
49
|
-
*
|
|
50
|
-
* @example
|
|
51
|
-
* ```typescript
|
|
52
|
-
* // Modern format
|
|
53
|
-
* getOutputPath('locales/{{language}}/{{namespace}}.json', 'de', 'validation')
|
|
54
|
-
* // Returns: 'locales/de/validation.json'
|
|
55
|
-
*
|
|
56
|
-
* // Legacy format (also supported)
|
|
57
|
-
* getOutputPath('locales/{{lng}}/{{ns}}.json', 'en', 'common')
|
|
58
|
-
* // Returns: 'locales/en/common.json'
|
|
59
|
-
* ```
|
|
44
|
+
* - If `outputTemplate` is a function, call it with (language, namespace)
|
|
45
|
+
* - If it's a string, replace placeholders:
|
|
46
|
+
* - {{language}} or {{lng}} -> language
|
|
47
|
+
* - {{namespace}} -> namespace (or removed if namespace is undefined)
|
|
48
|
+
* - Normalizes duplicate slashes and returns a platform-correct path.
|
|
60
49
|
*/
|
|
61
|
-
export declare function getOutputPath(
|
|
50
|
+
export declare function getOutputPath(outputTemplate: string | ((language: string, namespace?: string) => string) | undefined, language: string, namespace?: string): string;
|
|
62
51
|
/**
|
|
63
52
|
* Dynamically loads a translation file, supporting .json, .js, and .ts formats.
|
|
64
53
|
* @param filePath - The path to the translation file.
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"file-utils.d.ts","sourceRoot":"","sources":["../../src/utils/file-utils.ts"],"names":[],"mappings":"AAGA,OAAO,KAAK,EAAE,oBAAoB,EAAE,MAAM,UAAU,CAAA;AAGpD;;;;;;;;;;;GAWG;AACH,wBAAsB,qBAAqB,CAAE,QAAQ,EAAE,MAAM,GAAG,OAAO,CAAC,IAAI,CAAC,CAG5E;AAED;;;;;;;;;;;GAWG;AACH,wBAAsB,aAAa,CAAE,QAAQ,EAAE,MAAM,GAAG,OAAO,CAAC,MAAM,CAAC,CAEtE;AAED;;;;;;;;;;;GAWG;AACH,wBAAsB,cAAc,CAAE,QAAQ,EAAE,MAAM,EAAE,IAAI,EAAE,MAAM,GAAG,OAAO,CAAC,IAAI,CAAC,CAEnF;AAED
|
|
1
|
+
{"version":3,"file":"file-utils.d.ts","sourceRoot":"","sources":["../../src/utils/file-utils.ts"],"names":[],"mappings":"AAGA,OAAO,KAAK,EAAE,oBAAoB,EAAE,MAAM,UAAU,CAAA;AAGpD;;;;;;;;;;;GAWG;AACH,wBAAsB,qBAAqB,CAAE,QAAQ,EAAE,MAAM,GAAG,OAAO,CAAC,IAAI,CAAC,CAG5E;AAED;;;;;;;;;;;GAWG;AACH,wBAAsB,aAAa,CAAE,QAAQ,EAAE,MAAM,GAAG,OAAO,CAAC,MAAM,CAAC,CAEtE;AAED;;;;;;;;;;;GAWG;AACH,wBAAsB,cAAc,CAAE,QAAQ,EAAE,MAAM,EAAE,IAAI,EAAE,MAAM,GAAG,OAAO,CAAC,IAAI,CAAC,CAEnF;AAED;;;;;;;;GAQG;AACH,wBAAgB,aAAa,CAC3B,cAAc,EAAE,MAAM,GAAG,CAAC,CAAC,QAAQ,EAAE,MAAM,EAAE,SAAS,CAAC,EAAE,MAAM,KAAK,MAAM,CAAC,GAAG,SAAS,EACvF,QAAQ,EAAE,MAAM,EAChB,SAAS,CAAC,EAAE,MAAM,GACjB,MAAM,CA8BR;AAED;;;;GAIG;AACH,wBAAsB,mBAAmB,CAAE,QAAQ,EAAE,MAAM,GAAG,OAAO,CAAC,MAAM,CAAC,MAAM,EAAE,GAAG,CAAC,GAAG,IAAI,CAAC,CAgChG;AAED;;;;;;GAMG;AACH,wBAAgB,wBAAwB,CACtC,IAAI,EAAE,MAAM,CAAC,MAAM,EAAE,GAAG,CAAC,EACzB,MAAM,GAAE,oBAAoB,CAAC,SAAS,CAAC,CAAC,cAAc,CAAU,EAChE,WAAW,GAAE,MAAM,GAAG,MAAU,GAC/B,MAAM,CAgBR"}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"validation.d.ts","sourceRoot":"","sources":["../../src/utils/validation.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,oBAAoB,EAAE,MAAM,UAAU,CAAA;AAEpD;;;;;;;;;;;;;;;;;;;;;;GAsBG;AACH,wBAAgB,uBAAuB,CAAE,MAAM,EAAE,oBAAoB,GAAG,IAAI,
|
|
1
|
+
{"version":3,"file":"validation.d.ts","sourceRoot":"","sources":["../../src/utils/validation.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,oBAAoB,EAAE,MAAM,UAAU,CAAA;AAEpD;;;;;;;;;;;;;;;;;;;;;;GAsBG;AACH,wBAAgB,uBAAuB,CAAE,MAAM,EAAE,oBAAoB,GAAG,IAAI,CAmB3E;AAED;;;;;;;;GAQG;AACH,qBAAa,cAAe,SAAQ,KAAK;aAUrB,IAAI,CAAC,EAAE,MAAM;aACb,KAAK,CAAC,EAAE,KAAK;IAV/B;;;;;;OAMG;gBAED,OAAO,EAAE,MAAM,EACC,IAAI,CAAC,EAAE,MAAM,YAAA,EACb,KAAK,CAAC,EAAE,KAAK,YAAA;CAKhC"}
|