@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.
- package/README.md +0 -102
- package/README_backup.md +0 -102
- package/lib/schemas/defaultConfigValues.js +13 -29
- package/lib/schemas/defaultConfigValuesOnly.js +10 -14
- package/lib/shared/bundler/webpack/custom_plugins/I18nNumericIndexPlugin/I18nGroupRuntimeModule.js +90 -168
- package/lib/shared/bundler/webpack/custom_plugins/I18nNumericIndexPlugin/I18nNumericIndexHtmlInjectorPlugin.js +39 -24
- package/lib/shared/bundler/webpack/custom_plugins/I18nNumericIndexPlugin/I18nNumericIndexPlugin.js +199 -127
- package/lib/shared/bundler/webpack/custom_plugins/I18nNumericIndexPlugin/utils/i18nDataLoader.js +18 -1
- package/lib/shared/bundler/webpack/loaders/i18nIdReplaceLoader.js +15 -54
- package/lib/shared/bundler/webpack/optimizationConfig.js +1 -4
- package/lib/shared/bundler/webpack/pluginConfigs/configI18nNumericIndexPlugin.js +3 -1
- package/lib/shared/bundler/webpack/plugins.js +1 -4
- package/package.json +1 -1
- package/docs/DYNAMIC_TEMPLATE_EXAMPLE.md +0 -129
- package/docs/I18N_NUMERIC_INDEXING_PLUGIN.md +0 -225
- package/docs/I18N_SINGLE_FILE_MODE.md +0 -126
- package/docs/client_build_tool_source_doc.md +0 -390
package/lib/shared/bundler/webpack/custom_plugins/I18nNumericIndexPlugin/I18nNumericIndexPlugin.js
CHANGED
|
@@ -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
|
-
//
|
|
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
|
-
|
|
48
|
-
|
|
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
|
-
|
|
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.
|
|
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
|
-
|
|
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
|
-
}
|
|
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 = {};
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
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; //
|
|
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
|
-
|
|
155
|
-
|
|
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
|
-
} //
|
|
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
|
|
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
|
-
|
|
264
|
-
|
|
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
|
|
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.
|
|
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.
|
|
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.
|
|
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.
|
|
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
|
-
|
|
318
|
-
|
|
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
|
-
|
|
387
|
-
|
|
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
|
|
393
|
-
} //
|
|
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
|
-
|
|
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
|
-
}
|
|
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
|
-
|
|
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
|
-
}
|
|
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
|
-
|
|
442
|
-
|
|
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
|
-
|
|
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
|
|
package/lib/shared/bundler/webpack/custom_plugins/I18nNumericIndexPlugin/utils/i18nDataLoader.js
CHANGED
|
@@ -47,7 +47,24 @@ function loadNumericMap(numericMapPath, compilation) {
|
|
|
47
47
|
} // Create sorted array for numeric ID lookups
|
|
48
48
|
|
|
49
49
|
|
|
50
|
-
const
|
|
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
|
|
45
|
+
} // Load numeric ID mapping
|
|
55
46
|
|
|
56
47
|
|
|
57
|
-
let numericIdMap = options.numericIdMap
|
|
48
|
+
let numericIdMap = options.numericIdMap;
|
|
58
49
|
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
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
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
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'
|
|
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;
|
|
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
|
/**
|