@zohodesk/client_build_tool 0.0.11-exp.26.0 → 0.0.11-exp.27.0

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.
@@ -22,8 +22,27 @@ function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { de
22
22
  const {
23
23
  RawSource
24
24
  } = _webpack.sources;
25
+ const assetStoreKey = Symbol.for('I18nNumericIndexPluginAssets');
25
26
  const pluginName = 'I18nNumericIndexPlugin';
26
27
 
28
+ function buildChunkMappingFromGroups(customGroups) {
29
+ if (!customGroups) {
30
+ return {};
31
+ }
32
+
33
+ const mapping = {};
34
+ Object.entries(customGroups).forEach(([groupName, config]) => {
35
+ if (config && Array.isArray(config.chunks)) {
36
+ config.chunks.forEach(chunkName => {
37
+ if (chunkName && typeof chunkName === 'string') {
38
+ mapping[chunkName] = groupName;
39
+ }
40
+ });
41
+ }
42
+ });
43
+ return mapping;
44
+ }
45
+
27
46
  class I18nNumericIndexPlugin {
28
47
  constructor(options) {
29
48
  this.options = { ...options,
@@ -33,40 +52,45 @@ class I18nNumericIndexPlugin {
33
52
  generateManifest: options.generateManifest || false,
34
53
  outputFolder: options.outputFolder || 'i18n-chunk',
35
54
  manifestPath: options.manifestPath || null,
36
- emitFiles: options.emitFiles !== undefined ? options.emitFiles : true
55
+ emitFiles: options.emitFiles !== undefined ? options.emitFiles : true,
56
+ chunkToGroupMapping: options.chunkToGroupMapping || {}
37
57
  };
38
58
  this.numericMap = {};
39
59
  this.customGroups = {};
40
60
  this.manifest = {};
61
+ this.groupAssetUrls = {};
62
+ this.preparedAssets = [];
63
+ this.assetsPrepared = false;
64
+ this.assetsEmitted = false;
65
+ this.groupChunkMapping = buildChunkMappingFromGroups(this.options.customGroups);
41
66
  }
42
67
 
43
68
  apply(compiler) {
44
- // Detect webpackI18nGroup comments in code
69
+ // Build-time validation for critical file paths
70
+ this.validateCriticalFilePaths(compiler); // Detect webpackI18nGroup comments in code
71
+
45
72
  this.detectI18nGroupComments(compiler);
46
73
  compiler.hooks.thisCompilation.tap(pluginName, compilation => {
47
- // Add runtime module for group-based loading
48
- console.log('[I18nNumericIndexPlugin] customGroups:', this.options.customGroups);
74
+ this.groupAssetUrls = {};
75
+ this.detectedGroups = {};
76
+ this.preparedAssets = [];
77
+ this.assetsPrepared = false;
78
+ this.assetsEmitted = false; // Add runtime module for group-based loading
49
79
 
50
80
  if (this.options.customGroups) {
51
81
  compilation.hooks.additionalTreeRuntimeRequirements.tap(pluginName, (chunk, set) => {
52
82
  // Only add to the main/entry chunk to avoid duplication
53
83
  if (chunk.name === 'main' || chunk.hasRuntime()) {
54
- const chunkMapping = this.getChunkIdToGroupMapping(compilation); // Only log if we have actual mappings or it's the first time
55
-
56
- const hasMapping = Object.keys(chunkMapping).length > 0;
57
-
58
- if (hasMapping || !this._hasLoggedRuntime) {
59
- console.log('[I18nNumericIndexPlugin] Adding runtime module to chunk:', chunk.name);
60
- console.log('[I18nNumericIndexPlugin] Chunk mapping for runtime:', chunkMapping);
61
- this._hasLoggedRuntime = true;
62
- }
63
-
84
+ this.ensureAssetsPrepared(compilation);
85
+ const chunkMapping = this.getChunkIdToGroupMapping(compilation);
64
86
  compilation.addRuntimeModule(chunk, new _I18nGroupRuntimeModule.I18nGroupRuntimeModule({
65
87
  customGroups: this.options.customGroups,
66
88
  chunkIdToGroupMapping: chunkMapping,
67
89
  localeVarName: this.options.localeVarName,
68
90
  jsonpFunc: this.options.jsonpFunc,
69
- i18nPublicPathVar: this.options.i18nPublicPathVar
91
+ i18nPublicPathVar: this.options.i18nPublicPathVar,
92
+ groupAssetUrls: this.groupAssetUrls,
93
+ includeContentHash: this.options.includeContentHash
70
94
  }));
71
95
  }
72
96
  });
@@ -76,49 +100,96 @@ class I18nNumericIndexPlugin {
76
100
  name: pluginName,
77
101
  stage: compilation.PROCESS_ASSETS_STAGE_OPTIMIZE
78
102
  }, () => {
79
- this.processI18nFiles(compilation);
103
+ this.ensureAssetsPrepared(compilation);
104
+ this.emitPreparedAssets(compilation);
80
105
  });
81
106
  });
82
107
  }
83
108
 
109
+ validateCriticalFilePaths(compiler) {
110
+ const {
111
+ jsResourcePath,
112
+ propertiesFolderPath,
113
+ numericMapPath
114
+ } = this.options;
115
+ const missingPaths = [];
116
+ const resolvedPaths = []; // Check jsResourcePath (must exist)
117
+
118
+ if (jsResourcePath) {
119
+ const resolvedJsPath = _path.default.resolve(compiler.context, jsResourcePath);
120
+
121
+ resolvedPaths.push(`jsResourcePath: ${resolvedJsPath}`);
122
+
123
+ if (!_fs.default.existsSync(resolvedJsPath)) {
124
+ missingPaths.push(`jsResourcePath: ${resolvedJsPath}`);
125
+ }
126
+ } else {
127
+ missingPaths.push('jsResourcePath: [not configured]');
128
+ } // Check propertiesFolderPath (must exist)
129
+
130
+
131
+ if (propertiesFolderPath) {
132
+ const resolvedPropsPath = _path.default.resolve(compiler.context, propertiesFolderPath);
133
+
134
+ resolvedPaths.push(`propertiesFolderPath: ${resolvedPropsPath}`);
135
+
136
+ if (!_fs.default.existsSync(resolvedPropsPath)) {
137
+ missingPaths.push(`propertiesFolderPath: ${resolvedPropsPath}`);
138
+ }
139
+ } else {
140
+ missingPaths.push('propertiesFolderPath: [not configured]');
141
+ } // Check numericMapPath (must exist)
142
+
143
+
144
+ if (numericMapPath) {
145
+ const resolvedMapPath = _path.default.resolve(compiler.context, numericMapPath);
146
+
147
+ resolvedPaths.push(`numericMapPath: ${resolvedMapPath}`);
148
+
149
+ if (!_fs.default.existsSync(resolvedMapPath)) {
150
+ missingPaths.push(`numericMapPath: ${resolvedMapPath}`);
151
+ }
152
+ } else {
153
+ missingPaths.push('numericMapPath: [not configured]');
154
+ } // If any critical files are missing, fail the build immediately
155
+
156
+
157
+ if (missingPaths.length > 0) {
158
+ const errorMessage = ['', '🚨 I18N BUILD FAILURE: Critical i18n files are missing!', '', 'The following required files could not be found:', ...missingPaths.map(path => ` ❌ ${path}`), '', 'These files are essential for i18n functionality. Please ensure they exist before building.', '', 'Resolved paths checked:', ...resolvedPaths.map(path => ` 📁 ${path}`), ''].join('\n');
159
+ throw new Error(errorMessage);
160
+ } // Log successful validation in development mode
161
+
162
+
163
+ if (compiler.options.mode === 'development') {
164
+ console.log('✅ I18n critical file validation passed:');
165
+ resolvedPaths.forEach(path => console.log(` 📁 ${path}`));
166
+ }
167
+ }
168
+
84
169
  detectI18nGroupComments(compiler) {
85
- console.log('[I18nNumericIndexPlugin] Setting up i18n group comment detection');
86
170
  compiler.hooks.normalModuleFactory.tap(pluginName, factory => {
87
171
  factory.hooks.parser.for('javascript/auto').tap(pluginName, parser => {
88
172
  parser.hooks.importCall.tap(pluginName, expr => {
89
- // Only log if comments exist to reduce noise
90
173
  const comments = expr.leadingComments || [];
91
174
 
92
175
  if (comments.length > 0) {
93
- console.log('[I18nNumericIndexPlugin] Import call with comments detected');
94
- console.log('[I18nNumericIndexPlugin] Leading comments count:', comments.length);
95
- comments.forEach((comment, index) => {
96
- console.log(`[I18nNumericIndexPlugin] Comment ${index}:`, comment.value);
97
-
176
+ comments.forEach(comment => {
98
177
  if (comment.value && comment.value.includes('webpackI18nGroup')) {
99
- console.log('[I18nNumericIndexPlugin] Found webpackI18nGroup comment:', comment.value);
100
178
  const match = comment.value.match(/webpackI18nGroup:\s*["']([^"']+)["']/);
101
179
 
102
180
  if (match) {
103
181
  const groupName = match[1];
104
- console.log('[I18nNumericIndexPlugin] Extracted group name:', groupName); // Store this information for later use
105
182
 
106
183
  if (!this.detectedGroups) {
107
184
  this.detectedGroups = {};
108
- } // Extract chunk name from webpackChunkName comment
109
-
185
+ }
110
186
 
111
187
  const chunkNameMatch = comment.value.match(/webpackChunkName:\s*["']([^"']+)["']/);
112
188
 
113
189
  if (chunkNameMatch) {
114
190
  const chunkName = chunkNameMatch[1];
115
- console.log('[I18nNumericIndexPlugin] Mapping chunk:', chunkName, '→', groupName);
116
191
  this.detectedGroups[chunkName] = groupName;
117
- } else {
118
- console.log('[I18nNumericIndexPlugin] No webpackChunkName found in comment');
119
192
  }
120
- } else {
121
- console.log('[I18nNumericIndexPlugin] webpackI18nGroup pattern did not match');
122
193
  }
123
194
  }
124
195
  });
@@ -129,53 +200,33 @@ class I18nNumericIndexPlugin {
129
200
  }
130
201
 
131
202
  getChunkIdToGroupMapping(compilation) {
132
- const chunkIdToGroup = {}; // Hardcoded chunk name to group mapping based on our setupAsync.js
133
-
134
- const hardcodedMappings = {
135
- 'SetupHome': 'setup',
136
- 'preferencePopup': 'setup',
137
- 'auditloglist': 'setup'
138
- };
139
- let foundAnyMapping = false;
140
- let hasRelevantChunks = false; // Process chunks to get their actual IDs and map to groups
203
+ const chunkIdToGroup = {};
204
+ const configuredMappings = { ...this.groupChunkMapping,
205
+ ...(this.options.chunkToGroupMapping || {})
206
+ }; // Process chunks to get their actual IDs and map to groups
141
207
 
142
208
  for (const chunk of compilation.chunks) {
143
- const chunkName = chunk.name; // Check if this chunk is relevant to setup
144
-
145
- if (chunkName && (chunkName.toLowerCase().includes('setup') || chunkName.toLowerCase().includes('preference') || chunkName.toLowerCase().includes('audit'))) {
146
- hasRelevantChunks = true;
147
- } // First try detected groups, then fallback to hardcoded mappings
148
-
209
+ const chunkName = chunk.name; // First try detected groups, then fall back to configured mappings
149
210
 
150
211
  let groupName = null;
151
212
 
152
213
  if (this.detectedGroups && this.detectedGroups[chunkName]) {
153
214
  groupName = this.detectedGroups[chunkName];
154
- console.log(`[I18nNumericIndexPlugin] Found detected mapping: ${chunkName} ${groupName} (ID: ${chunk.id})`);
155
- foundAnyMapping = true;
156
- } else if (hardcodedMappings[chunkName]) {
157
- groupName = hardcodedMappings[chunkName];
158
- console.log(`[I18nNumericIndexPlugin] Found hardcoded mapping: ${chunkName} → ${groupName} (ID: ${chunk.id})`);
159
- foundAnyMapping = true;
215
+ } else if (configuredMappings[chunkName]) {
216
+ groupName = configuredMappings[chunkName];
160
217
  }
161
218
 
162
219
  if (groupName) {
163
220
  chunkIdToGroup[chunk.id] = groupName;
164
221
  }
165
- } // Only log summary if we found mappings or relevant chunks
166
-
167
-
168
- if (foundAnyMapping || hasRelevantChunks || compilation.chunks.size > 0) {
169
- console.log('[I18nNumericIndexPlugin] Building chunk ID to group mapping');
170
- console.log('[I18nNumericIndexPlugin] Detected groups:', this.detectedGroups);
171
- console.log('[I18nNumericIndexPlugin] Total chunks in compilation:', compilation.chunks.size);
172
- console.log('[I18nNumericIndexPlugin] Final chunk ID to group mapping:', chunkIdToGroup);
173
222
  }
174
223
 
175
224
  return chunkIdToGroup;
176
225
  }
177
226
 
178
227
  processI18nFiles(compilation) {
228
+ this.preparedAssets = [];
229
+ this.groupAssetUrls = {};
179
230
  const {
180
231
  jsResourcePath,
181
232
  propertiesFolderPath,
@@ -188,14 +239,17 @@ class I18nNumericIndexPlugin {
188
239
 
189
240
  if (!jsResourcePath || !propertiesFolderPath) {
190
241
  return;
191
- } // Load existing numeric map if available
242
+ } // Reset build-scoped caches so incremental builds don't carry stale data
243
+
192
244
 
245
+ this.numericMap = {};
246
+ this.manifest = {};
247
+ this.customGroups = {}; // Load existing numeric map if available
193
248
 
194
249
  if (numericMapPath) {
195
250
  const mapData = (0, _i18nDataLoader.loadNumericMap)(numericMapPath, compilation);
196
251
 
197
252
  if (mapData && mapData.sortedKeys) {
198
- // Initialize numericMap from existing data
199
253
  mapData.sortedKeys.forEach((key, id) => {
200
254
  if (key) {
201
255
  this.numericMap[key] = id;
@@ -221,15 +275,9 @@ class I18nNumericIndexPlugin {
221
275
 
222
276
  Object.keys(allI18nObject).forEach(loc => {
223
277
  allI18nObject[loc] = this.normalizeObjectValues(allI18nObject[loc]);
224
- }); // For en_US, use only JSResources (don't merge with ApplicationResources_en_US)
225
-
226
- if (allI18nObject['en_US']) {
227
- allI18nObject['en_US'] = jsResourceKeys;
228
- } else {
229
- // If en_US doesn't exist in the folder, create it from JSResources
230
- allI18nObject['en_US'] = jsResourceKeys;
231
- } // If requested, restrict all locales to base (English) keys only
278
+ }); // For en_US, use only JSResources
232
279
 
280
+ allI18nObject['en_US'] = jsResourceKeys; // If requested, restrict all locales to base (English) keys only
233
281
 
234
282
  if (this.options.restrictToBaseKeys) {
235
283
  const baseKeys = Object.keys(jsResourceKeys);
@@ -260,8 +308,11 @@ class I18nNumericIndexPlugin {
260
308
  Object.keys(localeData).forEach(key => {
261
309
  const value = localeData[key]; // Simple logic: if has numeric ID use it, otherwise it's dynamic
262
310
 
263
- if (this.numericMap[key]) {
264
- const numericKey = String(this.numericMap[key]); // Check if belongs to a custom group
311
+ const numericId = this.numericMap[key];
312
+ const hasNumericId = numericId !== undefined && numericId !== null;
313
+
314
+ if (hasNumericId) {
315
+ const numericKey = String(numericId); // Check if belongs to a custom group
265
316
 
266
317
  const belongsToGroup = this.getKeyGroup(key);
267
318
 
@@ -271,31 +322,29 @@ class I18nNumericIndexPlugin {
271
322
  numericData[numericKey] = value;
272
323
  }
273
324
  } else {
274
- // No numeric ID = dynamic key (regardless of placeholders)
325
+ // No numeric ID = dynamic key
275
326
  dynamicData[key] = value;
276
327
  }
277
328
  }); // Handle single-file mode or separate files
278
329
 
279
330
  if (this.options.singleFile) {
280
- // Combine numeric and dynamic data into a single file
281
331
  const combinedData = { ...numericData,
282
332
  ...dynamicData
283
333
  };
284
334
 
285
335
  if (Object.keys(combinedData).length > 0) {
286
- // Use singleFileTemplate for combined file - no fileType suffix needed
287
336
  const singleFileTemplate = this.options.singleFileTemplate || '[locale].js';
288
- this.emitChunk(compilation, singleFileTemplate, locale, combinedData, jsonpFunc, null, null);
337
+ this.prepareChunkAsset(compilation, singleFileTemplate, locale, combinedData, jsonpFunc, null, null);
289
338
  }
290
339
  } else {
291
340
  // Emit numeric chunk
292
341
  if (Object.keys(numericData).length > 0) {
293
- this.emitChunk(compilation, numericFilenameTemplate, locale, numericData, jsonpFunc, null, 'numeric');
342
+ this.prepareChunkAsset(compilation, numericFilenameTemplate, locale, numericData, jsonpFunc, null, 'numeric');
294
343
  } // Emit dynamic chunk
295
344
 
296
345
 
297
346
  if (Object.keys(dynamicData).length > 0) {
298
- this.emitChunk(compilation, dynamicFilenameTemplate, locale, dynamicData, jsonpFunc, null, 'dynamic');
347
+ this.prepareChunkAsset(compilation, dynamicFilenameTemplate, locale, dynamicData, jsonpFunc, null, 'dynamic');
299
348
  }
300
349
  } // Emit custom group chunks (always separate)
301
350
 
@@ -305,7 +354,7 @@ class I18nNumericIndexPlugin {
305
354
 
306
355
  if (groupConfig && Object.keys(data).length > 0) {
307
356
  const groupTemplate = groupConfig.filenameTemplate || `[locale]/${groupName}.i18n.js`;
308
- this.emitChunk(compilation, groupTemplate, locale, data, jsonpFunc, groupName, `group-${groupName}`);
357
+ this.prepareChunkAsset(compilation, groupTemplate, locale, data, jsonpFunc, groupName, `group-${groupName}`);
309
358
  }
310
359
  });
311
360
  }); // Generate manifest file if enabled
@@ -314,9 +363,34 @@ class I18nNumericIndexPlugin {
314
363
  const manifestPath = this.options.manifestPath || _path.default.join(this.options.outputFolder, 'manifest.json');
315
364
 
316
365
  const manifestContent = JSON.stringify(this.manifest, null, 2);
317
- compilation.emitAsset(manifestPath, new RawSource(manifestContent));
318
- } // Don't save numeric map - it should only be generated by the external script
366
+ this.preparedAssets.push({
367
+ outputPath: manifestPath,
368
+ source: new RawSource(manifestContent),
369
+ shouldEmit: true
370
+ });
371
+ }
372
+ }
373
+
374
+ ensureAssetsPrepared(compilation) {
375
+ if (this.assetsPrepared) {
376
+ return;
377
+ }
378
+
379
+ this.processI18nFiles(compilation);
380
+ this.assetsPrepared = true;
381
+ }
382
+
383
+ emitPreparedAssets(compilation) {
384
+ if (this.assetsEmitted) {
385
+ return;
386
+ }
319
387
 
388
+ this.preparedAssets.forEach(asset => {
389
+ if (asset.shouldEmit !== false) {
390
+ compilation.emitAsset(asset.outputPath, asset.source);
391
+ }
392
+ });
393
+ this.assetsEmitted = true;
320
394
  }
321
395
 
322
396
  parseCustomGroups(jsResourcePath, customGroups) {
@@ -357,16 +431,6 @@ class I18nNumericIndexPlugin {
357
431
  return null;
358
432
  }
359
433
 
360
- isDynamicKey(value) {
361
- // Check if value contains placeholders like {0}, {1}, etc.
362
- return /\{\d+\}/.test(value);
363
- }
364
-
365
- getNumericKey(key) {
366
- // Return numeric ID if it exists, null otherwise
367
- return this.numericMap[key] ? String(this.numericMap[key]) : null;
368
- }
369
-
370
434
  generateContentHash(content, compilation) {
371
435
  const {
372
436
  hashFunction,
@@ -382,31 +446,22 @@ class I18nNumericIndexPlugin {
382
446
 
383
447
  constructFilePath(template, locale) {
384
448
  const {
385
- outputFolder,
386
- singleFile
387
- } = this.options; // Replace locale placeholder
388
-
389
- let filePath = template.replace(/\[locale\]/g, locale); // If template already contains outputFolder or starts with a path separator, use as-is
449
+ outputFolder
450
+ } = this.options;
451
+ const filePath = template.replace(/\[locale\]/g, locale); // If template already contains outputFolder or starts with path separator, use as-is
390
452
 
391
453
  if (filePath.includes(outputFolder) || filePath.startsWith('/')) {
392
- return filePath.replace(/\\/g, '/');
393
- } // For single-file mode with a simple template like '[locale].js',
394
- // put it directly in outputFolder without subdirectories
395
-
396
-
397
- if (singleFile && !filePath.includes('/')) {
398
- return _path.default.join(outputFolder, filePath).replace(/\\/g, '/');
399
- } // For other cases, if template contains subdirectories, preserve them
454
+ return filePath;
455
+ } // Otherwise, join with outputFolder
400
456
 
401
457
 
402
458
  return _path.default.join(outputFolder, filePath).replace(/\\/g, '/');
403
459
  }
404
460
 
405
- emitChunk(compilation, filenameTemplate, locale, data, jsonpFunc, groupName = null, fileType = null) {
461
+ prepareChunkAsset(compilation, filenameTemplate, locale, data, jsonpFunc, groupName = null, fileType = null) {
406
462
  if (!filenameTemplate || Object.keys(data).length === 0) {
407
463
  return null;
408
- } // Generate content regardless of emitFiles flag (needed for runtime)
409
-
464
+ }
410
465
 
411
466
  const content = this.generateChunkContent(data, jsonpFunc, groupName);
412
467
  let outputPath = this.constructFilePath(filenameTemplate, locale); // Handle [contenthash] placeholder in template
@@ -415,42 +470,59 @@ class I18nNumericIndexPlugin {
415
470
  const contentHash = this.generateContentHash(content, compilation);
416
471
  outputPath = outputPath.replace(/\[contenthash\]/g, contentHash);
417
472
  } else if (this.options.includeContentHash) {
418
- // Legacy: Add content hash before .js extension if includeContentHash is true
419
473
  const contentHash = this.generateContentHash(content, compilation);
420
474
  outputPath = outputPath.replace(/\.js$/, `.${contentHash}.js`);
421
475
  } // Track in manifest if enabled
422
476
 
423
477
 
424
478
  if (this.options.generateManifest) {
425
- // For single-file mode, use clean locale.js format
426
- let manifestKey;
427
-
428
- if (this.options.singleFile) {
429
- // Just use locale.js without path or type suffix
430
- manifestKey = `${locale}.js`;
431
- } else {
432
- // For multi-file mode, include the full path
433
- const cleanName = this.constructFilePath(filenameTemplate, locale);
434
- manifestKey = fileType ? cleanName.replace(/\.js$/, `.${fileType}.js`) : cleanName;
435
- }
436
-
479
+ const manifestKey = this.options.singleFile ? `${locale}.js` : outputPath;
437
480
  this.manifest[manifestKey] = _path.default.basename(outputPath);
438
- } // Only emit the file if emitFiles is true
481
+ }
439
482
 
483
+ const source = new RawSource(content);
484
+ const assetInfo = {
485
+ locale,
486
+ fileType,
487
+ outputPath,
488
+ filenameTemplate
489
+ };
490
+ this.registerEmittedAsset(compilation, assetInfo);
491
+ this.preparedAssets.push({
492
+ outputPath,
493
+ source,
494
+ shouldEmit: this.options.emitFiles,
495
+ info: assetInfo
496
+ });
497
+ return outputPath;
498
+ }
440
499
 
441
- if (this.options.emitFiles) {
442
- compilation.emitAsset(outputPath, new RawSource(content));
500
+ registerEmittedAsset(compilation, assetInfo) {
501
+ if (!Object.prototype.hasOwnProperty.call(compilation, assetStoreKey)) {
502
+ Object.defineProperty(compilation, assetStoreKey, {
503
+ configurable: true,
504
+ enumerable: false,
505
+ value: []
506
+ });
443
507
  }
444
508
 
445
- return outputPath;
509
+ compilation[assetStoreKey].push(assetInfo);
510
+
511
+ if (assetInfo.fileType && assetInfo.fileType.startsWith('group-')) {
512
+ const groupName = assetInfo.fileType.slice('group-'.length);
513
+
514
+ if (!this.groupAssetUrls[groupName]) {
515
+ this.groupAssetUrls[groupName] = {};
516
+ }
517
+
518
+ this.groupAssetUrls[groupName][assetInfo.locale] = assetInfo.outputPath;
519
+ }
446
520
  }
447
521
 
448
522
  generateChunkContent(data, jsonpFunc, groupName) {
449
- // Decode Unicode escapes to convert \uXXXX to actual characters
450
523
  const jsonString = (0, _propertiesUtils.decodeUnicodeEscapes)(JSON.stringify(data));
451
524
 
452
525
  if (groupName) {
453
- // Include group name for lazy loading identification
454
526
  return `${jsonpFunc}(${jsonString}, "${groupName}");`;
455
527
  }
456
528
 
@@ -47,7 +47,24 @@ function loadNumericMap(numericMapPath, compilation) {
47
47
  } // Create sorted array for numeric ID lookups
48
48
 
49
49
 
50
- const maxId = Math.max(...Object.values(numericMap));
50
+ const values = Object.values(numericMap);
51
+
52
+ if (values.length === 0) {
53
+ throw new Error('numeric map is empty');
54
+ }
55
+
56
+ let maxId = 0;
57
+ values.forEach(id => {
58
+ const numericId = typeof id === 'number' ? id : Number(id);
59
+
60
+ if (Number.isNaN(numericId)) {
61
+ throw new Error(`invalid numeric map entry value: ${id}`);
62
+ }
63
+
64
+ if (numericId > maxId) {
65
+ maxId = numericId;
66
+ }
67
+ });
51
68
  const sortedKeys = new Array(maxId + 1);
52
69
  Object.entries(numericMap).forEach(([key, id]) => {
53
70
  sortedKeys[id] = key;
@@ -20,16 +20,7 @@ module.exports = function i18nIdReplaceLoader(source, map) {
20
20
  const resourcePath = this.resourcePath;
21
21
  this.cacheable && this.cacheable();
22
22
  const options = getOptions(this) || {};
23
- const callback = this.async();
24
- let numericMapAbsPath = null;
25
-
26
- if (options.numericMapPath) {
27
- try {
28
- numericMapAbsPath = path.isAbsolute(options.numericMapPath) ? options.numericMapPath : path.resolve(this.rootContext || this.context || process.cwd(), options.numericMapPath);
29
- this.addDependency(numericMapAbsPath);
30
- } catch (e) {}
31
- } // Skip files in excluded paths
32
-
23
+ const callback = this.async(); // Skip files in excluded paths
33
24
 
34
25
  if (options.excludePaths) {
35
26
  const shouldExclude = options.excludePaths.some(excludePath => resourcePath.includes(excludePath));
@@ -51,46 +42,23 @@ module.exports = function i18nIdReplaceLoader(source, map) {
51
42
 
52
43
  if (!options.allI18nData || Object.keys(options.allI18nData).length === 0) {
53
44
  return callback(new Error(`i18nIdReplaceLoader: 'allI18nData' option is missing or empty`));
54
- } // Load numeric ID mapping (use injected map if provided; fallback to memoized file read)
45
+ } // Load numeric ID mapping
55
46
 
56
47
 
57
- let numericIdMap = options.numericIdMap || null;
48
+ let numericIdMap = options.numericIdMap;
58
49
 
59
- try {
60
- if (!numericIdMap && (numericMapAbsPath || options.numericMapPath)) {
61
- // Module-level static cache
62
- if (!global.__CBT_I18N_NUMERIC_MAP_CACHE__) {
63
- global.__CBT_I18N_NUMERIC_MAP_CACHE__ = {};
64
- }
50
+ if (!numericIdMap && options.numericMapPath) {
51
+ try {
52
+ const mapPath = path.isAbsolute(options.numericMapPath) ? options.numericMapPath : path.resolve(this.rootContext || this.context || process.cwd(), options.numericMapPath);
53
+ this.addDependency(mapPath);
65
54
 
66
- const cache = global.__CBT_I18N_NUMERIC_MAP_CACHE__;
67
- const p = numericMapAbsPath || options.numericMapPath;
68
-
69
- try {
70
- const stat = fs.statSync(p);
71
- const key = `${p}`;
72
- const mtime = stat && stat.mtimeMs ? stat.mtimeMs : 0;
73
- const hit = cache[key];
74
-
75
- if (hit && hit.mtime === mtime && hit.map) {
76
- numericIdMap = hit.map;
77
- } else if (fs.existsSync(p)) {
78
- const txt = fs.readFileSync(p, 'utf-8');
79
- const parsed = JSON.parse(txt);
80
- const mapObj = parsed && parsed.originalKeyToNumericId ? parsed.originalKeyToNumericId : parsed;
81
-
82
- if (mapObj && typeof mapObj === 'object') {
83
- cache[key] = {
84
- mtime,
85
- map: mapObj
86
- };
87
- numericIdMap = mapObj;
88
- }
89
- }
90
- } catch (e) {// ignore and proceed without map
55
+ if (fs.existsSync(mapPath)) {
56
+ const mapContent = fs.readFileSync(mapPath, 'utf-8');
57
+ const parsed = JSON.parse(mapContent);
58
+ numericIdMap = parsed.originalKeyToNumericId || parsed;
91
59
  }
60
+ } catch (e) {// Silently continue without numeric map
92
61
  }
93
- } catch (e) {// ignore
94
62
  } // If no numeric map available, return source as-is
95
63
 
96
64
 
@@ -98,13 +66,11 @@ module.exports = function i18nIdReplaceLoader(source, map) {
98
66
  return callback(null, source, map);
99
67
  }
100
68
 
101
- const isDevMode = options.devMode || process.env.NODE_ENV === 'development';
102
-
103
69
  try {
104
70
  // Parse the JavaScript/TypeScript source code
105
71
  const ast = parser.parse(source, {
106
72
  sourceType: 'module',
107
- plugins: ['jsx', 'typescript', 'classProperties', 'optionalChaining', 'nullishCoalescingOperator'],
73
+ plugins: ['jsx', 'typescript'],
108
74
  sourceFilename: resourcePath
109
75
  });
110
76
  let hasTransformations = false; // Traverse AST and replace i18n keys with numeric IDs
@@ -113,14 +79,9 @@ module.exports = function i18nIdReplaceLoader(source, map) {
113
79
  StringLiteral(path) {
114
80
  const {
115
81
  node
116
- } = path; // Check if this string is an i18n key
117
-
118
- if (!options.allI18nData.hasOwnProperty(node.value)) {
119
- return;
120
- } // Replace with numeric ID if available
121
-
82
+ } = path; // Check if this string is an i18n key and has numeric mapping
122
83
 
123
- if (numericIdMap.hasOwnProperty(node.value)) {
84
+ if (options.allI18nData.hasOwnProperty(node.value) && numericIdMap.hasOwnProperty(node.value)) {
124
85
  const numericId = String(numericIdMap[node.value]);
125
86
  path.replaceWith(t.stringLiteral(numericId));
126
87
  hasTransformations = true;
@@ -23,10 +23,7 @@ function optimizationConfig(options) {
23
23
  } = options;
24
24
  const {
25
25
  chunkSplitEnable
26
- } = options.i18nChunkSplit; // TEMP: Test console logs for minification verification
27
-
28
- console.log('MINIFY_TEST: optimizationConfig called in mode:', options.mode);
29
- console.log('MINIFY_TEST: TerserPlugin and CSS minification active');
26
+ } = options.i18nChunkSplit;
30
27
  const i18nChunkFilename = (0, _nameTemplates.nameTemplates)('i18njs', options);
31
28
  const chunkFilenameHasContentHash = (0, _hashUtils.hasContentHash)(i18nChunkFilename);
32
29
  /**