i18next-cli 1.0.2 → 1.2.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 +15 -0
- package/README.md +3 -0
- package/dist/cjs/cli.js +1 -1
- package/dist/cjs/extractor/core/translation-manager.js +1 -1
- package/dist/cjs/utils/file-utils.js +1 -1
- package/dist/esm/cli.js +1 -1
- package/dist/esm/extractor/core/translation-manager.js +1 -1
- package/dist/esm/utils/file-utils.js +1 -1
- package/package.json +1 -1
- package/src/cli.ts +1 -1
- package/src/extractor/core/translation-manager.ts +9 -2
- package/src/types.ts +6 -3
- package/src/utils/file-utils.ts +2 -2
- package/types/extractor/core/translation-manager.d.ts.map +1 -1
- package/types/types.d.ts +5 -3
- package/types/types.d.ts.map +1 -1
- package/types/utils/file-utils.d.ts +1 -1
- package/types/utils/file-utils.d.ts.map +1 -1
package/CHANGELOG.md
CHANGED
|
@@ -5,6 +5,21 @@ 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.2.0](https://github.com/i18next/i18next-cli/compare/v1.1.0...v1.2.0) - 2025-10-02
|
|
9
|
+
|
|
10
|
+
### Added
|
|
11
|
+
|
|
12
|
+
- **Extractor:** The `extract.sort` option now accepts a custom comparator function, allowing for advanced key sorting logic beyond simple alphabetical order. [#16](https://github.com/i18next/i18next-cli/pull/16)
|
|
13
|
+
|
|
14
|
+
### Changed
|
|
15
|
+
|
|
16
|
+
- **File Output:** All generated translation files (`.json`, `.js`, `.ts`) now end with a trailing newline to improve compatibility with POSIX standards and various linters. [#17](https://github.com/i18next/i18next-cli/pull/17)
|
|
17
|
+
- **Extractor:** The `extract.indentation` option now accepts a string (e.g., `'\t'`) in addition to a number, allowing for the use of tab characters for indentation in the output files. [#15](https://github.com/i18next/i18next-cli/pull/15)
|
|
18
|
+
|
|
19
|
+
## [1.1.0](https://github.com/i18next/i18next-cli/compare/v1.0.2...v1.1.0) - 2025-10-02
|
|
20
|
+
|
|
21
|
+
- **Extractor:** Added a new `extract.removeUnusedKeys` option to control whether keys no longer found in the source code are removed from translation files. This defaults to `true` to maintain the existing pruning behavior. Set it to `false` to preserve all existing keys, which is useful for projects with dynamic keys. [#18](https://github.com/i18next/i18next-cli/issues/18)
|
|
22
|
+
|
|
8
23
|
## [1.0.2](https://github.com/i18next/i18next-cli/compare/v1.0.1...v1.0.2) - 2025-10-01
|
|
9
24
|
|
|
10
25
|
- **Extractor & Linter:** Added a new `extract.ignore` option to provide a simpler and more reliable way to exclude files from processing. This option accepts an array of glob patterns and is respected by both the `extract` and `lint` commands, avoiding the need for complex negative glob patterns.
|
package/README.md
CHANGED
|
@@ -347,6 +347,9 @@ export default defineConfig({
|
|
|
347
347
|
secondaryLanguages: ['de', 'fr'], // Defaults to all locales except primaryLanguage
|
|
348
348
|
|
|
349
349
|
defaultValue: '', // Default value for missing keys in secondary languages
|
|
350
|
+
|
|
351
|
+
/** If true, keys that are not found in the source code will be removed from translation files. (default: true) */
|
|
352
|
+
removeUnusedKeys: true,
|
|
350
353
|
},
|
|
351
354
|
|
|
352
355
|
// TypeScript type generation
|
package/dist/cjs/cli.js
CHANGED
|
@@ -1,2 +1,2 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
|
-
"use strict";var e=require("commander"),t=require("chokidar"),o=require("glob"),n=require("chalk"),i=require("./config.js"),a=require("./heuristic-config.js"),r=require("./extractor/core/extractor.js");require("node:path"),require("node:fs/promises"),require("jiti");var c=require("./types-generator.js"),s=require("./syncer.js"),l=require("./migrator.js"),u=require("./init.js"),d=require("./linter.js"),g=require("./status.js"),p=require("./locize.js");const f=new e.Command;f.name("i18next-cli").description("A unified, high-performance i18next CLI.").version("1.0
|
|
2
|
+
"use strict";var e=require("commander"),t=require("chokidar"),o=require("glob"),n=require("chalk"),i=require("./config.js"),a=require("./heuristic-config.js"),r=require("./extractor/core/extractor.js");require("node:path"),require("node:fs/promises"),require("jiti");var c=require("./types-generator.js"),s=require("./syncer.js"),l=require("./migrator.js"),u=require("./init.js"),d=require("./linter.js"),g=require("./status.js"),p=require("./locize.js");const f=new e.Command;f.name("i18next-cli").description("A unified, high-performance i18next CLI.").version("1.2.0"),f.command("extract").description("Extract translation keys from source files and update resource files.").option("-w, --watch","Watch for file changes and re-run the extractor.").option("--ci","Exit with a non-zero status code if any files are updated.").action(async e=>{const a=await i.ensureConfig(),c=async()=>{const t=await r.runExtractor(a);e.ci&&t&&(console.error(n.red.bold("\n[CI Mode] Error: Translation files were updated. Please commit the changes.")),console.log(n.yellow("💡 Tip: Tired of committing JSON files? locize syncs your team automatically => https://www.locize.com/docs/getting-started")),console.log(` Learn more: ${n.cyan("npx i18next-cli locize-sync")}`),process.exit(1))};if(await c(),e.watch){console.log("\nWatching for changes...");t.watch(await o.glob(a.extract.input),{ignored:/node_modules/,persistent:!0}).on("change",e=>{console.log(`\nFile changed: ${e}`),c()})}}),f.command("status [locale]").description("Display translation status. Provide a locale for a detailed key-by-key view.").option("-n, --namespace <ns>","Filter the status report by a specific namespace").action(async(e,t)=>{let o=await i.loadConfig();if(!o){console.log(n.blue("No config file found. Attempting to detect project structure..."));const e=await a.detectConfig();e||(console.error(n.red("Could not automatically detect your project structure.")),console.log(`Please create a config file first by running: ${n.cyan("npx i18next-cli init")}`),process.exit(1)),console.log(n.green("Project structure detected successfully!")),o=e}await g.runStatus(o,{detail:e,namespace:t.namespace})}),f.command("types").description("Generate TypeScript definitions from translation resource files.").option("-w, --watch","Watch for file changes and re-run the type generator.").action(async e=>{const n=await i.ensureConfig(),a=()=>c.runTypesGenerator(n);if(await a(),e.watch){console.log("\nWatching for changes...");t.watch(await o.glob(n.types?.input||[]),{persistent:!0}).on("change",e=>{console.log(`\nFile changed: ${e}`),a()})}}),f.command("sync").description("Synchronize secondary language files with the primary language file.").action(async()=>{const e=await i.ensureConfig();await s.runSyncer(e)}),f.command("migrate-config").description("Migrate a legacy i18next-parser.config.js to the new format.").action(async()=>{await l.runMigrator()}),f.command("init").description("Create a new i18next.config.ts/js file with an interactive setup wizard.").action(u.runInit),f.command("lint").description("Find potential issues like hardcoded strings in your codebase.").action(async()=>{let e=await i.loadConfig();if(!e){console.log(n.blue("No config file found. Attempting to detect project structure..."));const t=await a.detectConfig();t||(console.error(n.red("Could not automatically detect your project structure.")),console.log(`Please create a config file first by running: ${n.cyan("npx i1e-toolkit init")}`),process.exit(1)),console.log(n.green("Project structure detected successfully!")),e=t}await d.runLinter(e)}),f.command("locize-sync").description("Synchronize local translations with your locize project.").option("--update-values","Update values of existing translations on locize.").option("--src-lng-only","Check for changes in source language only.").option("--compare-mtime","Compare modification times when syncing.").option("--dry-run","Run the command without making any changes.").action(async e=>{const t=await i.ensureConfig();await p.runLocizeSync(t,e)}),f.command("locize-download").description("Download all translations from your locize project.").action(async e=>{const t=await i.ensureConfig();await p.runLocizeDownload(t,e)}),f.command("locize-migrate").description("Migrate local translation files to a new locize project.").action(async e=>{const t=await i.ensureConfig();await p.runLocizeMigrate(t,e)}),f.parse(process.argv);
|
|
@@ -1 +1 @@
|
|
|
1
|
-
"use strict";var t=require("node:path"),e=require("../../utils/nested-object.js"),s=require("../../utils/file-utils.js");function a(t){const e=`^${t.replace(/[.+?^${}()|[\]\\]/g,"\\$&").replace(/\*/g,".*")}$`;return new RegExp(e)}exports.getTranslations=async function(r,n,o){o.extract.primaryLanguage||=o.locales[0]||"en",o.extract.secondaryLanguages||=o.locales.filter(t=>t!==o?.extract?.primaryLanguage);const c=o.extract.primaryLanguage,l=o.extract.defaultNS??"translation",u=o.extract.keySeparator??".",i=[...o.extract.preservePatterns||[]],p=o.extract.mergeNamespaces??!1,f=o.extract.indentation??2;for(const t of n)i.push(`${t}.*`);const
|
|
1
|
+
"use strict";var t=require("node:path"),e=require("../../utils/nested-object.js"),s=require("../../utils/file-utils.js");function a(t){const e=`^${t.replace(/[.+?^${}()|[\]\\]/g,"\\$&").replace(/\*/g,".*")}$`;return new RegExp(e)}exports.getTranslations=async function(r,n,o){o.extract.primaryLanguage||=o.locales[0]||"en",o.extract.secondaryLanguages||=o.locales.filter(t=>t!==o?.extract?.primaryLanguage);const c=o.extract.primaryLanguage,l=o.extract.defaultNS??"translation",u=o.extract.keySeparator??".",i=[...o.extract.preservePatterns||[]],p=o.extract.mergeNamespaces??!1,f=o.extract.indentation??2,g=o.extract.removeUnusedKeys??!0;for(const t of n)i.push(`${t}.*`);const d=i.map(a),x=new Map;for(const t of r.values()){const e=t.ns||l;x.has(e)||x.set(e,[]),x.get(e).push(t)}const y=[];for(const a of o.locales){const r=p?await s.loadTranslationFile(t.resolve(process.cwd(),s.getOutputPath(o.extract.output,a)))||{}:null,n={};for(const[l,i]of x.entries()){const x=s.getOutputPath(o.extract.output,a,p?void 0:l),N=t.resolve(process.cwd(),x),h=p?r?.[l]||{}:await s.loadTranslationFile(N)||{},m=g?{}:JSON.parse(JSON.stringify(h)),w=e.getNestedKeys(h,u);for(const t of w)if(d.some(e=>e.test(t))){const s=e.getNestedValue(h,t,u);e.setNestedValue(m,t,s,u)}const O=!1===o.extract.sort?i:[...i].sort("function"==typeof o.extract.sort?o.extract.sort:(t,e)=>t.key.localeCompare(e.key));for(const{key:t,defaultValue:s}of O){const r=e.getNestedValue(h,t,u)??(a===c?s:o.extract.defaultValue??"");e.setNestedValue(m,t,r,u)}if(p)n[l]=m;else{const t=JSON.stringify(h,null,f),e=JSON.stringify(m,null,f);y.push({path:N,updated:e!==t,newTranslations:m,existingTranslations:h})}}if(p){const e=s.getOutputPath(o.extract.output,a),c=t.resolve(process.cwd(),e),l=JSON.stringify(r,null,f),u=JSON.stringify(n,null,f);y.push({path:c,updated:u!==l,newTranslations:n,existingTranslations:r||{}})}}return y};
|
|
@@ -1 +1 @@
|
|
|
1
|
-
"use strict";var e=require("node:fs/promises");require("node:path");var t=require("jiti"),r="undefined"!=typeof document?document.currentScript:null;exports.getOutputPath=function(e,t,r=""){return e.replace("{{language}}",t).replace("{{lng}}",t).replace("{{namespace}}",r).replace("{{ns}}",r)},exports.loadTranslationFile=async function(n){try{if(n.endsWith(".json")){const t=await e.readFile(n,"utf-8");return JSON.parse(t)}if(n.endsWith(".ts")||n.endsWith(".js")){const e=t.createJiti("undefined"==typeof document?require("url").pathToFileURL(__filename).href:r&&"SCRIPT"===r.tagName.toUpperCase()&&r.src||new URL("utils/file-utils.js",document.baseURI).href);return await e.import(n,{default:!0})}}catch(e){return null}return 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
|
|
1
|
+
"use strict";var e=require("node:fs/promises");require("node:path");var t=require("jiti"),r="undefined"!=typeof document?document.currentScript:null;exports.getOutputPath=function(e,t,r=""){return e.replace("{{language}}",t).replace("{{lng}}",t).replace("{{namespace}}",r).replace("{{ns}}",r)},exports.loadTranslationFile=async function(n){try{if(n.endsWith(".json")){const t=await e.readFile(n,"utf-8");return JSON.parse(t)}if(n.endsWith(".ts")||n.endsWith(".js")){const e=t.createJiti("undefined"==typeof document?require("url").pathToFileURL(__filename).href:r&&"SCRIPT"===r.tagName.toUpperCase()&&r.src||new URL("utils/file-utils.js",document.baseURI).href);return await e.import(n,{default:!0})}}catch(e){return null}return 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`}};
|
package/dist/esm/cli.js
CHANGED
|
@@ -1,2 +1,2 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
|
-
import{Command as o}from"commander";import t from"chokidar";import{glob as e}from"glob";import i from"chalk";import{ensureConfig as n,loadConfig as a}from"./config.js";import{detectConfig as c}from"./heuristic-config.js";import{runExtractor as r}from"./extractor/core/extractor.js";import"node:path";import"node:fs/promises";import"jiti";import{runTypesGenerator as s}from"./types-generator.js";import{runSyncer as l}from"./syncer.js";import{runMigrator as m}from"./migrator.js";import{runInit as p}from"./init.js";import{runLinter as d}from"./linter.js";import{runStatus as f}from"./status.js";import{runLocizeSync as g,runLocizeDownload as u,runLocizeMigrate as y}from"./locize.js";const w=new o;w.name("i18next-cli").description("A unified, high-performance i18next CLI.").version("1.0
|
|
2
|
+
import{Command as o}from"commander";import t from"chokidar";import{glob as e}from"glob";import i from"chalk";import{ensureConfig as n,loadConfig as a}from"./config.js";import{detectConfig as c}from"./heuristic-config.js";import{runExtractor as r}from"./extractor/core/extractor.js";import"node:path";import"node:fs/promises";import"jiti";import{runTypesGenerator as s}from"./types-generator.js";import{runSyncer as l}from"./syncer.js";import{runMigrator as m}from"./migrator.js";import{runInit as p}from"./init.js";import{runLinter as d}from"./linter.js";import{runStatus as f}from"./status.js";import{runLocizeSync as g,runLocizeDownload as u,runLocizeMigrate as y}from"./locize.js";const w=new o;w.name("i18next-cli").description("A unified, high-performance i18next CLI.").version("1.2.0"),w.command("extract").description("Extract translation keys from source files and update resource files.").option("-w, --watch","Watch for file changes and re-run the extractor.").option("--ci","Exit with a non-zero status code if any files are updated.").action(async o=>{const a=await n(),c=async()=>{const t=await r(a);o.ci&&t&&(console.error(i.red.bold("\n[CI Mode] Error: Translation files were updated. Please commit the changes.")),console.log(i.yellow("💡 Tip: Tired of committing JSON files? locize syncs your team automatically => https://www.locize.com/docs/getting-started")),console.log(` Learn more: ${i.cyan("npx i18next-cli locize-sync")}`),process.exit(1))};if(await c(),o.watch){console.log("\nWatching for changes...");t.watch(await e(a.extract.input),{ignored:/node_modules/,persistent:!0}).on("change",o=>{console.log(`\nFile changed: ${o}`),c()})}}),w.command("status [locale]").description("Display translation status. Provide a locale for a detailed key-by-key view.").option("-n, --namespace <ns>","Filter the status report by a specific namespace").action(async(o,t)=>{let e=await a();if(!e){console.log(i.blue("No config file found. Attempting to detect project structure..."));const o=await c();o||(console.error(i.red("Could not automatically detect your project structure.")),console.log(`Please create a config file first by running: ${i.cyan("npx i18next-cli init")}`),process.exit(1)),console.log(i.green("Project structure detected successfully!")),e=o}await f(e,{detail:o,namespace:t.namespace})}),w.command("types").description("Generate TypeScript definitions from translation resource files.").option("-w, --watch","Watch for file changes and re-run the type generator.").action(async o=>{const i=await n(),a=()=>s(i);if(await a(),o.watch){console.log("\nWatching for changes...");t.watch(await e(i.types?.input||[]),{persistent:!0}).on("change",o=>{console.log(`\nFile changed: ${o}`),a()})}}),w.command("sync").description("Synchronize secondary language files with the primary language file.").action(async()=>{const o=await n();await l(o)}),w.command("migrate-config").description("Migrate a legacy i18next-parser.config.js to the new format.").action(async()=>{await m()}),w.command("init").description("Create a new i18next.config.ts/js file with an interactive setup wizard.").action(p),w.command("lint").description("Find potential issues like hardcoded strings in your codebase.").action(async()=>{let o=await a();if(!o){console.log(i.blue("No config file found. Attempting to detect project structure..."));const t=await c();t||(console.error(i.red("Could not automatically detect your project structure.")),console.log(`Please create a config file first by running: ${i.cyan("npx i1e-toolkit init")}`),process.exit(1)),console.log(i.green("Project structure detected successfully!")),o=t}await d(o)}),w.command("locize-sync").description("Synchronize local translations with your locize project.").option("--update-values","Update values of existing translations on locize.").option("--src-lng-only","Check for changes in source language only.").option("--compare-mtime","Compare modification times when syncing.").option("--dry-run","Run the command without making any changes.").action(async o=>{const t=await n();await g(t,o)}),w.command("locize-download").description("Download all translations from your locize project.").action(async o=>{const t=await n();await u(t,o)}),w.command("locize-migrate").description("Migrate local translation files to a new locize project.").action(async o=>{const t=await n();await y(t,o)}),w.parse(process.argv);
|
|
@@ -1 +1 @@
|
|
|
1
|
-
import{resolve as t}from"node:path";import{getNestedKeys as e,getNestedValue as s,setNestedValue as a}from"../../utils/nested-object.js";import{loadTranslationFile as
|
|
1
|
+
import{resolve as t}from"node:path";import{getNestedKeys as e,getNestedValue as s,setNestedValue as a}from"../../utils/nested-object.js";import{loadTranslationFile as r,getOutputPath as o}from"../../utils/file-utils.js";function n(t){const e=`^${t.replace(/[.+?^${}()|[\]\\]/g,"\\$&").replace(/\*/g,".*")}$`;return new RegExp(e)}async function c(c,i,l){l.extract.primaryLanguage||=l.locales[0]||"en",l.extract.secondaryLanguages||=l.locales.filter(t=>t!==l?.extract?.primaryLanguage);const u=l.extract.primaryLanguage,p=l.extract.defaultNS??"translation",f=l.extract.keySeparator??".",x=[...l.extract.preservePatterns||[]],g=l.extract.mergeNamespaces??!1,d=l.extract.indentation??2,y=l.extract.removeUnusedKeys??!0;for(const t of i)x.push(`${t}.*`);const m=x.map(n),w=new Map;for(const t of c.values()){const e=t.ns||p;w.has(e)||w.set(e,[]),w.get(e).push(t)}const h=[];for(const n of l.locales){const c=g?await r(t(process.cwd(),o(l.extract.output,n)))||{}:null,i={};for(const[p,x]of w.entries()){const w=o(l.extract.output,n,g?void 0:p),N=t(process.cwd(),w),S=g?c?.[p]||{}:await r(N)||{},J=y?{}:JSON.parse(JSON.stringify(S)),O=e(S,f);for(const t of O)if(m.some(e=>e.test(t))){const e=s(S,t,f);a(J,t,e,f)}const $=!1===l.extract.sort?x:[...x].sort("function"==typeof l.extract.sort?l.extract.sort:(t,e)=>t.key.localeCompare(e.key));for(const{key:t,defaultValue:e}of $){const r=s(S,t,f)??(n===u?e:l.extract.defaultValue??"");a(J,t,r,f)}if(g)i[p]=J;else{const t=JSON.stringify(S,null,d),e=JSON.stringify(J,null,d);h.push({path:N,updated:e!==t,newTranslations:J,existingTranslations:S})}}if(g){const e=o(l.extract.output,n),s=t(process.cwd(),e),a=JSON.stringify(c,null,d),r=JSON.stringify(i,null,d);h.push({path:s,updated:r!==a,newTranslations:i,existingTranslations:c||{}})}}return h}export{c as getTranslations};
|
|
@@ -1 +1 @@
|
|
|
1
|
-
import{readFile as t}from"node:fs/promises";import"node:path";import{createJiti as e}from"jiti";function n(t,e,n=""){return t.replace("{{language}}",e).replace("{{lng}}",e).replace("{{namespace}}",n).replace("{{ns}}",n)}async function r(n){try{if(n.endsWith(".json")){const e=await t(n,"utf-8");return JSON.parse(e)}if(n.endsWith(".ts")||n.endsWith(".js")){const t=e(import.meta.url);return await t.import(n,{default:!0})}}catch(t){return null}return null}function s(t,e="json",n=2){const r=JSON.stringify(t,null,n);switch(e){case"js":case"js-esm":return`export default ${r};\n`;case"js-cjs":return`module.exports = ${r};\n`;case"ts":return`export default ${r} as const;\n`;default:return
|
|
1
|
+
import{readFile as t}from"node:fs/promises";import"node:path";import{createJiti as e}from"jiti";function n(t,e,n=""){return t.replace("{{language}}",e).replace("{{lng}}",e).replace("{{namespace}}",n).replace("{{ns}}",n)}async function r(n){try{if(n.endsWith(".json")){const e=await t(n,"utf-8");return JSON.parse(e)}if(n.endsWith(".ts")||n.endsWith(".js")){const t=e(import.meta.url);return await t.import(n,{default:!0})}}catch(t){return null}return null}function s(t,e="json",n=2){const r=JSON.stringify(t,null,n);switch(e){case"js":case"js-esm":return`export default ${r};\n`;case"js-cjs":return`module.exports = ${r};\n`;case"ts":return`export default ${r} as const;\n`;default:return`${r}\n`}}export{n as getOutputPath,r as loadTranslationFile,s as serializeTranslationFile};
|
package/package.json
CHANGED
package/src/cli.ts
CHANGED
|
@@ -55,6 +55,7 @@ export async function getTranslations (
|
|
|
55
55
|
const patternsToPreserve = [...(config.extract.preservePatterns || [])]
|
|
56
56
|
const mergeNamespaces = config.extract.mergeNamespaces ?? false
|
|
57
57
|
const indentation = config.extract.indentation ?? 2
|
|
58
|
+
const removeUnusedKeys = config.extract.removeUnusedKeys ?? true
|
|
58
59
|
|
|
59
60
|
for (const key of objectKeys) {
|
|
60
61
|
// Convert the object key to a glob pattern to preserve all its children
|
|
@@ -87,7 +88,9 @@ export async function getTranslations (
|
|
|
87
88
|
? existingMergedFile?.[ns] || {}
|
|
88
89
|
: await loadTranslationFile(fullPath) || {}
|
|
89
90
|
|
|
90
|
-
const newTranslations: Record<string, any> =
|
|
91
|
+
const newTranslations: Record<string, any> = removeUnusedKeys
|
|
92
|
+
? {}
|
|
93
|
+
: JSON.parse(JSON.stringify(existingTranslations))
|
|
91
94
|
|
|
92
95
|
const existingKeys = getNestedKeys(existingTranslations, keySeparator)
|
|
93
96
|
for (const existingKey of existingKeys) {
|
|
@@ -97,7 +100,11 @@ export async function getTranslations (
|
|
|
97
100
|
}
|
|
98
101
|
}
|
|
99
102
|
|
|
100
|
-
const sortedKeys =
|
|
103
|
+
const sortedKeys = config.extract.sort === false
|
|
104
|
+
? nsKeys
|
|
105
|
+
: [...nsKeys].sort(typeof config.extract.sort === 'function'
|
|
106
|
+
? config.extract.sort
|
|
107
|
+
: (a, b) => a.key.localeCompare(b.key))
|
|
101
108
|
for (const { key, defaultValue } of sortedKeys) {
|
|
102
109
|
const existingValue = getNestedValue(existingTranslations, key, keySeparator)
|
|
103
110
|
const valueToSet = existingValue ?? (locale === primaryLanguage ? defaultValue : (config.extract.defaultValue ?? ''))
|
package/src/types.ts
CHANGED
|
@@ -81,11 +81,11 @@ export interface I18nextToolkitConfig {
|
|
|
81
81
|
/** Glob patterns for keys to preserve even if not found in source (for dynamic keys) */
|
|
82
82
|
preservePatterns?: string[];
|
|
83
83
|
|
|
84
|
-
/** Whether to sort keys alphabetically in output files (default: true) */
|
|
85
|
-
sort?: boolean;
|
|
84
|
+
/** Whether to sort keys alphabetically in output files, or a comparator function to customize the order (default: true) */
|
|
85
|
+
sort?: boolean | ((a: ExtractedKey, b: ExtractedKey) => number);
|
|
86
86
|
|
|
87
87
|
/** Number of spaces for JSON indentation (default: 2) */
|
|
88
|
-
indentation?: number;
|
|
88
|
+
indentation?: number | string;
|
|
89
89
|
|
|
90
90
|
/** Default value to use for missing translations in secondary languages */
|
|
91
91
|
defaultValue?: string;
|
|
@@ -113,6 +113,9 @@ export interface I18nextToolkitConfig {
|
|
|
113
113
|
* (default: false)
|
|
114
114
|
*/
|
|
115
115
|
mergeNamespaces?: boolean;
|
|
116
|
+
|
|
117
|
+
/** If true, keys that are not found in the source code will be removed from translation files. (default: true) */
|
|
118
|
+
removeUnusedKeys?: boolean;
|
|
116
119
|
};
|
|
117
120
|
|
|
118
121
|
/** Configuration options for TypeScript type generation */
|
package/src/utils/file-utils.ts
CHANGED
|
@@ -114,7 +114,7 @@ export async function loadTranslationFile (filePath: string): Promise<Record<str
|
|
|
114
114
|
export function serializeTranslationFile (
|
|
115
115
|
data: Record<string, any>,
|
|
116
116
|
format: I18nextToolkitConfig['extract']['outputFormat'] = 'json',
|
|
117
|
-
indentation: number = 2
|
|
117
|
+
indentation: number | string = 2
|
|
118
118
|
): string {
|
|
119
119
|
const jsonString = JSON.stringify(data, null, indentation)
|
|
120
120
|
|
|
@@ -129,6 +129,6 @@ export function serializeTranslationFile (
|
|
|
129
129
|
return `export default ${jsonString} as const;\n`
|
|
130
130
|
case 'json':
|
|
131
131
|
default:
|
|
132
|
-
return jsonString
|
|
132
|
+
return `${jsonString}\n`
|
|
133
133
|
}
|
|
134
134
|
}
|
|
@@ -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;AAgBnF;;;;;;;;;;;;;;;;;;;;;;;;;;;GA2BG;AACH,wBAAsB,eAAe,CACnC,IAAI,EAAE,GAAG,CAAC,MAAM,EAAE,YAAY,CAAC,EAC/B,UAAU,EAAE,GAAG,CAAC,MAAM,CAAC,EACvB,MAAM,EAAE,oBAAoB,GAC3B,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;AAgBnF;;;;;;;;;;;;;;;;;;;;;;;;;;;GA2BG;AACH,wBAAsB,eAAe,CACnC,IAAI,EAAE,GAAG,CAAC,MAAM,EAAE,YAAY,CAAC,EAC/B,UAAU,EAAE,GAAG,CAAC,MAAM,CAAC,EACvB,MAAM,EAAE,oBAAoB,GAC3B,OAAO,CAAC,iBAAiB,EAAE,CAAC,CAoF9B"}
|
package/types/types.d.ts
CHANGED
|
@@ -64,10 +64,10 @@ export interface I18nextToolkitConfig {
|
|
|
64
64
|
transKeepBasicHtmlNodesFor?: string[];
|
|
65
65
|
/** Glob patterns for keys to preserve even if not found in source (for dynamic keys) */
|
|
66
66
|
preservePatterns?: string[];
|
|
67
|
-
/** Whether to sort keys alphabetically in output files (default: true) */
|
|
68
|
-
sort?: boolean;
|
|
67
|
+
/** Whether to sort keys alphabetically in output files, or a comparator function to customize the order (default: true) */
|
|
68
|
+
sort?: boolean | ((a: ExtractedKey, b: ExtractedKey) => number);
|
|
69
69
|
/** Number of spaces for JSON indentation (default: 2) */
|
|
70
|
-
indentation?: number;
|
|
70
|
+
indentation?: number | string;
|
|
71
71
|
/** Default value to use for missing translations in secondary languages */
|
|
72
72
|
defaultValue?: string;
|
|
73
73
|
/** Primary language that provides default values (default: first locale) */
|
|
@@ -90,6 +90,8 @@ export interface I18nextToolkitConfig {
|
|
|
90
90
|
* (default: false)
|
|
91
91
|
*/
|
|
92
92
|
mergeNamespaces?: boolean;
|
|
93
|
+
/** If true, keys that are not found in the source code will be removed from translation files. (default: true) */
|
|
94
|
+
removeUnusedKeys?: boolean;
|
|
93
95
|
};
|
|
94
96
|
/** Configuration options for TypeScript type generation */
|
|
95
97
|
types?: {
|
package/types/types.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"types.d.ts","sourceRoot":"","sources":["../src/types.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,IAAI,EAAE,UAAU,EAAE,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;QAEf,wEAAwE;QACxE,SAAS,CAAC,EAAE,MAAM,CAAC;QAEnB,uEAAuE;QACvE,YAAY,CAAC,EAAE,MAAM,GAAG,KAAK,GAAG,IAAI,CAAC;QAErC,8EAA8E;QAC9E,WAAW,CAAC,EAAE,MAAM,GAAG,KAAK,GAAG,IAAI,CAAC;QAEpC,oDAAoD;QACpD,gBAAgB,CAAC,EAAE,MAAM,CAAC;QAE1B,mDAAmD;QACnD,eAAe,CAAC,EAAE,MAAM,CAAC;QAEzB,wEAAwE;QACxE,SAAS,CAAC,EAAE,MAAM,EAAE,CAAC;QAErB,4EAA4E;QAC5E,eAAe,CAAC,EAAE,MAAM,EAAE,CAAC;QAE3B;;;;;WAKG;QACH,mBAAmB,CAAC,EAAE,KAAK,CAAC,MAAM,GAAG;YACnC,IAAI,EAAE,MAAM,CAAC;YACb,KAAK,CAAC,EAAE,MAAM,CAAC;YACf,YAAY,CAAC,EAAE,MAAM,CAAC;SACvB,CAAC,CAAC;QAEH,kFAAkF;QAClF,iBAAiB,CAAC,EAAE,MAAM,EAAE,CAAC;QAE7B,kGAAkG;QAClG,WAAW,CAAC,EAAE,MAAM,EAAE,CAAC;QAEvB,8FAA8F;QAC9F,0BAA0B,CAAC,EAAE,MAAM,EAAE,CAAC;QAEtC,wFAAwF;QACxF,gBAAgB,CAAC,EAAE,MAAM,EAAE,CAAC;QAE5B,
|
|
1
|
+
{"version":3,"file":"types.d.ts","sourceRoot":"","sources":["../src/types.ts"],"names":[],"mappings":"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"}
|
|
@@ -72,5 +72,5 @@ export declare function loadTranslationFile(filePath: string): Promise<Record<st
|
|
|
72
72
|
* @param indentation - The number of spaces for indentation.
|
|
73
73
|
* @returns The serialized file content as a string.
|
|
74
74
|
*/
|
|
75
|
-
export declare function serializeTranslationFile(data: Record<string, any>, format?: I18nextToolkitConfig['extract']['outputFormat'], indentation?: number): string;
|
|
75
|
+
export declare function serializeTranslationFile(data: Record<string, any>, format?: I18nextToolkitConfig['extract']['outputFormat'], indentation?: number | string): string;
|
|
76
76
|
//# sourceMappingURL=file-utils.d.ts.map
|
|
@@ -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;AAEpD;;;;;;;;;;;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;;;;;;;;;;;;;;;;;;;GAmBG;AACH,wBAAgB,aAAa,CAC3B,QAAQ,EAAE,MAAM,EAChB,MAAM,EAAE,MAAM,EACd,SAAS,GAAE,MAAW,GACrB,MAAM,CAIR;AAED;;;;GAIG;AACH,wBAAsB,mBAAmB,CAAE,QAAQ,EAAE,MAAM,GAAG,OAAO,CAAC,MAAM,CAAC,MAAM,EAAE,GAAG,CAAC,GAAG,IAAI,CAAC,CAehG;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,MAAU,
|
|
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;AAEpD;;;;;;;;;;;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;;;;;;;;;;;;;;;;;;;GAmBG;AACH,wBAAgB,aAAa,CAC3B,QAAQ,EAAE,MAAM,EAChB,MAAM,EAAE,MAAM,EACd,SAAS,GAAE,MAAW,GACrB,MAAM,CAIR;AAED;;;;GAIG;AACH,wBAAsB,mBAAmB,CAAE,QAAQ,EAAE,MAAM,GAAG,OAAO,CAAC,MAAM,CAAC,MAAM,EAAE,GAAG,CAAC,GAAG,IAAI,CAAC,CAehG;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"}
|