@ttoss/i18n-cli 0.7.30 → 0.7.32

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/README.md CHANGED
@@ -1,12 +1,15 @@
1
1
  # @ttoss/i18n-cli
2
2
 
3
- **@ttoss/i18n-cli** is a CLI to [extract](https://formatjs.io/docs/getting-started/message-extraction) and [compile](https://formatjs.io/docs/getting-started/message-distribution) translations from your code. It implements [FormatJS Application Workflow](https://formatjs.io/docs/getting-started/application-workflow).
3
+ A CLI tool for extracting and compiling translations from your code using [FormatJS](https://formatjs.io/docs/getting-started/application-workflow). Automatically handles translations from your application and all ttoss packages.
4
4
 
5
- This package is part of the ttoss ecosystem, so it simplifies the process of extracting and compiling translations of your application and all ttoss packages that it uses. For example, if your application uses the [@ttoss/react-i18n](/docs/modules/packages/react-i18n/), `@ttoss/i18n-cli` will extract and compile the translations of this package as well.
5
+ ## Key Features
6
6
 
7
- :::note
8
- You should declare your messages as describe in the [FormatJS](https://formatjs.io/docs/getting-started/message-declaration) documentation.
9
- :::
7
+ - **Automatic extraction** from source code using FormatJS patterns
8
+ - **Unified translation management** for your app and ttoss packages
9
+ - **Missing translation detection** with detailed reports
10
+ - **Unused translation cleanup** to maintain clean translation files
11
+ - **Flexible compilation** with optional steps
12
+ - **Custom file patterns** and ignore rules
10
13
 
11
14
  ## Installation
12
15
 
@@ -14,9 +17,9 @@ You should declare your messages as describe in the [FormatJS](https://formatjs.
14
17
  pnpm add @ttoss/i18n-cli --dev
15
18
  ```
16
19
 
17
- ## Setup
20
+ ## Quick Start
18
21
 
19
- Add this script to your `package.json`
22
+ Add script to your `package.json`:
20
23
 
21
24
  ```json
22
25
  {
@@ -26,104 +29,211 @@ Add this script to your `package.json`
26
29
  }
27
30
  ```
28
31
 
29
- Add to your `.gitignore`:
32
+ Add to `.gitignore`:
30
33
 
31
34
  ```
32
35
  i18n/compiled/
33
36
  i18n/missing/
37
+ i18n/unused/
38
+ ```
39
+
40
+ Run extraction and compilation:
41
+
42
+ ```sh
43
+ pnpm i18n
44
+ ```
45
+
46
+ ## How It Works
47
+
48
+ The CLI creates a structured workflow for managing translations:
49
+
50
+ ```
51
+ 📁 i18n/
52
+ ├── 📁 lang/ # Translation files
53
+ │ ├── 📄 en.json # Auto-generated (don't edit)
54
+ │ └── 📄 pt-BR.json # Edit with your translations
55
+ ├── 📁 compiled/ # Auto-generated (production files)
56
+ │ ├── 📄 en.json # Compiled for runtime
57
+ │ └── 📄 pt-BR.json # Compiled for runtime
58
+ ├── 📁 missing/ # Auto-generated (analysis reports)
59
+ │ └── 📄 pt-BR.json # What needs translation
60
+ └── 📁 unused/ # Auto-generated (cleanup reports)
61
+ └── 📄 pt-BR.json # Unused translations per language
34
62
  ```
35
63
 
36
64
  ## Usage
37
65
 
38
- ### Extract only:
66
+ ### Basic Commands
67
+
68
+ **Extract and compile everything:**
69
+
70
+ ```sh
71
+ pnpm i18n
72
+ ```
73
+
74
+ **Extract only (no compilation):**
75
+
76
+ ```sh
77
+ pnpm i18n --no-compile
78
+ ```
79
+
80
+ **Ignore ttoss package translations:**
81
+
82
+ ```sh
83
+ pnpm i18n --ignore-ttoss-packages
84
+ ```
85
+
86
+ **Custom file patterns:**
39
87
 
40
88
  ```sh
41
- pnpm i18n --no-compile # ttoss-i18n --no-compile
89
+ pnpm i18n --pattern "src/**/*.{ts,tsx}" --ignore "**/*.test.*"
42
90
  ```
43
91
 
44
- This command extracts translations from your code but doesn't compile them. And created a new path (`i18n/lang/en.json`) if doesn't exists with extracted translations. As followed below:
92
+ ### Translation Workflow
45
93
 
46
- - 📂 i18n
47
- - 📂 lang
48
- - 📄 en.json
94
+ Follow this step-by-step process to set up internationalization:
95
+
96
+ #### Step 1: Write Code with FormatJS Messages
97
+
98
+ ```tsx
99
+ import { FormattedMessage } from 'react-intl';
100
+
101
+ <FormattedMessage
102
+ defaultMessage="Welcome to our app!"
103
+ description="Main welcome message"
104
+ />;
105
+ ```
106
+
107
+ #### Step 2: Extract Messages from Source Code
108
+
109
+ ```sh
110
+ pnpm i18n --no-compile
111
+ ```
112
+
113
+ **Result:** Creates `i18n/lang/en.json` directly with all messages found in your code:
49
114
 
50
115
  ```json
51
- // i18n/lang/en.json
52
116
  {
53
- "0XOzcH": {
54
- "defaultMessage": "My title page",
55
- "description": "Page title"
117
+ "2mAHlQ": {
118
+ "defaultMessage": "Welcome to our app!",
119
+ "description": "Main welcome message"
56
120
  }
57
121
  }
58
122
  ```
59
123
 
60
- To translate your text, you only need to duplicate the file `i18n/lang/en.json` to your new language and translate it, as followed below:
124
+ > **Important**: Don't edit `en.json` manually - it will be overwritten on next extraction.
125
+
126
+ #### Step 3: Create Translation Files
127
+
128
+ Copy the base English file to create other language files:
129
+
130
+ ```sh
131
+ # Create files for other languages
132
+ cp i18n/lang/en.json i18n/lang/pt-BR.json
133
+ cp i18n/lang/en.json i18n/lang/es.json
134
+ ```
135
+
136
+ #### Step 4: Translate Your Messages
137
+
138
+ Edit each language file with the appropriate translations:
139
+
140
+ > **These are the ONLY files you should edit manually**
141
+
142
+ **`i18n/lang/pt-BR.json`:**
61
143
 
62
144
  ```json
63
- // i18n/lang/pt-BR.json
64
145
  {
65
- "0XOzcH": {
66
- "defaultMessage": "Título da minha página",
67
- "description": "Título da página"
146
+ "2mAHlQ": {
147
+ "defaultMessage": "Bem-vindo ao nosso app!",
148
+ "description": "Mensagem principal de boas-vindas"
68
149
  }
69
150
  }
70
151
  ```
71
152
 
72
- `en` is the default language, so you don't need to create a file for it. But you need to create a file for each language you want to translate.
153
+ **`i18n/lang/es.json`:**
73
154
 
74
- ### Extract and compile:
155
+ ```json
156
+ {
157
+ "2mAHlQ": {
158
+ "defaultMessage": "¡Bienvenido a nuestra app!",
159
+ "description": "Mensaje principal de bienvenida"
160
+ }
161
+ }
162
+ ```
163
+
164
+ #### Step 5: Compile for Production
75
165
 
76
166
  ```sh
77
- pnpm i18n # ttoss-i18n
167
+ pnpm i18n
78
168
  ```
79
169
 
80
- This command extracts translations from your code and compiles them into a usable format. And create a new path (`i18n/compiled/LANG.json` and `i18n/missing/LANG.json`) if doesn't exists with compiled translations based in all of the files on path `i18n/lang`. As followed below:
170
+ **Result:** Generates optimized compiled files and analysis reports:
171
+
172
+ - `i18n/compiled/` - Production-ready translation files
173
+ - `i18n/missing/` - Shows untranslated messages
174
+ - `i18n/unused/` - Identifies unused translations
81
175
 
82
- - 📂 i18n
83
- - 📂 compiled
84
- - 📄 en.json
85
- - 📄 pt-BR.json
86
- - 📂 lang
87
- - 📄 en.json
88
- - 📄 pt-BR.json
89
- - 📂 missing
90
- - 📄 en.json
91
- - 📄 pt-BR.json
176
+ ### Generated Files
92
177
 
93
- #### i18n/compiled/en.json
178
+ **Base Language File (`i18n/lang/en.json`) - Auto-generated:**
94
179
 
95
180
  ```json
96
- // i18n/compiled/en.json
97
181
  {
98
- "0XOzcH": [
99
- {
100
- "type": 0,
101
- "value": "My title page"
102
- }
103
- ]
182
+ "2mAHlQ": {
183
+ "defaultMessage": "Welcome to our app!",
184
+ "description": "Main welcome message"
185
+ }
186
+ }
187
+ ```
188
+
189
+ **Translation Files (`i18n/lang/pt-BR.json`) - Edit with your translations:**
190
+
191
+ ```json
192
+ {
193
+ "2mAHlQ": {
194
+ "defaultMessage": "Bem-vindo ao nosso app!",
195
+ "description": "Mensagem principal de boas-vindas"
196
+ }
104
197
  }
105
198
  ```
106
199
 
107
- #### i18n/compiled/pt-BR.json
200
+ **Compiled Output (`i18n/compiled/pt-BR.json`) - Auto-generated:**
108
201
 
109
202
  ```json
110
- // i18n/compiled/pt-BR.json
111
203
  {
112
- "0XOzcH": [
204
+ "2mAHlQ": [
113
205
  {
114
206
  "type": 0,
115
- "value": "Título da minha página"
207
+ "value": "Bem-vindo ao nosso app!"
116
208
  }
117
209
  ]
118
210
  }
119
211
  ```
120
212
 
121
- The `i18n/missing` folder contains all the translations that are missing in the `i18n/lang/LANG.json` file, compared with `i18n/lang/en.json`. This folder is useful to know which translations are missing in your application.
213
+ ### Analysis Reports
122
214
 
123
- ### Ignoring ttoss packages:
215
+ The CLI automatically generates helpful reports to assist with translation management:
124
216
 
125
- ```sh
126
- pnpm i18n --ignore-ttoss-packages # ttoss-i18n --ignore-ttoss-packages
127
- ```
217
+ - **Missing translations** (`i18n/missing/LANG.json`): Shows what needs translation
218
+ - **Unused translations** (`i18n/unused/LANG.json`): Identifies translations to remove per language
219
+
220
+ > **Note**: All reports are auto-generated - use them as reference only.
221
+
222
+ ## Command Options
223
+
224
+ | Option | Description |
225
+ | ------------------------- | ------------------------------------------------------------------------ |
226
+ | `--no-compile` | Extract only, skip compilation step |
227
+ | `--ignore-ttoss-packages` | Skip extraction from ttoss dependencies |
228
+ | `--pattern <glob>` | Custom file pattern for extraction (default: `src/**/*.{js,jsx,ts,tsx}`) |
229
+ | `--ignore <patterns>` | Files/patterns to ignore during extraction |
230
+
231
+ ## Integration with ttoss Ecosystem
232
+
233
+ When using ttoss packages like [@ttoss/react-i18n](https://ttoss.dev/docs/modules/packages/react-i18n/), the CLI automatically:
234
+
235
+ - Extracts translations from installed ttoss packages
236
+ - Merges them with your application translations
237
+ - Provides unified translation management
128
238
 
129
- This command extracts and compiles translations, ignoring translations from all ttoss packages, if you have them installed in your project.
239
+ This eliminates manual copying of package translations and ensures consistency across your application.
package/bin/cli.js CHANGED
@@ -1,2 +1,4 @@
1
1
  #!/usr/bin/env node
2
- require('../dist/index.js');
2
+ // eslint-disable-next-line no-undef, @typescript-eslint/no-require-imports
3
+ const { executeI18nCli } = require('../dist/index.js');
4
+ executeI18nCli();
package/dist/index.js CHANGED
@@ -7,6 +7,16 @@ var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
7
7
  var __getOwnPropNames = Object.getOwnPropertyNames;
8
8
  var __getProtoOf = Object.getPrototypeOf;
9
9
  var __hasOwnProp = Object.prototype.hasOwnProperty;
10
+ var __name = (target, value) => __defProp(target, "name", {
11
+ value,
12
+ configurable: true
13
+ });
14
+ var __export = (target, all) => {
15
+ for (var name in all) __defProp(target, name, {
16
+ get: all[name],
17
+ enumerable: true
18
+ });
19
+ };
10
20
  var __copyProps = (to, from, except, desc) => {
11
21
  if (from && typeof from === "object" || typeof from === "function") {
12
22
  for (let key of __getOwnPropNames(from)) if (!__hasOwnProp.call(to, key) && key !== except) __defProp(to, key, {
@@ -25,8 +35,26 @@ isNodeMode || !mod || !mod.__esModule ? __defProp(target, "default", {
25
35
  value: mod,
26
36
  enumerable: true
27
37
  }) : target, mod));
38
+ var __toCommonJS = mod => __copyProps(__defProp({}, "__esModule", {
39
+ value: true
40
+ }), mod);
28
41
 
29
42
  // src/index.ts
43
+ var index_exports = {};
44
+ __export(index_exports, {
45
+ analyzeMissingAndUnusedTranslations: () => analyzeMissingAndUnusedTranslations,
46
+ compareTranslations: () => compareTranslations,
47
+ compileTranslations: () => compileTranslations,
48
+ executeI18nCli: () => executeI18nCli,
49
+ extractTranslationsFromSource: () => extractTranslationsFromSource,
50
+ getI18nConfig: () => getI18nConfig,
51
+ getTtossExtractedTranslations: () => getTtossExtractedTranslations,
52
+ writeCleanTranslations: () => writeCleanTranslations,
53
+ writeFinalExtractedData: () => writeFinalExtractedData,
54
+ writeMissingTranslations: () => writeMissingTranslations,
55
+ writeUnusedTranslations: () => writeUnusedTranslations
56
+ });
57
+ module.exports = __toCommonJS(index_exports);
30
58
  var import_node_fs = __toESM(require("fs"));
31
59
  var import_node_path = __toESM(require("path"));
32
60
  var import_cli_lib = require("@formatjs/cli-lib");
@@ -39,72 +67,72 @@ var COMPILE_DIR = import_node_path.default.join(DEFAULT_DIR, "compiled");
39
67
  var MISSING_DIR = import_node_path.default.join(DEFAULT_DIR, "missing");
40
68
  var UNUSED_DIR = import_node_path.default.join(DEFAULT_DIR, "unused");
41
69
  var argv = (0, import_minimist.default)(process.argv.slice(2));
42
- var getTtossExtractedTranslations = async () => {
43
- const packageJsonAsString = await import_node_fs.default.promises.readFile(import_node_path.default.join(process.cwd(), "package.json"));
44
- const packageJson = JSON.parse(packageJsonAsString.toString());
45
- const ttossDependencies = Object.keys({
46
- ...packageJson.dependencies,
47
- ...packageJson.peerDependencies
48
- }).filter(dependency => {
49
- return dependency.startsWith("@ttoss");
50
- }).filter(dependency => {
51
- return dependency !== "@ttoss/react-i18n";
52
- }).filter((dependency, index, array) => {
53
- return array.indexOf(dependency) === index;
70
+ var getI18nConfig = /* @__PURE__ */__name(() => {
71
+ return {
72
+ defaultDir: DEFAULT_DIR,
73
+ extractDir: EXTRACT_DIR,
74
+ extractFile: EXTRACT_FILE,
75
+ compileDir: COMPILE_DIR,
76
+ missingDir: MISSING_DIR,
77
+ unusedDir: UNUSED_DIR
78
+ };
79
+ }, "getI18nConfig");
80
+ var extractTranslationsFromSource = /* @__PURE__ */__name(async (pattern, ignore) => {
81
+ return (0, import_cli_lib.extract)(import_fast_glob.default.sync(pattern, {
82
+ ignore
83
+ }), {
84
+ idInterpolationPattern: "[sha512:contenthash:base64:6]"
54
85
  });
55
- const ttossExtractedTranslations = {};
56
- for (const dependency of ttossDependencies) {
86
+ }, "extractTranslationsFromSource");
87
+ var getTtossExtractedTranslations = /* @__PURE__ */__name(async () => {
88
+ const readPackageJson = /* @__PURE__ */__name(async () => {
89
+ const packageJsonAsString = await import_node_fs.default.promises.readFile(import_node_path.default.join(process.cwd(), "package.json"));
90
+ return JSON.parse(packageJsonAsString.toString());
91
+ }, "readPackageJson");
92
+ const getTtossDependencies = /* @__PURE__ */__name(packageJson2 => {
93
+ return Object.keys({
94
+ ...packageJson2.dependencies,
95
+ ...packageJson2.peerDependencies
96
+ }).filter(dependency => {
97
+ return dependency.startsWith("@ttoss");
98
+ }).filter(dependency => {
99
+ return dependency !== "@ttoss/react-i18n";
100
+ }).filter((dependency, index, array) => {
101
+ return array.indexOf(dependency) === index;
102
+ });
103
+ }, "getTtossDependencies");
104
+ const loadDependencyTranslations = /* @__PURE__ */__name(dependency => {
57
105
  try {
58
- const dependencyPathFromCwd = import_node_path.default.join(process.cwd(), "node_modules", dependency);
59
- const requirePath = import_node_path.default.join(dependencyPathFromCwd, EXTRACT_FILE);
106
+ const dependencyPath = import_node_path.default.join(process.cwd(), "node_modules", dependency);
107
+ const config = getI18nConfig();
108
+ const requirePath = import_node_path.default.join(dependencyPath, config.extractFile);
60
109
  const extractedTranslations = require(requirePath);
61
- const extractedTranslationsWithModule = Object.keys(extractedTranslations).reduce((accumulator, key) => {
110
+ return Object.keys(extractedTranslations).reduce((accumulator, key) => {
62
111
  accumulator[key] = {
63
112
  module: dependency,
64
113
  ...extractedTranslations[key]
65
114
  };
66
115
  return accumulator;
67
116
  }, {});
68
- Object.assign(ttossExtractedTranslations, extractedTranslationsWithModule);
69
117
  } catch {
70
- continue;
118
+ return {};
71
119
  }
120
+ }, "loadDependencyTranslations");
121
+ const packageJson = await readPackageJson();
122
+ const ttossDependencies = getTtossDependencies(packageJson);
123
+ const ttossExtractedTranslations = {};
124
+ for (const dependency of ttossDependencies) {
125
+ const dependencyTranslations = loadDependencyTranslations(dependency);
126
+ Object.assign(ttossExtractedTranslations, dependencyTranslations);
72
127
  }
73
128
  return ttossExtractedTranslations;
74
- };
75
- var executeI18nCli = async () => {
76
- const pattern = argv.pattern || "src/**/*.{ts,tsx}";
77
- const ignore = argv.ignore || ["src/**/*.test.{ts,tsx}", "src/**/*.d.ts"];
78
- const extractedDataAsString = await (0, import_cli_lib.extract)(import_fast_glob.default.sync(pattern, {
79
- ignore
80
- }), {
81
- idInterpolationPattern: "[sha512:contenthash:base64:6]"
82
- });
83
- const ignoreTtossPackages = argv["ignore-ttoss-packages"];
84
- const ttossExtractedTranslations = await getTtossExtractedTranslations();
85
- const finalExtractedData = (() => {
86
- if (ignoreTtossPackages) {
87
- return extractedDataAsString;
88
- }
89
- const parsedExtractedData = JSON.parse(extractedDataAsString);
90
- const finalExtractedData2 = {
91
- ...parsedExtractedData,
92
- ...ttossExtractedTranslations
93
- };
94
- return JSON.stringify(finalExtractedData2, void 0, 2);
95
- })();
96
- await import_node_fs.default.promises.mkdir(EXTRACT_DIR, {
97
- recursive: true
98
- });
99
- await import_node_fs.default.promises.writeFile(EXTRACT_FILE, finalExtractedData);
100
- if (argv["no-compile"]) {
101
- return;
102
- }
129
+ }, "getTtossExtractedTranslations");
130
+ var compileTranslations = /* @__PURE__ */__name(async config => {
103
131
  const translations = import_fast_glob.default.sync("**/*.json", {
104
- cwd: EXTRACT_DIR,
132
+ cwd: config.extractDir,
105
133
  absolute: true
106
134
  });
107
- await import_node_fs.default.promises.mkdir(COMPILE_DIR, {
135
+ await import_node_fs.default.promises.mkdir(config.compileDir, {
108
136
  recursive: true
109
137
  });
110
138
  for (const translation of translations) {
@@ -113,65 +141,128 @@ var executeI18nCli = async () => {
113
141
  ast: true
114
142
  });
115
143
  if (filename) {
116
- await import_node_fs.default.promises.writeFile(import_node_path.default.join(COMPILE_DIR, filename), compiledDataAsString);
144
+ await import_node_fs.default.promises.writeFile(import_node_path.default.join(config.compileDir, filename), compiledDataAsString);
117
145
  }
118
146
  }
119
- await import_node_fs.default.promises.mkdir(MISSING_DIR, {
147
+ }, "compileTranslations");
148
+ var writeFinalExtractedData = /* @__PURE__ */__name(async (finalData, config) => {
149
+ await import_node_fs.default.promises.mkdir(config.extractDir, {
120
150
  recursive: true
121
151
  });
122
- await import_node_fs.default.promises.mkdir(UNUSED_DIR, {
152
+ await import_node_fs.default.promises.writeFile(config.extractFile, finalData);
153
+ }, "writeFinalExtractedData");
154
+ var compareTranslations = /* @__PURE__ */__name((extractedTranslations, translationData) => {
155
+ const missingTranslations = Object.keys(extractedTranslations).reduce((accumulator, key) => {
156
+ if (!translationData[key]) {
157
+ accumulator[key] = extractedTranslations[key];
158
+ }
159
+ return accumulator;
160
+ }, {});
161
+ const unusedTranslations = Object.keys(translationData).reduce((accumulator, key) => {
162
+ if (!extractedTranslations[key]) {
163
+ accumulator[key] = translationData[key];
164
+ }
165
+ return accumulator;
166
+ }, {});
167
+ const cleanTranslations = Object.keys(translationData).reduce((accumulator, key) => {
168
+ if (extractedTranslations[key]) {
169
+ accumulator[key] = translationData[key];
170
+ }
171
+ return accumulator;
172
+ }, {});
173
+ return {
174
+ missingTranslations,
175
+ unusedTranslations,
176
+ cleanTranslations
177
+ };
178
+ }, "compareTranslations");
179
+ var writeMissingTranslations = /* @__PURE__ */__name(async (filename, missingTranslations, config) => {
180
+ await import_node_fs.default.promises.writeFile(import_node_path.default.join(config.missingDir, filename), JSON.stringify(missingTranslations, void 0, 2));
181
+ }, "writeMissingTranslations");
182
+ var writeUnusedTranslations = /* @__PURE__ */__name(async (filename, unusedTranslations, config) => {
183
+ try {
184
+ const existingUnusedData = await import_node_fs.default.promises.readFile(import_node_path.default.join(config.unusedDir, filename));
185
+ const existingUnused = JSON.parse(existingUnusedData.toString());
186
+ const updatedUnused = {
187
+ ...existingUnused,
188
+ ...unusedTranslations
189
+ };
190
+ await import_node_fs.default.promises.writeFile(import_node_path.default.join(config.unusedDir, filename), JSON.stringify(updatedUnused, void 0, 2));
191
+ } catch {
192
+ await import_node_fs.default.promises.writeFile(import_node_path.default.join(config.unusedDir, filename), JSON.stringify(unusedTranslations, void 0, 2));
193
+ }
194
+ }, "writeUnusedTranslations");
195
+ var writeCleanTranslations = /* @__PURE__ */__name(async (filename, cleanTranslations, config) => {
196
+ await import_node_fs.default.promises.writeFile(import_node_path.default.join(config.extractDir, filename), JSON.stringify(cleanTranslations, void 0, 2));
197
+ }, "writeCleanTranslations");
198
+ var analyzeMissingAndUnusedTranslations = /* @__PURE__ */__name(async (finalExtractedData, config) => {
199
+ const translations = import_fast_glob.default.sync("**/*.json", {
200
+ cwd: config.extractDir,
201
+ absolute: true
202
+ });
203
+ await import_node_fs.default.promises.mkdir(config.missingDir, {
123
204
  recursive: true
124
205
  });
206
+ await import_node_fs.default.promises.mkdir(config.unusedDir, {
207
+ recursive: true
208
+ });
209
+ const extractedTranslations = JSON.parse(finalExtractedData);
125
210
  for (const translation of translations) {
126
211
  const filename = translation.split("/").pop();
127
212
  if (filename === "en.json") {
128
213
  continue;
129
214
  }
130
- const extractedTranslations = JSON.parse(finalExtractedData);
131
- const object = JSON.parse(import_node_fs.default.readFileSync(translation, {
215
+ const translationData = JSON.parse(import_node_fs.default.readFileSync(translation, {
132
216
  encoding: "utf8"
133
217
  }));
134
- const missingTranslations = Object.keys(extractedTranslations).reduce((accumulator, key) => {
135
- if (!object[key]) {
136
- accumulator[key] = extractedTranslations[key];
137
- }
138
- return accumulator;
139
- },
140
- // eslint-disable-next-line @typescript-eslint/no-explicit-any
141
- {});
142
- const unusedTranslations = Object.keys(object).reduce((accumulator, key) => {
143
- if (!extractedTranslations[key]) {
144
- accumulator[key] = object[key];
145
- }
146
- return accumulator;
147
- },
148
- // eslint-disable-next-line @typescript-eslint/no-explicit-any
149
- {});
150
- const withoutUnnecessaryTranslations = Object.keys(object).reduce((accumulator, key) => {
151
- if (object[key] !== unusedTranslations[key]) {
152
- accumulator[key] = object[key];
153
- }
154
- return accumulator;
155
- },
156
- // eslint-disable-next-line @typescript-eslint/no-explicit-any
157
- {});
218
+ const {
219
+ missingTranslations,
220
+ unusedTranslations,
221
+ cleanTranslations
222
+ } = compareTranslations(extractedTranslations, translationData);
158
223
  if (filename) {
159
- await import_node_fs.default.promises.writeFile(import_node_path.default.join(MISSING_DIR, filename), JSON.stringify(missingTranslations, void 0, 2));
160
- try {
161
- const existentUnusedJsonAsString = await import_node_fs.default.promises.readFile(import_node_path.default.join(UNUSED_DIR, filename));
162
- if (existentUnusedJsonAsString) {
163
- const existentUnusedJson = JSON.parse(existentUnusedJsonAsString.toString());
164
- const updatedUnusedTranslations = {
165
- ...existentUnusedJson,
166
- ...unusedTranslations
167
- };
168
- await import_node_fs.default.promises.writeFile(import_node_path.default.join(UNUSED_DIR, filename), JSON.stringify(updatedUnusedTranslations, void 0, 2));
169
- }
170
- } catch {
171
- await import_node_fs.default.promises.writeFile(import_node_path.default.join(UNUSED_DIR, filename), JSON.stringify(unusedTranslations, void 0, 2));
172
- }
173
- await import_node_fs.default.promises.writeFile(import_node_path.default.join(EXTRACT_DIR, filename), JSON.stringify(withoutUnnecessaryTranslations, void 0, 2));
224
+ await writeMissingTranslations(filename, missingTranslations, config);
225
+ await writeUnusedTranslations(filename, unusedTranslations, config);
226
+ await writeCleanTranslations(filename, cleanTranslations, config);
174
227
  }
175
228
  }
176
- };
177
- executeI18nCli();
229
+ }, "analyzeMissingAndUnusedTranslations");
230
+ var executeI18nCli = /* @__PURE__ */__name(async () => {
231
+ const config = getI18nConfig();
232
+ const pattern = argv.pattern || "src/**/*.{ts,tsx}";
233
+ const ignore = argv.ignore || ["src/**/*.test.{ts,tsx}", "src/**/*.d.ts"];
234
+ const ignoreTtossPackages = argv["ignore-ttoss-packages"];
235
+ const extractedDataAsString = await extractTranslationsFromSource(pattern, ignore);
236
+ const ttossExtractedTranslations = ignoreTtossPackages ? {} : await getTtossExtractedTranslations();
237
+ const finalExtractedData = (() => {
238
+ if (ignoreTtossPackages) {
239
+ return extractedDataAsString;
240
+ }
241
+ const parsedExtractedData = JSON.parse(extractedDataAsString);
242
+ const finalData = {
243
+ ...parsedExtractedData,
244
+ ...ttossExtractedTranslations
245
+ };
246
+ return JSON.stringify(finalData, void 0, 2);
247
+ })();
248
+ await writeFinalExtractedData(finalExtractedData, config);
249
+ if (argv["no-compile"]) {
250
+ return;
251
+ }
252
+ await compileTranslations(config);
253
+ await analyzeMissingAndUnusedTranslations(finalExtractedData, config);
254
+ }, "executeI18nCli");
255
+ // Annotate the CommonJS export names for ESM import in node:
256
+ 0 && (module.exports = {
257
+ analyzeMissingAndUnusedTranslations,
258
+ compareTranslations,
259
+ compileTranslations,
260
+ executeI18nCli,
261
+ extractTranslationsFromSource,
262
+ getI18nConfig,
263
+ getTtossExtractedTranslations,
264
+ writeCleanTranslations,
265
+ writeFinalExtractedData,
266
+ writeMissingTranslations,
267
+ writeUnusedTranslations
268
+ });
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@ttoss/i18n-cli",
3
- "version": "0.7.30",
3
+ "version": "0.7.32",
4
4
  "license": "MIT",
5
5
  "author": "ttoss",
6
6
  "contributors": [
@@ -24,16 +24,19 @@
24
24
  "minimist": "^1.2.8"
25
25
  },
26
26
  "devDependencies": {
27
+ "@types/jest": "^30.0.0",
27
28
  "@types/minimist": "^1.2.5",
29
+ "jest": "^30.0.4",
28
30
  "ts-node": "^10.9.2",
29
31
  "tsup": "^8.5.0",
30
- "@ttoss/config": "^1.35.5"
32
+ "@ttoss/config": "^1.35.6"
31
33
  },
32
34
  "publishConfig": {
33
35
  "access": "public",
34
36
  "provenance": true
35
37
  },
36
38
  "scripts": {
37
- "build-config": "tsup-node"
39
+ "build-config": "tsup-node",
40
+ "test": "jest --projects tests/unit"
38
41
  }
39
42
  }