@zohodesk/client_build_tool 0.0.1-0.exp.0.0.8 → 0.0.1-0.exp.1.0.3
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 +0 -10
- package/README.md +0 -10
- package/lib/schemas/defaultConfigValues.js +14 -63
- package/lib/schemas/defaultConfigValuesOnly.js +6 -10
- package/lib/shared/babel/getBabelPlugin.js +4 -9
- package/lib/shared/babel/runBabelForTsFile.js +1 -1
- package/lib/shared/bundler/webpack/custom_plugins/I18nNumericIndexPlugin/I18nNumericIndexHtmlInjectorPlugin.js +14 -12
- package/lib/shared/bundler/webpack/custom_plugins/I18nNumericIndexPlugin/I18nNumericIndexPlugin.js +90 -426
- package/lib/shared/bundler/webpack/custom_plugins/I18nNumericIndexPlugin/I18nNumericIndexPlugin_simplified.js +129 -0
- package/lib/shared/bundler/webpack/custom_plugins/I18nNumericIndexPlugin/i18nDataLoader.js +134 -0
- package/lib/shared/bundler/webpack/custom_plugins/I18nNumericIndexPlugin/utils/i18nDataLoader.js +113 -0
- package/lib/shared/bundler/webpack/custom_plugins/I18nSplitPlugin/I18nFilesEmitPlugin.js +5 -66
- package/lib/shared/bundler/webpack/custom_plugins/I18nSplitPlugin/optionsHandler.js +0 -3
- package/lib/shared/bundler/webpack/custom_plugins/I18nSplitPlugin/utils/collectAstKeys.js +4 -6
- package/lib/shared/bundler/webpack/custom_plugins/getInitialI18nAssetsArrayStr.js +1 -6
- package/lib/shared/bundler/webpack/jsLoaders.js +12 -7
- package/lib/shared/bundler/webpack/loaderConfigs/i18nIdReplaceLoaderConfig.js +37 -88
- package/lib/shared/bundler/webpack/loaders/i18nIdReplaceLoader.js +67 -191
- package/lib/shared/bundler/webpack/pluginConfigs/configI18nNumericIndexPlugin.js +27 -99
- package/lib/shared/bundler/webpack/pluginConfigs/configI18nSplitPlugin.js +1 -4
- package/lib/shared/bundler/webpack/plugins.js +3 -20
- package/lib/shared/bundler/webpack/utils/i18n/collectAstKeys.js +96 -0
- package/lib/shared/bundler/webpack/utils/propertiesParser.js +1 -1
- package/lib/shared/server/mockApiHandler.js +0 -7
- package/npm-shrinkwrap.json +32 -8225
- package/package.json +5 -6
- package/lib/shared/bundler/webpack/common/hashUtils.js +0 -20
- package/lib/shared/bundler/webpack/custom_plugins/I18nNumericIndexPlugin/CLAUDE.md +0 -0
package/lib/shared/bundler/webpack/custom_plugins/I18nNumericIndexPlugin/I18nNumericIndexPlugin.js
CHANGED
|
@@ -1,26 +1,22 @@
|
|
|
1
1
|
"use strict";
|
|
2
2
|
|
|
3
|
-
const fs = require('fs');
|
|
4
|
-
|
|
5
3
|
const path = require('path');
|
|
6
4
|
|
|
7
5
|
const {
|
|
8
6
|
sources,
|
|
9
|
-
Compilation
|
|
7
|
+
Compilation,
|
|
8
|
+
util
|
|
10
9
|
} = require('webpack');
|
|
11
10
|
|
|
12
11
|
const {
|
|
13
|
-
parseProperties,
|
|
14
12
|
decodeUnicodeEscapes
|
|
15
13
|
} = require('../../utils/propertiesParser');
|
|
16
14
|
|
|
17
|
-
let createHashFunction;
|
|
18
|
-
|
|
19
15
|
const {
|
|
20
|
-
|
|
21
|
-
|
|
16
|
+
loadNumericMap,
|
|
17
|
+
loadI18nData
|
|
18
|
+
} = require('./utils/i18nDataLoader');
|
|
22
19
|
|
|
23
|
-
createHashFunction = createHash;
|
|
24
20
|
const {
|
|
25
21
|
RawSource
|
|
26
22
|
} = sources;
|
|
@@ -28,463 +24,131 @@ const pluginName = 'I18nNumericIndexPlugin';
|
|
|
28
24
|
|
|
29
25
|
class I18nNumericIndexPlugin {
|
|
30
26
|
constructor(options = {}) {
|
|
31
|
-
this.options = options
|
|
27
|
+
this.options = { ...options,
|
|
28
|
+
singleFile: options.singleFile || false,
|
|
29
|
+
includeContentHash: options.includeContentHash || false,
|
|
30
|
+
generateManifest: options.generateManifest || false
|
|
31
|
+
};
|
|
32
32
|
this.numericMap = null;
|
|
33
|
-
this.
|
|
34
|
-
|
|
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
|
|
33
|
+
this.i18nData = null;
|
|
34
|
+
this.manifest = {};
|
|
82
35
|
}
|
|
83
36
|
|
|
84
|
-
|
|
85
|
-
if (this.numericMap) {
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
const numericMapPathOpt = this.options.numericMapPath;
|
|
90
|
-
const numericMapPath = numericMapPathOpt ? path.resolve(compilation.compiler.context, numericMapPathOpt) : null;
|
|
91
|
-
|
|
92
|
-
try {
|
|
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)
|
|
96
|
-
|
|
97
|
-
if (parsedData && parsedData.sortedOriginalKeys && parsedData.totalKeys !== undefined) {
|
|
98
|
-
// Old format
|
|
99
|
-
this.numericMap = {
|
|
100
|
-
sortedOriginalKeys: parsedData.sortedOriginalKeys,
|
|
101
|
-
totalKeys: parsedData.totalKeys
|
|
102
|
-
};
|
|
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
|
-
};
|
|
120
|
-
return this.numericMap;
|
|
121
|
-
} else {
|
|
122
|
-
compilation.warnings.push(new Error(`${pluginName}: numericMap file (${numericMapPath}) parsed but seems malformed. Expected 'sortedOriginalKeys' + 'totalKeys' or 'originalKeyToNumericId' + 'totalKeysInMap'. Using empty map.`));
|
|
123
|
-
this.numericMap = {
|
|
124
|
-
sortedOriginalKeys: [],
|
|
125
|
-
totalKeys: 0
|
|
126
|
-
};
|
|
127
|
-
}
|
|
128
|
-
} else {
|
|
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
|
-
|
|
134
|
-
this.numericMap = {
|
|
135
|
-
sortedOriginalKeys: [],
|
|
136
|
-
totalKeys: 0
|
|
137
|
-
};
|
|
138
|
-
}
|
|
139
|
-
} catch (err) {
|
|
140
|
-
compilation.errors.push(new Error(`${pluginName}: Error loading/parsing numericMapPath ${numericMapPath}: ${err.message}`));
|
|
141
|
-
this.numericMap = {
|
|
142
|
-
sortedOriginalKeys: [],
|
|
143
|
-
totalKeys: 0
|
|
144
|
-
};
|
|
37
|
+
getNumericMap(compilation) {
|
|
38
|
+
if (!this.numericMap) {
|
|
39
|
+
const mapPath = path.resolve(compilation.compiler.context, this.options.numericMapPath);
|
|
40
|
+
this.numericMap = loadNumericMap(mapPath, compilation);
|
|
145
41
|
}
|
|
146
42
|
|
|
147
43
|
return this.numericMap;
|
|
148
44
|
}
|
|
149
45
|
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
if (!jsResourcePathOpt) {
|
|
155
|
-
return {};
|
|
46
|
+
getI18nData(compilation) {
|
|
47
|
+
if (!this.i18nData) {
|
|
48
|
+
this.i18nData = loadI18nData(this.options, compilation);
|
|
156
49
|
}
|
|
157
50
|
|
|
158
|
-
|
|
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
|
-
}
|
|
51
|
+
return this.i18nData;
|
|
172
52
|
}
|
|
173
53
|
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
|
|
177
|
-
|
|
178
|
-
|
|
179
|
-
|
|
180
|
-
|
|
181
|
-
|
|
54
|
+
generateContentHash(content, compilation) {
|
|
55
|
+
const {
|
|
56
|
+
hashFunction,
|
|
57
|
+
hashDigest,
|
|
58
|
+
hashDigestLength
|
|
59
|
+
} = compilation.outputOptions;
|
|
60
|
+
const hash = util.createHash(hashFunction || 'xxhash64');
|
|
61
|
+
hash.update(content);
|
|
62
|
+
return hash.digest(hashDigest || 'hex').substring(0, hashDigestLength || 20);
|
|
63
|
+
}
|
|
182
64
|
|
|
183
|
-
|
|
184
|
-
const
|
|
185
|
-
const
|
|
186
|
-
|
|
187
|
-
const jsResourcePathOpt = this.options.jsResourcePath;
|
|
65
|
+
emitChunk(compilation, filename, locale, data, fileType = null) {
|
|
66
|
+
const content = decodeUnicodeEscapes(JSON.stringify(data));
|
|
67
|
+
const fileContent = `${this.options.jsonpFunc}(${content});`;
|
|
68
|
+
let outputPath = filename.replace(/\[locale\]/g, locale);
|
|
188
69
|
|
|
189
|
-
if (
|
|
190
|
-
|
|
191
|
-
|
|
192
|
-
allI18nObject,
|
|
193
|
-
locales: [],
|
|
194
|
-
jsResourceBase: {}
|
|
195
|
-
};
|
|
70
|
+
if (this.options.includeContentHash) {
|
|
71
|
+
const contentHash = this.generateContentHash(fileContent, compilation);
|
|
72
|
+
outputPath = outputPath.replace(/\.js$/, `.${contentHash}.js`);
|
|
196
73
|
}
|
|
197
74
|
|
|
198
|
-
|
|
199
|
-
|
|
200
|
-
|
|
201
|
-
|
|
202
|
-
compilation.errors.push(new Error(`${pluginName}: propertiesFolderPath does not exist: ${propertiesFolderPath}`));
|
|
203
|
-
return {
|
|
204
|
-
allI18nObject,
|
|
205
|
-
locales: [],
|
|
206
|
-
jsResourceBase: {}
|
|
207
|
-
};
|
|
75
|
+
if (this.options.generateManifest) {
|
|
76
|
+
const cleanName = filename.replace(/\[locale\]/g, locale).replace(/\.js$/, '.js');
|
|
77
|
+
const cleanNameWithType = fileType ? cleanName.replace(/\.js$/, `.${fileType}.js`) : cleanName;
|
|
78
|
+
this.manifest[cleanNameWithType] = outputPath.split('/').pop();
|
|
208
79
|
}
|
|
209
80
|
|
|
210
|
-
|
|
211
|
-
|
|
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});`;
|
|
292
|
-
const source = new RawSource(fileContent);
|
|
293
|
-
const actualContentHash = createHashFunction({
|
|
294
|
-
// Ensure createHashFunction is loaded
|
|
295
|
-
outputOptions: compilation.outputOptions,
|
|
296
|
-
content: fileContent // Hash is based on the final content
|
|
297
|
-
|
|
298
|
-
});
|
|
299
|
-
let processedFilenameTemplate = filenameTemplate.replace(/\[locale\]/g, locale).replace(/\[name\]/g, locale).replace(/\[id\]/g, locale);
|
|
300
|
-
const finalFileName = compilation.getAssetPath(processedFilenameTemplate, {
|
|
301
|
-
locale: locale,
|
|
302
|
-
contentHash: actualContentHash,
|
|
303
|
-
hash: compilation.hash,
|
|
304
|
-
// Webpack compilation hash
|
|
305
|
-
chunk: {
|
|
306
|
-
// Mock chunk object for placeholders
|
|
307
|
-
id: `i18n-${chunkType}-${locale}`,
|
|
308
|
-
name: `${chunkType}-${locale}`,
|
|
309
|
-
hash: actualContentHash,
|
|
310
|
-
// Chunk-specific hash
|
|
311
|
-
contentHash: {
|
|
312
|
-
[this.options.moduleType || 'i18n/data-extract']: actualContentHash
|
|
313
|
-
}
|
|
314
|
-
},
|
|
315
|
-
contentHashType: this.options.moduleType || 'i18n/data-extract'
|
|
316
|
-
});
|
|
317
|
-
compilation.emitAsset(finalFileName, source);
|
|
81
|
+
compilation.emitAsset(outputPath, new RawSource(fileContent));
|
|
82
|
+
return outputPath;
|
|
318
83
|
}
|
|
319
84
|
|
|
320
85
|
apply(compiler) {
|
|
321
86
|
compiler.hooks.thisCompilation.tap(pluginName, compilation => {
|
|
322
87
|
compilation.hooks.processAssets.tapAsync({
|
|
323
88
|
name: pluginName,
|
|
324
|
-
stage: Compilation.PROCESS_ASSETS_STAGE_SUMMARIZE
|
|
325
|
-
|
|
326
|
-
}, (unusedAssets, callback) => {
|
|
89
|
+
stage: Compilation.PROCESS_ASSETS_STAGE_SUMMARIZE
|
|
90
|
+
}, (assets, callback) => {
|
|
327
91
|
if (!this.options.enable) {
|
|
328
92
|
return callback();
|
|
329
93
|
}
|
|
330
94
|
|
|
331
95
|
const {
|
|
332
|
-
|
|
333
|
-
|
|
334
|
-
|
|
335
|
-
|
|
336
|
-
|
|
337
|
-
|
|
338
|
-
|
|
339
|
-
|
|
340
|
-
|
|
341
|
-
|
|
342
|
-
|
|
343
|
-
|
|
344
|
-
|
|
345
|
-
|
|
346
|
-
|
|
347
|
-
|
|
348
|
-
|
|
349
|
-
|
|
350
|
-
|
|
351
|
-
|
|
352
|
-
|
|
353
|
-
module.buildInfo.loaderIdentifiedCommentI18nKeys.forEach(key => globallyUsedCommentKeys.add(key));
|
|
96
|
+
sortedKeys,
|
|
97
|
+
totalKeys
|
|
98
|
+
} = this.getNumericMap(compilation);
|
|
99
|
+
const {
|
|
100
|
+
jsResourceBase,
|
|
101
|
+
allI18n,
|
|
102
|
+
locales
|
|
103
|
+
} = this.getI18nData(compilation);
|
|
104
|
+
if (!locales.length) return callback();
|
|
105
|
+
const numericKeysSet = new Set(sortedKeys);
|
|
106
|
+
const englishData = allI18n.en_US || jsResourceBase;
|
|
107
|
+
locales.forEach(locale => {
|
|
108
|
+
const localeData = allI18n[locale] || {};
|
|
109
|
+
const numericData = {};
|
|
110
|
+
|
|
111
|
+
for (let i = 0; i < totalKeys; i++) {
|
|
112
|
+
const key = sortedKeys[i];
|
|
113
|
+
|
|
114
|
+
if (key && jsResourceBase[key] !== undefined) {
|
|
115
|
+
numericData[i] = localeData[key] ?? englishData[key];
|
|
116
|
+
}
|
|
354
117
|
}
|
|
355
|
-
}
|
|
356
|
-
|
|
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
|
|
363
|
-
}
|
|
364
|
-
|
|
365
|
-
if (!dynamicFilenameTemplate) {
|
|
366
|
-
compilation.errors.push(new Error(`${pluginName}: Missing required option 'dynamicFilenameTemplate' in plugin options.`)); // return callback();
|
|
367
|
-
}
|
|
368
118
|
|
|
369
|
-
|
|
370
|
-
|
|
371
|
-
|
|
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
|
|
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) {
|
|
396
|
-
for (let i = 0; i < totalKeys; i++) {
|
|
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.
|
|
401
|
-
|
|
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
|
-
}
|
|
411
|
-
}
|
|
412
|
-
|
|
413
|
-
numericDataForLocale.push(value);
|
|
119
|
+
const dynamicData = {};
|
|
120
|
+
Object.keys(jsResourceBase).forEach(key => {
|
|
121
|
+
if (!numericKeysSet.has(key)) {
|
|
122
|
+
dynamicData[key] = localeData[key] ?? englishData[key];
|
|
414
123
|
}
|
|
415
|
-
}
|
|
416
|
-
|
|
417
|
-
|
|
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;
|
|
124
|
+
});
|
|
425
125
|
|
|
426
|
-
|
|
427
|
-
|
|
428
|
-
|
|
429
|
-
|
|
430
|
-
value = this.intern(englishTranslations[originalKey]);
|
|
431
|
-
}
|
|
126
|
+
if (this.options.singleFile) {
|
|
127
|
+
const combinedData = { ...numericData,
|
|
128
|
+
...dynamicData
|
|
129
|
+
};
|
|
432
130
|
|
|
433
|
-
|
|
131
|
+
if (Object.keys(combinedData).length > 0) {
|
|
132
|
+
const filename = this.options.numericFilenameTemplate || this.options.dynamicFilenameTemplate;
|
|
133
|
+
this.emitChunk(compilation, filename, locale, combinedData);
|
|
434
134
|
}
|
|
435
|
-
}
|
|
436
|
-
|
|
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;
|
|
135
|
+
} else {
|
|
136
|
+
if (Object.keys(numericData).length > 0) {
|
|
137
|
+
this.emitChunk(compilation, this.options.numericFilenameTemplate, locale, numericData, 'numeric');
|
|
451
138
|
}
|
|
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
139
|
|
|
473
|
-
|
|
474
|
-
|
|
475
|
-
|
|
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');
|
|
140
|
+
if (Object.keys(dynamicData).length > 0) {
|
|
141
|
+
this.emitChunk(compilation, this.options.dynamicFilenameTemplate, locale, dynamicData, 'dynamic');
|
|
142
|
+
}
|
|
483
143
|
}
|
|
484
|
-
}
|
|
144
|
+
});
|
|
485
145
|
|
|
146
|
+
if (this.options.generateManifest && Object.keys(this.manifest).length > 0) {
|
|
147
|
+
const manifestPath = path.dirname(this.options.numericFilenameTemplate) + '/manifest.json';
|
|
148
|
+
const manifestContent = JSON.stringify(this.manifest, null, 2);
|
|
149
|
+
compilation.emitAsset(manifestPath, new RawSource(manifestContent));
|
|
150
|
+
}
|
|
486
151
|
|
|
487
|
-
this.stringPool.clear();
|
|
488
152
|
callback();
|
|
489
153
|
});
|
|
490
154
|
});
|