@zohodesk/client_build_tool 0.0.1-0.exp.0.0.3 → 0.0.1-0.exp.0.0.8
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/lib/schemas/defaultConfigValues.js +39 -19
- package/lib/schemas/defaultConfigValuesOnly.js +14 -8
- package/lib/shared/bundler/webpack/custom_plugins/I18nNumericIndexPlugin/CLAUDE.md +0 -0
- package/lib/shared/bundler/webpack/custom_plugins/I18nNumericIndexPlugin/I18nNumericIndexPlugin.js +382 -75
- package/lib/shared/bundler/webpack/custom_plugins/I18nSplitPlugin/I18nFilesEmitPlugin.js +66 -5
- package/lib/shared/bundler/webpack/custom_plugins/I18nSplitPlugin/optionsHandler.js +3 -0
- package/lib/shared/bundler/webpack/custom_plugins/getInitialI18nAssetsArrayStr.js +6 -1
- package/lib/shared/bundler/webpack/loaderConfigs/i18nIdReplaceLoaderConfig.js +92 -61
- package/lib/shared/bundler/webpack/loaders/i18nIdReplaceLoader.js +151 -123
- package/lib/shared/bundler/webpack/pluginConfigs/configI18nNumericIndexPlugin.js +74 -45
- package/lib/shared/bundler/webpack/pluginConfigs/configI18nSplitPlugin.js +4 -1
- package/lib/shared/bundler/webpack/utils/propertiesParser.js +103 -0
- package/npm-shrinkwrap.json +8086 -21
- package/package.json +1 -1
package/lib/shared/bundler/webpack/custom_plugins/I18nNumericIndexPlugin/I18nNumericIndexPlugin.js
CHANGED
|
@@ -9,20 +9,76 @@ const {
|
|
|
9
9
|
Compilation
|
|
10
10
|
} = require('webpack');
|
|
11
11
|
|
|
12
|
+
const {
|
|
13
|
+
parseProperties,
|
|
14
|
+
decodeUnicodeEscapes
|
|
15
|
+
} = require('../../utils/propertiesParser');
|
|
16
|
+
|
|
17
|
+
let createHashFunction;
|
|
18
|
+
|
|
12
19
|
const {
|
|
13
20
|
createHash
|
|
14
21
|
} = require("../I18nSplitPlugin/createHash");
|
|
15
22
|
|
|
23
|
+
createHashFunction = createHash;
|
|
16
24
|
const {
|
|
17
25
|
RawSource
|
|
18
26
|
} = sources;
|
|
19
27
|
const pluginName = 'I18nNumericIndexPlugin';
|
|
20
28
|
|
|
21
29
|
class I18nNumericIndexPlugin {
|
|
22
|
-
constructor(options) {
|
|
30
|
+
constructor(options = {}) {
|
|
23
31
|
this.options = options;
|
|
24
32
|
this.numericMap = null;
|
|
25
|
-
this.
|
|
33
|
+
this.stringPool = new Map(); // String interning for deduplication
|
|
34
|
+
// Default emitLiteralUnicode to true if not specified
|
|
35
|
+
|
|
36
|
+
if (typeof this.options.emitLiteralUnicode === 'undefined') {
|
|
37
|
+
this.options.emitLiteralUnicode = true;
|
|
38
|
+
} // Default to compact mode for memory optimization
|
|
39
|
+
|
|
40
|
+
|
|
41
|
+
if (typeof this.options.useCompactFormat === 'undefined') {
|
|
42
|
+
this.options.useCompactFormat = true;
|
|
43
|
+
}
|
|
44
|
+
} // String interning to reduce memory usage
|
|
45
|
+
|
|
46
|
+
|
|
47
|
+
intern(str) {
|
|
48
|
+
if (typeof str !== 'string') {
|
|
49
|
+
return str;
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
if (!this.stringPool.has(str)) {
|
|
53
|
+
this.stringPool.set(str, str);
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
return this.stringPool.get(str);
|
|
57
|
+
} // Generate compact numeric data structure - returns just an array of values
|
|
58
|
+
|
|
59
|
+
|
|
60
|
+
generateCompactNumericData(numericDataArray) {
|
|
61
|
+
if (!this.options.useCompactFormat) {
|
|
62
|
+
return numericDataArray;
|
|
63
|
+
} // Create a sparse array with only non-null values
|
|
64
|
+
|
|
65
|
+
|
|
66
|
+
const compactArray = [];
|
|
67
|
+
|
|
68
|
+
for (let i = 0; i < numericDataArray.length; i++) {
|
|
69
|
+
if (numericDataArray[i] !== null) {
|
|
70
|
+
compactArray[i] = this.intern(numericDataArray[i]);
|
|
71
|
+
}
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
return compactArray;
|
|
75
|
+
} // Compact serializer for JSON
|
|
76
|
+
|
|
77
|
+
|
|
78
|
+
compactSerialize(data) {
|
|
79
|
+
return JSON.stringify(data, null, 0).replace(/"(\d+)":/g, '$1:') // Remove quotes from numeric keys
|
|
80
|
+
.replace(/,null/g, ',0') // Replace null with 0 for smaller size
|
|
81
|
+
.replace(/null,/g, '0,'); // Replace null with 0 for smaller size
|
|
26
82
|
}
|
|
27
83
|
|
|
28
84
|
loadNumericMapOnce(compilation) {
|
|
@@ -30,34 +86,58 @@ class I18nNumericIndexPlugin {
|
|
|
30
86
|
return this.numericMap;
|
|
31
87
|
}
|
|
32
88
|
|
|
89
|
+
const numericMapPathOpt = this.options.numericMapPath;
|
|
90
|
+
const numericMapPath = numericMapPathOpt ? path.resolve(compilation.compiler.context, numericMapPathOpt) : null;
|
|
91
|
+
|
|
33
92
|
try {
|
|
34
|
-
if (
|
|
35
|
-
const fileContent = fs.readFileSync(
|
|
36
|
-
const parsedData = JSON.parse(fileContent);
|
|
93
|
+
if (numericMapPath && fs.existsSync(numericMapPath)) {
|
|
94
|
+
const fileContent = fs.readFileSync(numericMapPath, 'utf-8');
|
|
95
|
+
const parsedData = JSON.parse(fileContent); // Handle both old format (sortedOriginalKeys) and new format (originalKeyToNumericId)
|
|
37
96
|
|
|
38
97
|
if (parsedData && parsedData.sortedOriginalKeys && parsedData.totalKeys !== undefined) {
|
|
98
|
+
// Old format
|
|
39
99
|
this.numericMap = {
|
|
40
100
|
sortedOriginalKeys: parsedData.sortedOriginalKeys,
|
|
41
101
|
totalKeys: parsedData.totalKeys
|
|
42
102
|
};
|
|
43
|
-
|
|
103
|
+
return this.numericMap;
|
|
104
|
+
} else if (parsedData && parsedData.originalKeyToNumericId && parsedData.totalKeysInMap !== undefined) {
|
|
105
|
+
// New format - convert originalKeyToNumericId to sortedOriginalKeys
|
|
106
|
+
const keyToIdMap = parsedData.originalKeyToNumericId;
|
|
107
|
+
const sortedOriginalKeys = new Array(parsedData.totalKeysInMap); // Fill the array with keys at their numeric positions
|
|
108
|
+
|
|
109
|
+
Object.keys(keyToIdMap).forEach(key => {
|
|
110
|
+
const numericId = keyToIdMap[key];
|
|
111
|
+
|
|
112
|
+
if (numericId >= 0 && numericId < parsedData.totalKeysInMap) {
|
|
113
|
+
sortedOriginalKeys[numericId] = key;
|
|
114
|
+
}
|
|
115
|
+
});
|
|
116
|
+
this.numericMap = {
|
|
117
|
+
sortedOriginalKeys: sortedOriginalKeys,
|
|
118
|
+
totalKeys: parsedData.totalKeysInMap
|
|
119
|
+
};
|
|
44
120
|
return this.numericMap;
|
|
45
121
|
} else {
|
|
46
|
-
compilation.
|
|
122
|
+
compilation.warnings.push(new Error(`${pluginName}: numericMap file (${numericMapPath}) parsed but seems malformed. Expected 'sortedOriginalKeys' + 'totalKeys' or 'originalKeyToNumericId' + 'totalKeysInMap'. Using empty map.`));
|
|
47
123
|
this.numericMap = {
|
|
48
124
|
sortedOriginalKeys: [],
|
|
49
125
|
totalKeys: 0
|
|
50
126
|
};
|
|
51
127
|
}
|
|
52
128
|
} else {
|
|
53
|
-
|
|
129
|
+
if (numericMapPathOpt) {
|
|
130
|
+
// Only warn if a path was actually provided
|
|
131
|
+
compilation.warnings.push(new Error(`${pluginName}: numericMapPath (${numericMapPath}) not found. Using empty map.`));
|
|
132
|
+
}
|
|
133
|
+
|
|
54
134
|
this.numericMap = {
|
|
55
135
|
sortedOriginalKeys: [],
|
|
56
136
|
totalKeys: 0
|
|
57
137
|
};
|
|
58
138
|
}
|
|
59
139
|
} catch (err) {
|
|
60
|
-
compilation.
|
|
140
|
+
compilation.errors.push(new Error(`${pluginName}: Error loading/parsing numericMapPath ${numericMapPath}: ${err.message}`));
|
|
61
141
|
this.numericMap = {
|
|
62
142
|
sortedOriginalKeys: [],
|
|
63
143
|
totalKeys: 0
|
|
@@ -67,117 +147,344 @@ class I18nNumericIndexPlugin {
|
|
|
67
147
|
return this.numericMap;
|
|
68
148
|
}
|
|
69
149
|
|
|
70
|
-
|
|
71
|
-
const
|
|
150
|
+
loadJSResourceBaseFile(compilation) {
|
|
151
|
+
const compilerContext = compilation.compiler.context;
|
|
152
|
+
const jsResourcePathOpt = this.options.jsResourcePath;
|
|
153
|
+
|
|
154
|
+
if (!jsResourcePathOpt) {
|
|
155
|
+
return {};
|
|
156
|
+
}
|
|
157
|
+
|
|
158
|
+
const jsResourcePath = path.resolve(compilerContext, jsResourcePathOpt);
|
|
159
|
+
|
|
160
|
+
try {
|
|
161
|
+
if (fs.existsSync(jsResourcePath)) {
|
|
162
|
+
const fileContent = fs.readFileSync(jsResourcePath, 'utf-8');
|
|
163
|
+
return parseProperties(fileContent);
|
|
164
|
+
} else {
|
|
165
|
+
compilation.warnings.push(new Error(`${pluginName}: JS Resource base file not found: ${jsResourcePath}`));
|
|
166
|
+
return {};
|
|
167
|
+
}
|
|
168
|
+
} catch (err) {
|
|
169
|
+
compilation.errors.push(new Error(`${pluginName}: Error loading JS Resource base file ${jsResourcePath}: ${err.message}`));
|
|
170
|
+
return {};
|
|
171
|
+
}
|
|
172
|
+
}
|
|
173
|
+
|
|
174
|
+
loadAllI18nData(compilation) {
|
|
175
|
+
if (this.options.allI18nObject && this.options.locales) {
|
|
176
|
+
return {
|
|
177
|
+
allI18nObject: this.options.allI18nObject,
|
|
178
|
+
locales: this.options.locales,
|
|
179
|
+
jsResourceBase: this.loadJSResourceBaseFile(compilation)
|
|
180
|
+
};
|
|
181
|
+
}
|
|
182
|
+
|
|
183
|
+
const allI18nObject = {};
|
|
184
|
+
const discoveredLocales = new Set();
|
|
185
|
+
const compilerContext = compilation.compiler.context;
|
|
186
|
+
const propertiesFolderPathOpt = this.options.propertiesFolderPath;
|
|
187
|
+
const jsResourcePathOpt = this.options.jsResourcePath;
|
|
188
|
+
|
|
189
|
+
if (!propertiesFolderPathOpt) {
|
|
190
|
+
compilation.errors.push(new Error(`${pluginName}: 'propertiesFolderPath' option is missing, cannot load translations.`));
|
|
191
|
+
return {
|
|
192
|
+
allI18nObject,
|
|
193
|
+
locales: [],
|
|
194
|
+
jsResourceBase: {}
|
|
195
|
+
};
|
|
196
|
+
}
|
|
197
|
+
|
|
198
|
+
const propertiesFolderPath = path.resolve(compilerContext, propertiesFolderPathOpt);
|
|
199
|
+
const baseJsResourcePath = jsResourcePathOpt ? path.resolve(compilerContext, jsResourcePathOpt) : null;
|
|
200
|
+
|
|
201
|
+
if (!fs.existsSync(propertiesFolderPath)) {
|
|
202
|
+
compilation.errors.push(new Error(`${pluginName}: propertiesFolderPath does not exist: ${propertiesFolderPath}`));
|
|
203
|
+
return {
|
|
204
|
+
allI18nObject,
|
|
205
|
+
locales: [],
|
|
206
|
+
jsResourceBase: {}
|
|
207
|
+
};
|
|
208
|
+
}
|
|
209
|
+
|
|
210
|
+
const baseFileName = baseJsResourcePath ? path.basename(baseJsResourcePath, path.extname(baseJsResourcePath)) : null;
|
|
211
|
+
const baseExtension = baseJsResourcePath ? path.extname(baseJsResourcePath) : '.properties';
|
|
212
|
+
|
|
213
|
+
try {
|
|
214
|
+
const files = fs.readdirSync(propertiesFolderPath);
|
|
215
|
+
files.forEach(file => {
|
|
216
|
+
const filePath = path.join(propertiesFolderPath, file);
|
|
217
|
+
|
|
218
|
+
if (fs.statSync(filePath).isFile()) {
|
|
219
|
+
let locale = null;
|
|
220
|
+
const ext = path.extname(file);
|
|
221
|
+
const nameWithoutExt = path.basename(file, ext);
|
|
222
|
+
|
|
223
|
+
if (baseFileName && nameWithoutExt.startsWith(baseFileName + '_')) {
|
|
224
|
+
locale = nameWithoutExt.substring((baseFileName + '_').length);
|
|
225
|
+
} else if (baseFileName && nameWithoutExt === baseFileName) {
|
|
226
|
+
locale = this.options.defaultLocaleForBaseFile || 'en';
|
|
227
|
+
} else if (!baseFileName && nameWithoutExt.includes('_')) {
|
|
228
|
+
// Generic pattern: messages_fr_CA.properties -> fr_CA
|
|
229
|
+
const parts = nameWithoutExt.split('_');
|
|
230
|
+
|
|
231
|
+
if (parts.length > 1) {
|
|
232
|
+
locale = parts.slice(1).join('_');
|
|
233
|
+
}
|
|
234
|
+
} else if (!baseFileName && !nameWithoutExt.includes('_') && ext.toLowerCase() === baseExtension.toLowerCase()) {
|
|
235
|
+
// Fallback for files like 'fr.properties' if no baseFileName is defined
|
|
236
|
+
locale = nameWithoutExt;
|
|
237
|
+
}
|
|
238
|
+
|
|
239
|
+
if (locale && ext.toLowerCase() === baseExtension.toLowerCase()) {
|
|
240
|
+
try {
|
|
241
|
+
const fileContent = fs.readFileSync(filePath, 'utf-8');
|
|
242
|
+
allI18nObject[locale] = parseProperties(fileContent);
|
|
243
|
+
discoveredLocales.add(locale);
|
|
244
|
+
} catch (readErr) {
|
|
245
|
+
compilation.errors.push(new Error(`${pluginName}: Error reading/parsing properties file ${filePath}: ${readErr.message}`));
|
|
246
|
+
}
|
|
247
|
+
}
|
|
248
|
+
}
|
|
249
|
+
});
|
|
250
|
+
} catch (err) {
|
|
251
|
+
compilation.errors.push(new Error(`${pluginName}: Error reading propertiesFolderPath ${propertiesFolderPath}: ${err.message}`));
|
|
252
|
+
} // Load JS Resource base file separately
|
|
253
|
+
|
|
254
|
+
|
|
255
|
+
const jsResourceBase = this.loadJSResourceBaseFile(compilation);
|
|
256
|
+
return {
|
|
257
|
+
allI18nObject,
|
|
258
|
+
locales: Array.from(discoveredLocales),
|
|
259
|
+
jsResourceBase
|
|
260
|
+
};
|
|
261
|
+
}
|
|
262
|
+
|
|
263
|
+
validateAndSanitizeData(data, chunkType, locale, compilation) {
|
|
264
|
+
// Placeholder for any future validation or sanitization logic
|
|
265
|
+
return data;
|
|
266
|
+
}
|
|
267
|
+
|
|
268
|
+
emitChunkFile(compilation, filenameTemplate, locale, fileContentData, jsonpFunc, chunkType) {
|
|
269
|
+
const dataToStringify = this.validateAndSanitizeData(fileContentData, chunkType, locale, compilation);
|
|
270
|
+
let stringifiedData;
|
|
271
|
+
|
|
272
|
+
try {
|
|
273
|
+
// Use compact serializer for smaller output
|
|
274
|
+
if (this.options.useCompactFormat) {
|
|
275
|
+
stringifiedData = this.compactSerialize(dataToStringify);
|
|
276
|
+
} else {
|
|
277
|
+
stringifiedData = JSON.stringify(dataToStringify, null, 0);
|
|
278
|
+
} // If emitLiteralUnicode is true (default), convert \uXXXX sequences back to actual Unicode characters
|
|
279
|
+
// This ensures the output file contains literal UTF-8 characters if desired.
|
|
280
|
+
|
|
281
|
+
|
|
282
|
+
if (this.options.emitLiteralUnicode) {
|
|
283
|
+
stringifiedData = decodeUnicodeEscapes(stringifiedData);
|
|
284
|
+
}
|
|
285
|
+
} catch (e) {
|
|
286
|
+
compilation.errors.push(new Error(`${pluginName}: Failed to stringify or post-process data for ${chunkType} chunk (${locale}): ${e.message}`));
|
|
287
|
+
return;
|
|
288
|
+
} // Create content in the format: window.loadI18nChunk({data})
|
|
289
|
+
|
|
290
|
+
|
|
291
|
+
const fileContent = `${jsonpFunc}(${stringifiedData});`;
|
|
72
292
|
const source = new RawSource(fileContent);
|
|
73
|
-
const actualContentHash =
|
|
74
|
-
//
|
|
293
|
+
const actualContentHash = createHashFunction({
|
|
294
|
+
// Ensure createHashFunction is loaded
|
|
75
295
|
outputOptions: compilation.outputOptions,
|
|
76
|
-
content: fileContent
|
|
296
|
+
content: fileContent // Hash is based on the final content
|
|
297
|
+
|
|
77
298
|
});
|
|
78
|
-
let processedFilenameTemplate =
|
|
299
|
+
let processedFilenameTemplate = filenameTemplate.replace(/\[locale\]/g, locale).replace(/\[name\]/g, locale).replace(/\[id\]/g, locale);
|
|
79
300
|
const finalFileName = compilation.getAssetPath(processedFilenameTemplate, {
|
|
80
301
|
locale: locale,
|
|
81
302
|
contentHash: actualContentHash,
|
|
82
303
|
hash: compilation.hash,
|
|
304
|
+
// Webpack compilation hash
|
|
83
305
|
chunk: {
|
|
84
|
-
|
|
85
|
-
|
|
306
|
+
// Mock chunk object for placeholders
|
|
307
|
+
id: `i18n-${chunkType}-${locale}`,
|
|
308
|
+
name: `${chunkType}-${locale}`,
|
|
86
309
|
hash: actualContentHash,
|
|
310
|
+
// Chunk-specific hash
|
|
87
311
|
contentHash: {
|
|
88
|
-
[this.options.moduleType || 'i18n/
|
|
312
|
+
[this.options.moduleType || 'i18n/data-extract']: actualContentHash
|
|
89
313
|
}
|
|
90
314
|
},
|
|
91
|
-
contentHashType: this.options.moduleType || 'i18n/
|
|
315
|
+
contentHashType: this.options.moduleType || 'i18n/data-extract'
|
|
92
316
|
});
|
|
93
317
|
compilation.emitAsset(finalFileName, source);
|
|
94
|
-
compilation.logger.info(`${pluginName}: Emitted ${typeSuffix} i18n file for locale ${locale} to ${finalFileName}`);
|
|
95
318
|
}
|
|
96
319
|
|
|
97
320
|
apply(compiler) {
|
|
98
321
|
compiler.hooks.thisCompilation.tap(pluginName, compilation => {
|
|
99
322
|
compilation.hooks.processAssets.tapAsync({
|
|
100
323
|
name: pluginName,
|
|
101
|
-
stage: Compilation.PROCESS_ASSETS_STAGE_SUMMARIZE
|
|
102
|
-
|
|
324
|
+
stage: Compilation.PROCESS_ASSETS_STAGE_SUMMARIZE // Or a later stage if needed
|
|
325
|
+
|
|
326
|
+
}, (unusedAssets, callback) => {
|
|
327
|
+
if (!this.options.enable) {
|
|
328
|
+
return callback();
|
|
329
|
+
}
|
|
330
|
+
|
|
103
331
|
const {
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
332
|
+
allI18nObject,
|
|
333
|
+
locales,
|
|
334
|
+
jsResourceBase
|
|
335
|
+
} = this.loadAllI18nData(compilation);
|
|
336
|
+
|
|
337
|
+
if (!locales || locales.length === 0) {
|
|
338
|
+
if (this.options.propertiesFolderPath) {
|
|
339
|
+
// Only warn if path was given
|
|
340
|
+
compilation.warnings.push(new Error(`${pluginName}: No locales found or no translation data loaded from ${this.options.propertiesFolderPath}.`));
|
|
341
|
+
}
|
|
107
342
|
|
|
108
|
-
|
|
109
|
-
compilation.logger.info(`${pluginName}: No numeric keys to process. Skipping numeric file emission.`);
|
|
343
|
+
return callback();
|
|
110
344
|
}
|
|
111
345
|
|
|
112
|
-
const
|
|
346
|
+
const numericMapData = this.loadNumericMapOnce(compilation);
|
|
347
|
+
const sortedOriginalKeys = numericMapData.sortedOriginalKeys || [];
|
|
348
|
+
const totalKeys = numericMapData.totalKeys || 0;
|
|
113
349
|
const globallyUsedCommentKeys = new Set();
|
|
114
350
|
|
|
115
351
|
for (const module of compilation.modules) {
|
|
116
|
-
if (module.buildInfo) {
|
|
117
|
-
|
|
118
|
-
module.buildInfo.loaderIdentifiedLiteralI18nKeys.forEach(key => globallyUsedLiteralKeys.add(key));
|
|
119
|
-
}
|
|
120
|
-
|
|
121
|
-
if (Array.isArray(module.buildInfo.loaderIdentifiedCommentI18nKeys)) {
|
|
122
|
-
module.buildInfo.loaderIdentifiedCommentI18nKeys.forEach(key => globallyUsedCommentKeys.add(key));
|
|
123
|
-
}
|
|
352
|
+
if (module.buildInfo && Array.isArray(module.buildInfo.loaderIdentifiedCommentI18nKeys)) {
|
|
353
|
+
module.buildInfo.loaderIdentifiedCommentI18nKeys.forEach(key => globallyUsedCommentKeys.add(key));
|
|
124
354
|
}
|
|
125
355
|
}
|
|
126
356
|
|
|
127
|
-
|
|
128
|
-
const
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
numericJsonpFunc,
|
|
134
|
-
dynamicJsonpFunc
|
|
135
|
-
} = this.options;
|
|
136
|
-
|
|
137
|
-
if (!locales || !allI18nObject || !numericFilenameTemplate || !dynamicFilenameTemplate || !numericJsonpFunc || !dynamicJsonpFunc) {
|
|
138
|
-
compilation.errors.push(new Error(`${pluginName}: Missing some required options (locales, allI18nObject, filename templates, jsonp funcs).`));
|
|
139
|
-
return callback();
|
|
357
|
+
const numericFilenameTemplate = this.options.numericFilenameTemplate;
|
|
358
|
+
const dynamicFilenameTemplate = this.options.dynamicFilenameTemplate;
|
|
359
|
+
const jsonpFunction = this.options.jsonpFunc || 'window.loadI18nChunk';
|
|
360
|
+
|
|
361
|
+
if (!numericFilenameTemplate) {
|
|
362
|
+
compilation.errors.push(new Error(`${pluginName}: Missing required option 'numericFilenameTemplate' in plugin options.`)); // return callback(); // Allow processing other chunks if one template is missing
|
|
140
363
|
}
|
|
141
364
|
|
|
142
|
-
if (
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
365
|
+
if (!dynamicFilenameTemplate) {
|
|
366
|
+
compilation.errors.push(new Error(`${pluginName}: Missing required option 'dynamicFilenameTemplate' in plugin options.`)); // return callback();
|
|
367
|
+
}
|
|
368
|
+
|
|
369
|
+
if (typeof numericFilenameTemplate === 'string' && !numericFilenameTemplate.includes('[locale]') && !numericFilenameTemplate.includes('[name]') && !numericFilenameTemplate.includes('[id]')) {
|
|
370
|
+
compilation.warnings.push(new Error( // Changed to warning as it might be intentional for a single combined file not per-locale
|
|
371
|
+
`${pluginName}: The 'numericFilenameTemplate' ("${numericFilenameTemplate}") ` + `does not include '[locale]', '[name]', or '[id]'. All locales will overwrite the same file if multiple locales exist.`));
|
|
372
|
+
}
|
|
373
|
+
|
|
374
|
+
if (typeof dynamicFilenameTemplate === 'string' && !dynamicFilenameTemplate.includes('[locale]') && !dynamicFilenameTemplate.includes('[name]') && !dynamicFilenameTemplate.includes('[id]')) {
|
|
375
|
+
compilation.warnings.push(new Error(`${pluginName}: The 'dynamicFilenameTemplate' ("${dynamicFilenameTemplate}") ` + `does not include '[locale]', '[name]', or '[id]'. All locales will overwrite the same file if multiple locales exist.`));
|
|
376
|
+
}
|
|
377
|
+
|
|
378
|
+
const numericKeysSet = new Set(sortedOriginalKeys); // Use JS resource base file as the master reference for keys
|
|
379
|
+
|
|
380
|
+
const jsResourceBaseTranslations = jsResourceBase || {}; // Get English translations for fallback
|
|
381
|
+
|
|
382
|
+
const englishTranslations = allI18nObject.en || allI18nObject[this.options.defaultLocaleForBaseFile || 'en'] || jsResourceBaseTranslations; // Log base file and numeric map stats
|
|
146
383
|
|
|
384
|
+
console.log(`${pluginName}: === DEBUGGING STATS ===`);
|
|
385
|
+
console.log(`${pluginName}: JS Resource base file keys count: ${Object.keys(jsResourceBaseTranslations).length}`);
|
|
386
|
+
console.log(`${pluginName}: English fallback keys count: ${Object.keys(englishTranslations).length}`);
|
|
387
|
+
console.log(`${pluginName}: Numeric map total keys: ${totalKeys}`);
|
|
388
|
+
console.log(`${pluginName}: Numeric map actual keys: ${sortedOriginalKeys.filter(key => key !== undefined).length}`);
|
|
389
|
+
console.log(`${pluginName}: Global comment keys count: ${globallyUsedCommentKeys.size}`);
|
|
390
|
+
|
|
391
|
+
for (const locale of locales) {
|
|
392
|
+
const localeTranslations = allI18nObject[locale] || {};
|
|
393
|
+
const numericDataForLocale = []; // Fill numericDataForLocale based on sortedOriginalKeys
|
|
394
|
+
|
|
395
|
+
if (totalKeys > 0 && sortedOriginalKeys.length > 0) {
|
|
147
396
|
for (let i = 0; i < totalKeys; i++) {
|
|
148
|
-
|
|
397
|
+
// Iterate up to totalKeys (max expected length)
|
|
398
|
+
const originalKey = sortedOriginalKeys[i]; // Get key if available
|
|
399
|
+
// If originalKey is undefined (i < sortedOriginalKeys.length but originalKey is not there due to sparse array or shorter sortedKeys)
|
|
400
|
+
// or if the translation is missing, use English fallback if available, otherwise null.
|
|
149
401
|
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
402
|
+
let value = null;
|
|
403
|
+
|
|
404
|
+
if (originalKey !== undefined && jsResourceBaseTranslations[originalKey] !== undefined) {
|
|
405
|
+
if (localeTranslations[originalKey] !== undefined) {
|
|
406
|
+
value = this.intern(localeTranslations[originalKey]);
|
|
407
|
+
} else if (englishTranslations[originalKey] !== undefined) {
|
|
408
|
+
// Fallback to English value for missing key
|
|
409
|
+
value = this.intern(englishTranslations[originalKey]);
|
|
410
|
+
}
|
|
154
411
|
}
|
|
412
|
+
|
|
413
|
+
numericDataForLocale.push(value);
|
|
155
414
|
}
|
|
415
|
+
} // Convert to compact format
|
|
156
416
|
|
|
157
|
-
this.emitFile(compilation, numericFilenameTemplate, locale, orderedNumericTranslations, numericJsonpFunc, 'numeric');
|
|
158
|
-
}
|
|
159
|
-
}
|
|
160
417
|
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
|
|
418
|
+
const compactNumericData = this.generateCompactNumericData(numericDataForLocale);
|
|
419
|
+
const dynamicDataForLocale = Object.create(null); // Use null prototype for cleaner object
|
|
420
|
+
// Process globally used comment keys first (only if they exist in JS resource base)
|
|
421
|
+
|
|
422
|
+
globallyUsedCommentKeys.forEach(originalKey => {
|
|
423
|
+
if (!numericKeysSet.has(originalKey) && jsResourceBaseTranslations[originalKey] !== undefined) {
|
|
424
|
+
let value = null;
|
|
425
|
+
|
|
166
426
|
if (localeTranslations[originalKey] !== undefined) {
|
|
167
|
-
|
|
168
|
-
} else {
|
|
169
|
-
|
|
427
|
+
value = this.intern(localeTranslations[originalKey]);
|
|
428
|
+
} else if (englishTranslations[originalKey] !== undefined) {
|
|
429
|
+
// Fallback to English value for missing key
|
|
430
|
+
value = this.intern(englishTranslations[originalKey]);
|
|
170
431
|
}
|
|
171
|
-
});
|
|
172
432
|
|
|
173
|
-
|
|
174
|
-
this.emitFile(compilation, dynamicFilenameTemplate, locale, dynamicKeyTranslations, dynamicJsonpFunc, 'dynamic');
|
|
433
|
+
dynamicDataForLocale[originalKey] = value;
|
|
175
434
|
}
|
|
435
|
+
}); // Process remaining keys from JS resource base (keys in JS resource but not in numeric mapping)
|
|
436
|
+
// Only include keys that exist in the JS resource base file
|
|
437
|
+
|
|
438
|
+
Object.keys(jsResourceBaseTranslations).forEach(originalKey => {
|
|
439
|
+
// Add if not in numeric set AND not already added from globallyUsedCommentKeys
|
|
440
|
+
if (!numericKeysSet.has(originalKey) && !Object.prototype.hasOwnProperty.call(dynamicDataForLocale, originalKey)) {
|
|
441
|
+
let value = null;
|
|
442
|
+
|
|
443
|
+
if (localeTranslations[originalKey] !== undefined) {
|
|
444
|
+
value = this.intern(localeTranslations[originalKey]);
|
|
445
|
+
} else if (englishTranslations[originalKey] !== undefined) {
|
|
446
|
+
// Use English fallback for missing key
|
|
447
|
+
value = this.intern(englishTranslations[originalKey]);
|
|
448
|
+
}
|
|
449
|
+
|
|
450
|
+
dynamicDataForLocale[originalKey] = value;
|
|
451
|
+
}
|
|
452
|
+
}); // Log per-locale stats
|
|
453
|
+
|
|
454
|
+
const numericKeysWithValues = numericDataForLocale.filter(v => v !== null).length;
|
|
455
|
+
const numericKeysWithFallbacks = numericDataForLocale.filter((v, i) => {
|
|
456
|
+
const originalKey = sortedOriginalKeys[i];
|
|
457
|
+
return v !== null && originalKey && localeTranslations[originalKey] === undefined && englishTranslations[originalKey] !== undefined;
|
|
458
|
+
}).length;
|
|
459
|
+
const dynamicKeysCount = Object.keys(dynamicDataForLocale).length;
|
|
460
|
+
const dynamicKeysWithFallbacks = Object.keys(dynamicDataForLocale).filter(key => localeTranslations[key] === undefined && englishTranslations[key] !== undefined).length; // Count how many locale keys are filtered out (not in JS resource base)
|
|
461
|
+
|
|
462
|
+
const localeKeysCount = Object.keys(localeTranslations).length;
|
|
463
|
+
const localeKeysInBase = Object.keys(localeTranslations).filter(key => jsResourceBaseTranslations[key] !== undefined).length;
|
|
464
|
+
const localeKeysFiltered = localeKeysCount - localeKeysInBase;
|
|
465
|
+
console.log(`${pluginName}: ${locale} - Total keys in locale file: ${localeKeysCount}`);
|
|
466
|
+
console.log(`${pluginName}: ${locale} - Keys from locale matching JS resource base: ${localeKeysInBase}`);
|
|
467
|
+
console.log(`${pluginName}: ${locale} - Keys filtered out (not in base): ${localeKeysFiltered}`);
|
|
468
|
+
console.log(`${pluginName}: ${locale} - Numeric: ${numericKeysWithValues}/${numericDataForLocale.length} (${numericKeysWithFallbacks} fallbacks)`);
|
|
469
|
+
console.log(`${pluginName}: ${locale} - Dynamic: ${dynamicKeysCount} (${dynamicKeysWithFallbacks} fallbacks)`);
|
|
470
|
+
console.log(`${pluginName}: ${locale} - Total emitted: ${numericKeysWithValues + dynamicKeysCount}`);
|
|
471
|
+
console.log(`${pluginName}: ---`); // Keep original debug logging for compatibility
|
|
472
|
+
|
|
473
|
+
if (this.options.logKeyCounts && this.options.isDevelopment && locale === (this.options.logKeyCountsForLocale || 'en_US')) {
|
|
474
|
+
console.log(`${pluginName}: ${locale} - Numeric: ${numericKeysWithValues}/${numericDataForLocale.length}, Dynamic: ${Object.keys(dynamicDataForLocale).length}`);
|
|
176
475
|
}
|
|
177
|
-
} else {
|
|
178
|
-
compilation.logger.info(`${pluginName}: No globally used comment keys found. Skipping dynamic file emission.`);
|
|
179
|
-
}
|
|
180
476
|
|
|
477
|
+
if (numericFilenameTemplate && (this.options.useCompactFormat ? compactNumericData.length > 0 : numericDataForLocale.length > 0)) {
|
|
478
|
+
this.emitChunkFile(compilation, numericFilenameTemplate, locale, this.options.useCompactFormat ? compactNumericData : numericDataForLocale, jsonpFunction, 'numeric');
|
|
479
|
+
}
|
|
480
|
+
|
|
481
|
+
if (dynamicFilenameTemplate && Object.keys(dynamicDataForLocale).length > 0) {
|
|
482
|
+
this.emitChunkFile(compilation, dynamicFilenameTemplate, locale, dynamicDataForLocale, jsonpFunction, 'dynamic');
|
|
483
|
+
}
|
|
484
|
+
} // Clear string pool after processing to free memory
|
|
485
|
+
|
|
486
|
+
|
|
487
|
+
this.stringPool.clear();
|
|
181
488
|
callback();
|
|
182
489
|
});
|
|
183
490
|
});
|
|
@@ -7,6 +7,10 @@ exports.I18nFilesEmitPlugin = void 0;
|
|
|
7
7
|
|
|
8
8
|
var _webpack = require("webpack");
|
|
9
9
|
|
|
10
|
+
var _fs = _interopRequireDefault(require("fs"));
|
|
11
|
+
|
|
12
|
+
var _path = _interopRequireDefault(require("path"));
|
|
13
|
+
|
|
10
14
|
var _createHash = require("./createHash");
|
|
11
15
|
|
|
12
16
|
var _pathCreator = require("./pathCreator");
|
|
@@ -15,6 +19,8 @@ var _propertiesUtils = require("./utils/propertiesUtils");
|
|
|
15
19
|
|
|
16
20
|
var _LocaleChunkAssetsStore = require("./LocaleChunkAssetsStore");
|
|
17
21
|
|
|
22
|
+
function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { default: obj }; }
|
|
23
|
+
|
|
18
24
|
/* eslint-disable no-restricted-syntax */
|
|
19
25
|
const pluginName = 'I18nFilesEmitPlugin';
|
|
20
26
|
const {
|
|
@@ -23,18 +29,57 @@ const {
|
|
|
23
29
|
|
|
24
30
|
class I18nFilesEmitPlugin {
|
|
25
31
|
constructor(options) {
|
|
26
|
-
this.options = options;
|
|
32
|
+
this.options = options;
|
|
33
|
+
this.numericIdMap = null; // this.options = {
|
|
27
34
|
// locales: options.locales,
|
|
28
35
|
// chunkFilename: options.chunkFilename,
|
|
29
36
|
// filename: options.filename,
|
|
30
37
|
// allI18nObject: options.allI18nObject,
|
|
31
|
-
// jsonpFunc: options.jsonpFunc
|
|
38
|
+
// jsonpFunc: options.jsonpFunc,
|
|
39
|
+
// useNumericIndexing: options.useNumericIndexing, // NEW OPTION
|
|
40
|
+
// numericMapPath: options.numericMapPath // NEW OPTION
|
|
32
41
|
// };
|
|
33
42
|
}
|
|
34
43
|
|
|
44
|
+
loadNumericIdMap(compilation) {
|
|
45
|
+
if (this.numericIdMap || !this.options.useNumericIndexing || !this.options.numericMapPath) {
|
|
46
|
+
return this.numericIdMap;
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
const absoluteMapPath = _path.default.isAbsolute(this.options.numericMapPath) ? this.options.numericMapPath : _path.default.resolve(compilation.compiler.context, this.options.numericMapPath);
|
|
50
|
+
|
|
51
|
+
try {
|
|
52
|
+
if (_fs.default.existsSync(absoluteMapPath)) {
|
|
53
|
+
const fileContent = _fs.default.readFileSync(absoluteMapPath, 'utf-8');
|
|
54
|
+
|
|
55
|
+
const parsedData = JSON.parse(fileContent);
|
|
56
|
+
|
|
57
|
+
if (parsedData && parsedData.originalKeyToNumericId && typeof parsedData.originalKeyToNumericId === 'object') {
|
|
58
|
+
this.numericIdMap = parsedData.originalKeyToNumericId;
|
|
59
|
+
} else {
|
|
60
|
+
compilation.warnings.push(new Error(`${pluginName}: Invalid numeric map format in ${absoluteMapPath}`));
|
|
61
|
+
this.numericIdMap = {};
|
|
62
|
+
}
|
|
63
|
+
} else {
|
|
64
|
+
compilation.warnings.push(new Error(`${pluginName}: Numeric map file not found: ${absoluteMapPath}`));
|
|
65
|
+
this.numericIdMap = {};
|
|
66
|
+
}
|
|
67
|
+
} catch (err) {
|
|
68
|
+
compilation.errors.push(new Error(`${pluginName}: Error loading numeric map from ${absoluteMapPath}: ${err.message}`));
|
|
69
|
+
this.numericIdMap = {};
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
return this.numericIdMap;
|
|
73
|
+
}
|
|
74
|
+
|
|
35
75
|
apply(compiler) {
|
|
36
76
|
compiler.hooks.thisCompilation.tap(pluginName, compilation => {
|
|
37
|
-
//
|
|
77
|
+
// Load numeric map if needed
|
|
78
|
+
if (this.options.useNumericIndexing) {
|
|
79
|
+
this.loadNumericIdMap(compilation);
|
|
80
|
+
} // Get store for cache
|
|
81
|
+
|
|
82
|
+
|
|
38
83
|
this.store = (0, _LocaleChunkAssetsStore.getLocaleChunkAssetsStore)(compilation);
|
|
39
84
|
const i18nStore = this.store;
|
|
40
85
|
compilation.hooks.beforeHash.tap(pluginName, () => {
|
|
@@ -150,12 +195,28 @@ class I18nFilesEmitPlugin {
|
|
|
150
195
|
|
|
151
196
|
getI18nContentForkeys(i18nKeys, locale) {
|
|
152
197
|
const {
|
|
153
|
-
allI18nObject
|
|
198
|
+
allI18nObject,
|
|
199
|
+
useNumericIndexing
|
|
154
200
|
} = this.options;
|
|
155
201
|
const data = {};
|
|
156
202
|
|
|
157
203
|
for (const key of i18nKeys) {
|
|
158
|
-
|
|
204
|
+
const value = allI18nObject[locale][key];
|
|
205
|
+
|
|
206
|
+
if (useNumericIndexing && this.numericIdMap) {
|
|
207
|
+
// Use numeric ID as key if available, otherwise keep original key
|
|
208
|
+
const numericId = this.numericIdMap[key];
|
|
209
|
+
|
|
210
|
+
if (numericId !== undefined) {
|
|
211
|
+
data[numericId] = value;
|
|
212
|
+
} else {
|
|
213
|
+
// Keep original key if no numeric mapping exists
|
|
214
|
+
data[key] = value;
|
|
215
|
+
}
|
|
216
|
+
} else {
|
|
217
|
+
// Default behavior: use original key
|
|
218
|
+
data[key] = value;
|
|
219
|
+
}
|
|
159
220
|
}
|
|
160
221
|
|
|
161
222
|
return data;
|