@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.
Files changed (28) hide show
  1. package/CHANGELOG.md +0 -10
  2. package/README.md +0 -10
  3. package/lib/schemas/defaultConfigValues.js +14 -63
  4. package/lib/schemas/defaultConfigValuesOnly.js +6 -10
  5. package/lib/shared/babel/getBabelPlugin.js +4 -9
  6. package/lib/shared/babel/runBabelForTsFile.js +1 -1
  7. package/lib/shared/bundler/webpack/custom_plugins/I18nNumericIndexPlugin/I18nNumericIndexHtmlInjectorPlugin.js +14 -12
  8. package/lib/shared/bundler/webpack/custom_plugins/I18nNumericIndexPlugin/I18nNumericIndexPlugin.js +90 -426
  9. package/lib/shared/bundler/webpack/custom_plugins/I18nNumericIndexPlugin/I18nNumericIndexPlugin_simplified.js +129 -0
  10. package/lib/shared/bundler/webpack/custom_plugins/I18nNumericIndexPlugin/i18nDataLoader.js +134 -0
  11. package/lib/shared/bundler/webpack/custom_plugins/I18nNumericIndexPlugin/utils/i18nDataLoader.js +113 -0
  12. package/lib/shared/bundler/webpack/custom_plugins/I18nSplitPlugin/I18nFilesEmitPlugin.js +5 -66
  13. package/lib/shared/bundler/webpack/custom_plugins/I18nSplitPlugin/optionsHandler.js +0 -3
  14. package/lib/shared/bundler/webpack/custom_plugins/I18nSplitPlugin/utils/collectAstKeys.js +4 -6
  15. package/lib/shared/bundler/webpack/custom_plugins/getInitialI18nAssetsArrayStr.js +1 -6
  16. package/lib/shared/bundler/webpack/jsLoaders.js +12 -7
  17. package/lib/shared/bundler/webpack/loaderConfigs/i18nIdReplaceLoaderConfig.js +37 -88
  18. package/lib/shared/bundler/webpack/loaders/i18nIdReplaceLoader.js +67 -191
  19. package/lib/shared/bundler/webpack/pluginConfigs/configI18nNumericIndexPlugin.js +27 -99
  20. package/lib/shared/bundler/webpack/pluginConfigs/configI18nSplitPlugin.js +1 -4
  21. package/lib/shared/bundler/webpack/plugins.js +3 -20
  22. package/lib/shared/bundler/webpack/utils/i18n/collectAstKeys.js +96 -0
  23. package/lib/shared/bundler/webpack/utils/propertiesParser.js +1 -1
  24. package/lib/shared/server/mockApiHandler.js +0 -7
  25. package/npm-shrinkwrap.json +32 -8225
  26. package/package.json +5 -6
  27. package/lib/shared/bundler/webpack/common/hashUtils.js +0 -20
  28. package/lib/shared/bundler/webpack/custom_plugins/I18nNumericIndexPlugin/CLAUDE.md +0 -0
@@ -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
- createHash
21
- } = require("../I18nSplitPlugin/createHash");
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.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
33
+ this.i18nData = null;
34
+ this.manifest = {};
82
35
  }
83
36
 
84
- loadNumericMapOnce(compilation) {
85
- if (this.numericMap) {
86
- return this.numericMap;
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
- loadJSResourceBaseFile(compilation) {
151
- const compilerContext = compilation.compiler.context;
152
- const jsResourcePathOpt = this.options.jsResourcePath;
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
- 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
- }
51
+ return this.i18nData;
172
52
  }
173
53
 
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
- }
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
- 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;
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 (!propertiesFolderPathOpt) {
190
- compilation.errors.push(new Error(`${pluginName}: 'propertiesFolderPath' option is missing, cannot load translations.`));
191
- return {
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
- 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
- };
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
- 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});`;
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 // Or a later stage if needed
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
- 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
- }
342
-
343
- return callback();
344
- }
345
-
346
- const numericMapData = this.loadNumericMapOnce(compilation);
347
- const sortedOriginalKeys = numericMapData.sortedOriginalKeys || [];
348
- const totalKeys = numericMapData.totalKeys || 0;
349
- const globallyUsedCommentKeys = new Set();
350
-
351
- for (const module of compilation.modules) {
352
- if (module.buildInfo && Array.isArray(module.buildInfo.loaderIdentifiedCommentI18nKeys)) {
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
- 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
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
- } // Convert to compact format
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
- if (localeTranslations[originalKey] !== undefined) {
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]);
431
- }
126
+ if (this.options.singleFile) {
127
+ const combinedData = { ...numericData,
128
+ ...dynamicData
129
+ };
432
130
 
433
- dynamicDataForLocale[originalKey] = value;
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
- }); // 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;
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
- 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}`);
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
- } // Clear string pool after processing to free memory
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
  });