@ttoss/i18n-cli 0.7.31 → 0.7.33
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 +165 -55
- package/bin/cli.js +3 -1
- package/dist/index.js +187 -96
- package/package.json +6 -3
package/README.md
CHANGED
|
@@ -1,12 +1,15 @@
|
|
|
1
1
|
# @ttoss/i18n-cli
|
|
2
2
|
|
|
3
|
-
|
|
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
|
-
|
|
5
|
+
## Key Features
|
|
6
6
|
|
|
7
|
-
|
|
8
|
-
|
|
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
|
-
##
|
|
20
|
+
## Quick Start
|
|
18
21
|
|
|
19
|
-
Add
|
|
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
|
|
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
|
-
###
|
|
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 --
|
|
89
|
+
pnpm i18n --pattern "src/**/*.{ts,tsx}" --ignore "**/*.test.*"
|
|
42
90
|
```
|
|
43
91
|
|
|
44
|
-
|
|
92
|
+
### Translation Workflow
|
|
45
93
|
|
|
46
|
-
-
|
|
47
|
-
|
|
48
|
-
|
|
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
|
-
"
|
|
54
|
-
"defaultMessage": "
|
|
55
|
-
"description": "
|
|
117
|
+
"2mAHlQ": {
|
|
118
|
+
"defaultMessage": "Welcome to our app!",
|
|
119
|
+
"description": "Main welcome message"
|
|
56
120
|
}
|
|
57
121
|
}
|
|
58
122
|
```
|
|
59
123
|
|
|
60
|
-
|
|
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
|
-
"
|
|
66
|
-
"defaultMessage": "
|
|
67
|
-
"description": "
|
|
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
|
-
|
|
153
|
+
**`i18n/lang/es.json`:**
|
|
73
154
|
|
|
74
|
-
|
|
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
|
|
167
|
+
pnpm i18n
|
|
78
168
|
```
|
|
79
169
|
|
|
80
|
-
|
|
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
|
-
|
|
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
|
-
|
|
178
|
+
**Base Language File (`i18n/lang/en.json`) - Auto-generated:**
|
|
94
179
|
|
|
95
180
|
```json
|
|
96
|
-
// i18n/compiled/en.json
|
|
97
181
|
{
|
|
98
|
-
"
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
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
|
-
|
|
200
|
+
**Compiled Output (`i18n/compiled/pt-BR.json`) - Auto-generated:**
|
|
108
201
|
|
|
109
202
|
```json
|
|
110
|
-
// i18n/compiled/pt-BR.json
|
|
111
203
|
{
|
|
112
|
-
"
|
|
204
|
+
"2mAHlQ": [
|
|
113
205
|
{
|
|
114
206
|
"type": 0,
|
|
115
|
-
"value": "
|
|
207
|
+
"value": "Bem-vindo ao nosso app!"
|
|
116
208
|
}
|
|
117
209
|
]
|
|
118
210
|
}
|
|
119
211
|
```
|
|
120
212
|
|
|
121
|
-
|
|
213
|
+
### Analysis Reports
|
|
122
214
|
|
|
123
|
-
|
|
215
|
+
The CLI automatically generates helpful reports to assist with translation management:
|
|
124
216
|
|
|
125
|
-
|
|
126
|
-
|
|
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
|
|
239
|
+
This eliminates manual copying of package translations and ensures consistency across your application.
|
package/bin/cli.js
CHANGED
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
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
}
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
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
|
-
|
|
56
|
-
|
|
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
|
|
59
|
-
const
|
|
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
|
-
|
|
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
|
-
|
|
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
|
|
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:
|
|
132
|
+
cwd: config.extractDir,
|
|
105
133
|
absolute: true
|
|
106
134
|
});
|
|
107
|
-
await import_node_fs.default.promises.mkdir(
|
|
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(
|
|
144
|
+
await import_node_fs.default.promises.writeFile(import_node_path.default.join(config.compileDir, filename), compiledDataAsString);
|
|
117
145
|
}
|
|
118
146
|
}
|
|
119
|
-
|
|
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.
|
|
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
|
|
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
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
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
|
|
160
|
-
|
|
161
|
-
|
|
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.
|
|
3
|
+
"version": "0.7.33",
|
|
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.
|
|
32
|
+
"@ttoss/config": "^1.35.7"
|
|
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
|
}
|