@zohodesk/client_build_tool 0.0.1-0.exp.0.0.4 → 0.0.1-0.exp.0.0.9

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.
@@ -174,7 +174,17 @@ var _default = {
174
174
  localeVarName: 'document.documentElement.lang',
175
175
  jsonpFunc: 'console.log',
176
176
  jsResource: null,
177
- propertiesFolder: null
177
+ propertiesFolder: null,
178
+ propertiesPattern: '',
179
+ // NEW OPTIONS FOR NUMERIC INDEXING IN CHUNK SPLIT
180
+ useNumericIndexing: {
181
+ value: false,
182
+ cli: 'i18n_split_use_numeric'
183
+ },
184
+ numericMapPath: {
185
+ value: './deskapp/properties/i18n-numeric-map.json',
186
+ cli: 'i18n_split_numeric_map_path'
187
+ }
178
188
  },
179
189
  i18nIndexing: {
180
190
  enable: {
@@ -202,7 +212,7 @@ var _default = {
202
212
  cli: 'i18n_idx_dynamic_filename_template'
203
213
  },
204
214
  jsonpFunc: {
205
- value: 'window.loadI18nData',
215
+ value: 'window.loadI18nChunk',
206
216
  cli: 'i18n_idx_jsonp_func'
207
217
  },
208
218
  htmlTemplateLabel: {
@@ -212,6 +222,19 @@ var _default = {
212
222
  localeVarName: {
213
223
  value: 'window.userLangCode',
214
224
  cli: 'i18n_idx_locale_var_name'
225
+ },
226
+ // NEW LOADER CONFIGURATION OPTIONS
227
+ retainLines: {
228
+ value: false,
229
+ cli: 'i18n_idx_retain_lines'
230
+ },
231
+ preserveComments: {
232
+ value: true,
233
+ cli: 'i18n_idx_preserve_comments'
234
+ },
235
+ babelPlugins: {
236
+ value: null,
237
+ cli: 'i18n_idx_babel_plugins'
215
238
  }
216
239
  },
217
240
  publicFolders: ['...'],
@@ -94,18 +94,26 @@ var _default = {
94
94
  localeVarName: 'document.documentElement.lang',
95
95
  jsonpFunc: 'console.log',
96
96
  jsResource: null,
97
- propertiesFolder: null
97
+ propertiesFolder: null,
98
+ propertiesPattern: '',
99
+ // NEW OPTIONS FOR NUMERIC INDEXING IN CHUNK SPLIT
100
+ useNumericIndexing: false,
101
+ numericMapPath: './deskapp/properties/i18n-numeric-map.json'
98
102
  },
99
103
  i18nIndexing: {
100
104
  enable: false,
101
105
  jsResourcePath: './deskapp/properties/JSResources.properties',
102
106
  propertiesFolderPath: './deskapp/properties',
103
107
  numericMapPath: './deskapp/properties/i18n-numeric-map.json',
104
- numericFilenameTemplate: 'i18n-chunks/numeric/[locale]/numeric.[contenthash].js',
105
- dynamicFilenameTemplate: 'i18n-chunks/dynamic/[locale]/dynamic.[contenthash].js',
108
+ numericFilenameTemplate: 'i18n-chunks/[locale]/numeric.[contenthash].js',
109
+ dynamicFilenameTemplate: 'i18n-chunks/[locale]/dynamic.[contenthash].js',
106
110
  jsonpFunc: 'window.loadI18nChunk',
107
111
  htmlTemplateLabel: '{{--user-locale}}',
108
- localeVarName: 'window.userLangCode'
112
+ localeVarName: 'window.userLangCode',
113
+ // NEW LOADER CONFIGURATION OPTIONS
114
+ retainLines: false,
115
+ preserveComments: true,
116
+ babelPlugins: null
109
117
  },
110
118
  publicFolders: ['...', {
111
119
  source: './deskapp/tp/',
@@ -23,8 +23,8 @@ class I18nNumericIndexHtmlInjectorPlugin {
23
23
  i18nAssetsPublicPathPrefix = ''
24
24
  } = this.options;
25
25
  const DEFAULT_LOCALE = '{{--user-locale}}';
26
- const NUMERIC_FILENAME = `i18n-chunks/${DEFAULT_LOCALE}/numeric.i18n.js`;
27
- const DYNAMIC_FILENAME = `i18n-chunks/${DEFAULT_LOCALE}/dynamic.i18n.js`;
26
+ const NUMERIC_FILENAME = `i18n-chunk/${DEFAULT_LOCALE}/numeric.i18n.js`;
27
+ const DYNAMIC_FILENAME = `i18n-chunk/${DEFAULT_LOCALE}/dynamic.i18n.js`;
28
28
  const newI18nAssetUrlsToAdd = [];
29
29
  const numericAssetUrl = urlConcat('', NUMERIC_FILENAME);
30
30
  newI18nAssetUrlsToAdd.push(numericAssetUrl);
@@ -9,9 +9,12 @@ const {
9
9
  Compilation
10
10
  } = require('webpack');
11
11
 
12
- let createHashFunction; // Assuming "../I18nSplitPlugin/createHash" exists and exports createHash.
13
- // If it might not, the original try-catch with a fallback was more robust.
14
- // For this fix, I'm keeping your current simplified version.
12
+ const {
13
+ parseProperties,
14
+ decodeUnicodeEscapes
15
+ } = require('../../utils/propertiesParser');
16
+
17
+ let createHashFunction;
15
18
 
16
19
  const {
17
20
  createHash
@@ -23,58 +26,59 @@ const {
23
26
  } = sources;
24
27
  const pluginName = 'I18nNumericIndexPlugin';
25
28
 
26
- function parseProperties(content) {
27
- const lines = content.split(/\r?\n/);
28
- const data = {};
29
+ class I18nNumericIndexPlugin {
30
+ constructor(options = {}) {
31
+ this.options = options;
32
+ this.numericMap = null;
33
+ this.stringPool = new Map(); // String interning for deduplication
34
+ // Default emitLiteralUnicode to true if not specified
29
35
 
30
- for (let lineNum = 0; lineNum < lines.length; lineNum++) {
31
- const line = lines[lineNum];
32
- const trimmedLine = line.trim();
36
+ if (typeof this.options.emitLiteralUnicode === 'undefined') {
37
+ this.options.emitLiteralUnicode = true;
38
+ } // Default to compact mode for memory optimization
33
39
 
34
- if (trimmedLine && !trimmedLine.startsWith('#') && !trimmedLine.startsWith('!')) {
35
- const separatorIndex = trimmedLine.indexOf('=');
36
40
 
37
- if (separatorIndex !== -1) {
38
- // Ensure key is trimmed before use, especially if spaces can exist before '='
39
- let key = trimmedLine.substring(0, separatorIndex).trim();
40
- let value = trimmedLine.substring(separatorIndex + 1).trim();
41
+ if (typeof this.options.useCompactFormat === 'undefined') {
42
+ this.options.useCompactFormat = true;
43
+ }
44
+ } // String interning to reduce memory usage
41
45
 
42
- if (key) {
43
- // Decode Unicode escape sequences in both key and value
44
- // Assumes input like "\\uXXXX" from properties file for \uXXXX
45
- key = decodeUnicodeEscapes(key);
46
- value = decodeUnicodeEscapes(value);
47
- data[key] = value;
48
- }
49
- }
46
+
47
+ intern(str) {
48
+ if (typeof str !== 'string') {
49
+ return str;
50
50
  }
51
- }
52
51
 
53
- return {
54
- data,
55
- problematicLines: []
56
- };
57
- } // This function converts strings like "Hello \\u00E9" to "Hello é"
52
+ if (!this.stringPool.has(str)) {
53
+ this.stringPool.set(str, str);
54
+ }
58
55
 
56
+ return this.stringPool.get(str);
57
+ } // Generate compact numeric data structure - returns just an array of values
59
58
 
60
- function decodeUnicodeEscapes(str) {
61
- if (typeof str !== 'string') {
62
- return str;
63
- }
64
59
 
65
- return str.replace(/\\u([0-9a-fA-F]{4})/g, (match, hex) => {
66
- return String.fromCharCode(parseInt(hex, 16));
67
- });
68
- }
60
+ generateCompactNumericData(numericDataArray) {
61
+ if (!this.options.useCompactFormat) {
62
+ return numericDataArray;
63
+ } // Create a sparse array with only non-null values
69
64
 
70
- class I18nNumericIndexPlugin {
71
- constructor(options = {}) {
72
- this.options = options;
73
- this.numericMap = null; // Default emitLiteralUnicode to true if not specified
74
65
 
75
- if (typeof this.options.emitLiteralUnicode === 'undefined') {
76
- this.options.emitLiteralUnicode = true;
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
+ }
77
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
78
82
  }
79
83
 
80
84
  loadNumericMapOnce(compilation) {
@@ -88,16 +92,34 @@ class I18nNumericIndexPlugin {
88
92
  try {
89
93
  if (numericMapPath && fs.existsSync(numericMapPath)) {
90
94
  const fileContent = fs.readFileSync(numericMapPath, 'utf-8');
91
- const parsedData = JSON.parse(fileContent);
95
+ const parsedData = JSON.parse(fileContent); // Handle both old format (sortedOriginalKeys) and new format (originalKeyToNumericId)
92
96
 
93
97
  if (parsedData && parsedData.sortedOriginalKeys && parsedData.totalKeys !== undefined) {
98
+ // Old format
94
99
  this.numericMap = {
95
100
  sortedOriginalKeys: parsedData.sortedOriginalKeys,
96
101
  totalKeys: parsedData.totalKeys
97
102
  };
98
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;
99
121
  } else {
100
- compilation.warnings.push(new Error(`${pluginName}: numericMap file (${numericMapPath}) parsed but seems malformed. Using empty map.`));
122
+ compilation.warnings.push(new Error(`${pluginName}: numericMap file (${numericMapPath}) parsed but seems malformed. Expected 'sortedOriginalKeys' + 'totalKeys' or 'originalKeyToNumericId' + 'totalKeysInMap'. Using empty map.`));
101
123
  this.numericMap = {
102
124
  sortedOriginalKeys: [],
103
125
  totalKeys: 0
@@ -125,11 +147,36 @@ class I18nNumericIndexPlugin {
125
147
  return this.numericMap;
126
148
  }
127
149
 
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
+
128
174
  loadAllI18nData(compilation) {
129
175
  if (this.options.allI18nObject && this.options.locales) {
130
176
  return {
131
177
  allI18nObject: this.options.allI18nObject,
132
- locales: this.options.locales
178
+ locales: this.options.locales,
179
+ jsResourceBase: this.loadJSResourceBaseFile(compilation)
133
180
  };
134
181
  }
135
182
 
@@ -143,7 +190,8 @@ class I18nNumericIndexPlugin {
143
190
  compilation.errors.push(new Error(`${pluginName}: 'propertiesFolderPath' option is missing, cannot load translations.`));
144
191
  return {
145
192
  allI18nObject,
146
- locales: []
193
+ locales: [],
194
+ jsResourceBase: {}
147
195
  };
148
196
  }
149
197
 
@@ -154,7 +202,8 @@ class I18nNumericIndexPlugin {
154
202
  compilation.errors.push(new Error(`${pluginName}: propertiesFolderPath does not exist: ${propertiesFolderPath}`));
155
203
  return {
156
204
  allI18nObject,
157
- locales: []
205
+ locales: [],
206
+ jsResourceBase: {}
158
207
  };
159
208
  }
160
209
 
@@ -190,8 +239,7 @@ class I18nNumericIndexPlugin {
190
239
  if (locale && ext.toLowerCase() === baseExtension.toLowerCase()) {
191
240
  try {
192
241
  const fileContent = fs.readFileSync(filePath, 'utf-8');
193
- const parseResult = parseProperties(fileContent);
194
- allI18nObject[locale] = parseResult.data;
242
+ allI18nObject[locale] = parseProperties(fileContent);
195
243
  discoveredLocales.add(locale);
196
244
  } catch (readErr) {
197
245
  compilation.errors.push(new Error(`${pluginName}: Error reading/parsing properties file ${filePath}: ${readErr.message}`));
@@ -201,11 +249,14 @@ class I18nNumericIndexPlugin {
201
249
  });
202
250
  } catch (err) {
203
251
  compilation.errors.push(new Error(`${pluginName}: Error reading propertiesFolderPath ${propertiesFolderPath}: ${err.message}`));
204
- }
252
+ } // Load JS Resource base file separately
253
+
205
254
 
255
+ const jsResourceBase = this.loadJSResourceBaseFile(compilation);
206
256
  return {
207
257
  allI18nObject,
208
- locales: Array.from(discoveredLocales)
258
+ locales: Array.from(discoveredLocales),
259
+ jsResourceBase
209
260
  };
210
261
  }
211
262
 
@@ -219,21 +270,25 @@ class I18nNumericIndexPlugin {
219
270
  let stringifiedData;
220
271
 
221
272
  try {
222
- // JSON.stringify will escape non-ASCII characters to \uXXXX sequences
223
- stringifiedData = JSON.stringify(dataToStringify, null, 0); // If emitLiteralUnicode is true (default), convert \uXXXX sequences back to actual Unicode characters
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
224
279
  // This ensures the output file contains literal UTF-8 characters if desired.
225
280
 
281
+
226
282
  if (this.options.emitLiteralUnicode) {
227
283
  stringifiedData = decodeUnicodeEscapes(stringifiedData);
228
284
  }
229
285
  } catch (e) {
230
286
  compilation.errors.push(new Error(`${pluginName}: Failed to stringify or post-process data for ${chunkType} chunk (${locale}): ${e.message}`));
231
287
  return;
232
- }
288
+ } // Create content in the format: window.loadI18nChunk({data})
233
289
 
234
- const fileContent = `${jsonpFunc}(${stringifiedData});`; // RawSource expects a string. If stringifiedData contains literal Unicode,
235
- // fileContent will also, and Webpack handles UTF-8 output correctly.
236
290
 
291
+ const fileContent = `${jsonpFunc}(${stringifiedData});`;
237
292
  const source = new RawSource(fileContent);
238
293
  const actualContentHash = createHashFunction({
239
294
  // Ensure createHashFunction is loaded
@@ -268,14 +323,15 @@ class I18nNumericIndexPlugin {
268
323
  name: pluginName,
269
324
  stage: Compilation.PROCESS_ASSETS_STAGE_SUMMARIZE // Or a later stage if needed
270
325
 
271
- }, (assets, callback) => {
326
+ }, (unusedAssets, callback) => {
272
327
  if (!this.options.enable) {
273
328
  return callback();
274
329
  }
275
330
 
276
331
  const {
277
332
  allI18nObject,
278
- locales
333
+ locales,
334
+ jsResourceBase
279
335
  } = this.loadAllI18nData(compilation);
280
336
 
281
337
  if (!locales || locales.length === 0) {
@@ -300,8 +356,7 @@ class I18nNumericIndexPlugin {
300
356
 
301
357
  const numericFilenameTemplate = this.options.numericFilenameTemplate;
302
358
  const dynamicFilenameTemplate = this.options.dynamicFilenameTemplate;
303
- const numericJsonpFunction = this.options.numericJsonpFunction || 'loadI18nNumericChunk';
304
- const dynamicJsonpFunction = this.options.dynamicJsonpFunction || 'loadI18nDynamicChunk';
359
+ const jsonpFunction = this.options.jsonpFunc || 'window.loadI18nChunk';
305
360
 
306
361
  if (!numericFilenameTemplate) {
307
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
@@ -320,7 +375,18 @@ class I18nNumericIndexPlugin {
320
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.`));
321
376
  }
322
377
 
323
- const numericKeysSet = new Set(sortedOriginalKeys);
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}`);
324
390
 
325
391
  for (const locale of locales) {
326
392
  const localeTranslations = allI18nObject[locale] || {};
@@ -331,54 +397,94 @@ class I18nNumericIndexPlugin {
331
397
  // Iterate up to totalKeys (max expected length)
332
398
  const originalKey = sortedOriginalKeys[i]; // Get key if available
333
399
  // If originalKey is undefined (i < sortedOriginalKeys.length but originalKey is not there due to sparse array or shorter sortedKeys)
334
- // or if the translation is missing, push null.
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
+ }
335
412
 
336
- const value = originalKey !== undefined && localeTranslations[originalKey] !== undefined ? localeTranslations[originalKey] : null;
337
413
  numericDataForLocale.push(value);
338
414
  }
339
- }
415
+ } // Convert to compact format
416
+
340
417
 
341
- const dynamicDataForLocale = {}; // Process globally used comment keys first
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)
342
421
 
343
422
  globallyUsedCommentKeys.forEach(originalKey => {
344
- if (!numericKeysSet.has(originalKey)) {
345
- // Only if not already in numeric set
346
- dynamicDataForLocale[originalKey] = localeTranslations[originalKey] !== undefined ? localeTranslations[originalKey] : null; // Explicitly null for missing keys referenced in comments
423
+ if (!numericKeysSet.has(originalKey) && jsResourceBaseTranslations[originalKey] !== undefined) {
424
+ let value = null;
425
+
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
+ }
432
+
433
+ dynamicDataForLocale[originalKey] = value;
347
434
  }
348
- }); // Process remaining keys from localeTranslations
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
349
437
 
350
- Object.keys(localeTranslations).forEach(originalKey => {
438
+ Object.keys(jsResourceBaseTranslations).forEach(originalKey => {
351
439
  // Add if not in numeric set AND not already added from globallyUsedCommentKeys
352
- if (!numericKeysSet.has(originalKey) && !dynamicDataForLocale.hasOwnProperty(originalKey)) {
353
- dynamicDataForLocale[originalKey] = localeTranslations[originalKey];
354
- }
355
- });
440
+ if (!numericKeysSet.has(originalKey) && !Object.prototype.hasOwnProperty.call(dynamicDataForLocale, originalKey)) {
441
+ let value = null;
356
442
 
357
- if (this.options.logKeyCounts && locale === (this.options.logKeyCountsForLocale || 'en_US')) {
358
- const numericKeysWithValues = numericDataForLocale.filter(v => v !== null).length;
359
- const numericKeysWithNullValues = numericDataForLocale.length - numericKeysWithValues;
360
- const totalDynamicKeys = Object.keys(dynamicDataForLocale).length;
361
- const dynamicKeysWithNullValues = Object.values(dynamicDataForLocale).filter(v => v === null).length;
362
- const commentKeysInDynamic = Array.from(globallyUsedCommentKeys).filter(key => !numericKeysSet.has(key) && dynamicDataForLocale.hasOwnProperty(key)).length;
363
- const jsResourceKeysInDynamic = totalDynamicKeys - commentKeysInDynamic;
364
- console.log(`\n=== ${pluginName} KEY COUNTS FOR ${locale} ===`);
365
- console.log(`📊 NUMERIC MAPPING: ${totalKeys} map size, ${sortedOriginalKeys.length} sorted keys provided`);
366
- console.log(`📊 LOCALE TRANSLATIONS: ${Object.keys(localeTranslations).length} total keys for this locale`);
367
- console.log(`📊 GLOBALLY USED COMMENT KEYS: ${globallyUsedCommentKeys.size} total, ${Array.from(globallyUsedCommentKeys).filter(k => numericKeysSet.has(k)).length} in numeric`);
368
- console.log(`📊 NUMERIC CHUNK (${locale}): ${numericDataForLocale.length} items (${numericKeysWithValues} values, ${numericKeysWithNullValues} nulls)`);
369
- console.log(`📊 DYNAMIC CHUNK (${locale}): ${totalDynamicKeys} items (${commentKeysInDynamic} from comments, ${jsResourceKeysInDynamic} from JS resources only, ${dynamicKeysWithNullValues} nulls)`);
370
- console.log(`=== END KEY COUNTS ===\n`);
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}`);
371
475
  }
372
476
 
373
- if (numericFilenameTemplate && numericDataForLocale.length > 0) {
374
- this.emitChunkFile(compilation, numericFilenameTemplate, locale, numericDataForLocale, numericJsonpFunction, 'numeric');
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');
375
479
  }
376
480
 
377
481
  if (dynamicFilenameTemplate && Object.keys(dynamicDataForLocale).length > 0) {
378
- this.emitChunkFile(compilation, dynamicFilenameTemplate, locale, dynamicDataForLocale, dynamicJsonpFunction, 'dynamic');
482
+ this.emitChunkFile(compilation, dynamicFilenameTemplate, locale, dynamicDataForLocale, jsonpFunction, 'dynamic');
379
483
  }
380
- }
484
+ } // Clear string pool after processing to free memory
485
+
381
486
 
487
+ this.stringPool.clear();
382
488
  callback();
383
489
  });
384
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; // this.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
- // Get store for cache
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
- data[key] = allI18nObject[locale][key];
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;
@@ -40,6 +40,9 @@ function optionsHandler(options) {
40
40
  jsResourceI18nKeys,
41
41
  allI18nObject,
42
42
  locales,
43
+ // NEW OPTIONS FOR NUMERIC INDEXING
44
+ useNumericIndexing: options.useNumericIndexing,
45
+ numericMapPath: options.numericMapPath,
43
46
  // template: (object, locale) => `window.loadI18n(${JSON.stringify(object)}, ${JSON.stringify(locale)})`,
44
47
  runtime: true,
45
48
  runtimeOptions: {