@zohodesk/client_build_tool 0.0.1-0.exp.0.0.3 → 0.0.1-0.exp.0.0.4
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/lib/schemas/defaultConfigValues.js +15 -18
- package/lib/schemas/defaultConfigValuesOnly.js +5 -7
- package/lib/shared/bundler/webpack/custom_plugins/I18nNumericIndexPlugin/I18nNumericIndexPlugin.js +278 -77
- package/lib/shared/bundler/webpack/loaderConfigs/i18nIdReplaceLoaderConfig.js +30 -23
- package/lib/shared/bundler/webpack/loaders/i18nIdReplaceLoader.js +41 -74
- package/lib/shared/bundler/webpack/pluginConfigs/configI18nNumericIndexPlugin.js +73 -44
- package/package.json +1 -1
|
@@ -181,18 +181,6 @@ var _default = {
|
|
|
181
181
|
value: false,
|
|
182
182
|
cli: 'i18n_idx_enable'
|
|
183
183
|
},
|
|
184
|
-
disableDefaultMerge: {
|
|
185
|
-
value: false,
|
|
186
|
-
cli: 'i18n_idx_disable_default_merge'
|
|
187
|
-
},
|
|
188
|
-
htmlTemplateLabel: {
|
|
189
|
-
value: '{{--user-locale}}',
|
|
190
|
-
cli: 'i18n_idx_html_template_label'
|
|
191
|
-
},
|
|
192
|
-
localeVarName: {
|
|
193
|
-
value: 'window.userLangCode',
|
|
194
|
-
cli: 'i18n_idx_locale_var_name'
|
|
195
|
-
},
|
|
196
184
|
jsResourcePath: {
|
|
197
185
|
value: './deskapp/properties/JSResources.properties',
|
|
198
186
|
cli: 'i18n_idx_js_resource_path'
|
|
@@ -201,20 +189,29 @@ var _default = {
|
|
|
201
189
|
value: './deskapp/properties',
|
|
202
190
|
cli: 'i18n_idx_properties_folder_path'
|
|
203
191
|
},
|
|
204
|
-
propertiesPattern: {
|
|
205
|
-
value: ''
|
|
206
|
-
},
|
|
207
192
|
numericMapPath: {
|
|
208
193
|
value: './deskapp/properties/i18n-numeric-map.json',
|
|
209
194
|
cli: 'i18n_idx_numeric_map_path'
|
|
210
195
|
},
|
|
196
|
+
numericFilenameTemplate: {
|
|
197
|
+
value: 'i18n-chunks/[locale]/numeric.[contenthash].js',
|
|
198
|
+
cli: 'i18n_idx_numeric_filename_template'
|
|
199
|
+
},
|
|
200
|
+
dynamicFilenameTemplate: {
|
|
201
|
+
value: 'i18n-chunks/[locale]/dynamic.[contenthash].js',
|
|
202
|
+
cli: 'i18n_idx_dynamic_filename_template'
|
|
203
|
+
},
|
|
211
204
|
jsonpFunc: {
|
|
212
205
|
value: 'window.loadI18nData',
|
|
213
206
|
cli: 'i18n_idx_jsonp_func'
|
|
214
207
|
},
|
|
215
|
-
|
|
216
|
-
value:
|
|
217
|
-
cli: '
|
|
208
|
+
htmlTemplateLabel: {
|
|
209
|
+
value: '{{--user-locale}}',
|
|
210
|
+
cli: 'i18n_idx_html_template_label'
|
|
211
|
+
},
|
|
212
|
+
localeVarName: {
|
|
213
|
+
value: 'window.userLangCode',
|
|
214
|
+
cli: 'i18n_idx_locale_var_name'
|
|
218
215
|
}
|
|
219
216
|
},
|
|
220
217
|
publicFolders: ['...'],
|
|
@@ -98,16 +98,14 @@ var _default = {
|
|
|
98
98
|
},
|
|
99
99
|
i18nIndexing: {
|
|
100
100
|
enable: false,
|
|
101
|
-
disableDefaultMerge: false,
|
|
102
|
-
htmlTemplateLabel: '{{--user-locale}}',
|
|
103
|
-
localeVarName: 'window.userLangCode',
|
|
104
101
|
jsResourcePath: './deskapp/properties/JSResources.properties',
|
|
105
102
|
propertiesFolderPath: './deskapp/properties',
|
|
106
|
-
propertiesPattern: '',
|
|
107
103
|
numericMapPath: './deskapp/properties/i18n-numeric-map.json',
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
104
|
+
numericFilenameTemplate: 'i18n-chunks/numeric/[locale]/numeric.[contenthash].js',
|
|
105
|
+
dynamicFilenameTemplate: 'i18n-chunks/dynamic/[locale]/dynamic.[contenthash].js',
|
|
106
|
+
jsonpFunc: 'window.loadI18nChunk',
|
|
107
|
+
htmlTemplateLabel: '{{--user-locale}}',
|
|
108
|
+
localeVarName: 'window.userLangCode'
|
|
111
109
|
},
|
|
112
110
|
publicFolders: ['...', {
|
|
113
111
|
source: './deskapp/tp/',
|
package/lib/shared/bundler/webpack/custom_plugins/I18nNumericIndexPlugin/I18nNumericIndexPlugin.js
CHANGED
|
@@ -9,20 +9,72 @@ 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.
|
|
15
|
+
|
|
12
16
|
const {
|
|
13
17
|
createHash
|
|
14
18
|
} = require("../I18nSplitPlugin/createHash");
|
|
15
19
|
|
|
20
|
+
createHashFunction = createHash;
|
|
16
21
|
const {
|
|
17
22
|
RawSource
|
|
18
23
|
} = sources;
|
|
19
24
|
const pluginName = 'I18nNumericIndexPlugin';
|
|
20
25
|
|
|
26
|
+
function parseProperties(content) {
|
|
27
|
+
const lines = content.split(/\r?\n/);
|
|
28
|
+
const data = {};
|
|
29
|
+
|
|
30
|
+
for (let lineNum = 0; lineNum < lines.length; lineNum++) {
|
|
31
|
+
const line = lines[lineNum];
|
|
32
|
+
const trimmedLine = line.trim();
|
|
33
|
+
|
|
34
|
+
if (trimmedLine && !trimmedLine.startsWith('#') && !trimmedLine.startsWith('!')) {
|
|
35
|
+
const separatorIndex = trimmedLine.indexOf('=');
|
|
36
|
+
|
|
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
|
+
|
|
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
|
+
}
|
|
50
|
+
}
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
return {
|
|
54
|
+
data,
|
|
55
|
+
problematicLines: []
|
|
56
|
+
};
|
|
57
|
+
} // This function converts strings like "Hello \\u00E9" to "Hello é"
|
|
58
|
+
|
|
59
|
+
|
|
60
|
+
function decodeUnicodeEscapes(str) {
|
|
61
|
+
if (typeof str !== 'string') {
|
|
62
|
+
return str;
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
return str.replace(/\\u([0-9a-fA-F]{4})/g, (match, hex) => {
|
|
66
|
+
return String.fromCharCode(parseInt(hex, 16));
|
|
67
|
+
});
|
|
68
|
+
}
|
|
69
|
+
|
|
21
70
|
class I18nNumericIndexPlugin {
|
|
22
|
-
constructor(options) {
|
|
71
|
+
constructor(options = {}) {
|
|
23
72
|
this.options = options;
|
|
24
|
-
this.numericMap = null;
|
|
25
|
-
|
|
73
|
+
this.numericMap = null; // Default emitLiteralUnicode to true if not specified
|
|
74
|
+
|
|
75
|
+
if (typeof this.options.emitLiteralUnicode === 'undefined') {
|
|
76
|
+
this.options.emitLiteralUnicode = true;
|
|
77
|
+
}
|
|
26
78
|
}
|
|
27
79
|
|
|
28
80
|
loadNumericMapOnce(compilation) {
|
|
@@ -30,9 +82,12 @@ class I18nNumericIndexPlugin {
|
|
|
30
82
|
return this.numericMap;
|
|
31
83
|
}
|
|
32
84
|
|
|
85
|
+
const numericMapPathOpt = this.options.numericMapPath;
|
|
86
|
+
const numericMapPath = numericMapPathOpt ? path.resolve(compilation.compiler.context, numericMapPathOpt) : null;
|
|
87
|
+
|
|
33
88
|
try {
|
|
34
|
-
if (
|
|
35
|
-
const fileContent = fs.readFileSync(
|
|
89
|
+
if (numericMapPath && fs.existsSync(numericMapPath)) {
|
|
90
|
+
const fileContent = fs.readFileSync(numericMapPath, 'utf-8');
|
|
36
91
|
const parsedData = JSON.parse(fileContent);
|
|
37
92
|
|
|
38
93
|
if (parsedData && parsedData.sortedOriginalKeys && parsedData.totalKeys !== undefined) {
|
|
@@ -40,24 +95,27 @@ class I18nNumericIndexPlugin {
|
|
|
40
95
|
sortedOriginalKeys: parsedData.sortedOriginalKeys,
|
|
41
96
|
totalKeys: parsedData.totalKeys
|
|
42
97
|
};
|
|
43
|
-
compilation.logger.info(`${pluginName}: Loaded numeric map from ${this.numericMapPath}.`);
|
|
44
98
|
return this.numericMap;
|
|
45
99
|
} else {
|
|
46
|
-
compilation.
|
|
100
|
+
compilation.warnings.push(new Error(`${pluginName}: numericMap file (${numericMapPath}) parsed but seems malformed. Using empty map.`));
|
|
47
101
|
this.numericMap = {
|
|
48
102
|
sortedOriginalKeys: [],
|
|
49
103
|
totalKeys: 0
|
|
50
104
|
};
|
|
51
105
|
}
|
|
52
106
|
} else {
|
|
53
|
-
|
|
107
|
+
if (numericMapPathOpt) {
|
|
108
|
+
// Only warn if a path was actually provided
|
|
109
|
+
compilation.warnings.push(new Error(`${pluginName}: numericMapPath (${numericMapPath}) not found. Using empty map.`));
|
|
110
|
+
}
|
|
111
|
+
|
|
54
112
|
this.numericMap = {
|
|
55
113
|
sortedOriginalKeys: [],
|
|
56
114
|
totalKeys: 0
|
|
57
115
|
};
|
|
58
116
|
}
|
|
59
117
|
} catch (err) {
|
|
60
|
-
compilation.
|
|
118
|
+
compilation.errors.push(new Error(`${pluginName}: Error loading/parsing numericMapPath ${numericMapPath}: ${err.message}`));
|
|
61
119
|
this.numericMap = {
|
|
62
120
|
sortedOriginalKeys: [],
|
|
63
121
|
totalKeys: 0
|
|
@@ -67,115 +125,258 @@ class I18nNumericIndexPlugin {
|
|
|
67
125
|
return this.numericMap;
|
|
68
126
|
}
|
|
69
127
|
|
|
70
|
-
|
|
71
|
-
|
|
128
|
+
loadAllI18nData(compilation) {
|
|
129
|
+
if (this.options.allI18nObject && this.options.locales) {
|
|
130
|
+
return {
|
|
131
|
+
allI18nObject: this.options.allI18nObject,
|
|
132
|
+
locales: this.options.locales
|
|
133
|
+
};
|
|
134
|
+
}
|
|
135
|
+
|
|
136
|
+
const allI18nObject = {};
|
|
137
|
+
const discoveredLocales = new Set();
|
|
138
|
+
const compilerContext = compilation.compiler.context;
|
|
139
|
+
const propertiesFolderPathOpt = this.options.propertiesFolderPath;
|
|
140
|
+
const jsResourcePathOpt = this.options.jsResourcePath;
|
|
141
|
+
|
|
142
|
+
if (!propertiesFolderPathOpt) {
|
|
143
|
+
compilation.errors.push(new Error(`${pluginName}: 'propertiesFolderPath' option is missing, cannot load translations.`));
|
|
144
|
+
return {
|
|
145
|
+
allI18nObject,
|
|
146
|
+
locales: []
|
|
147
|
+
};
|
|
148
|
+
}
|
|
149
|
+
|
|
150
|
+
const propertiesFolderPath = path.resolve(compilerContext, propertiesFolderPathOpt);
|
|
151
|
+
const baseJsResourcePath = jsResourcePathOpt ? path.resolve(compilerContext, jsResourcePathOpt) : null;
|
|
152
|
+
|
|
153
|
+
if (!fs.existsSync(propertiesFolderPath)) {
|
|
154
|
+
compilation.errors.push(new Error(`${pluginName}: propertiesFolderPath does not exist: ${propertiesFolderPath}`));
|
|
155
|
+
return {
|
|
156
|
+
allI18nObject,
|
|
157
|
+
locales: []
|
|
158
|
+
};
|
|
159
|
+
}
|
|
160
|
+
|
|
161
|
+
const baseFileName = baseJsResourcePath ? path.basename(baseJsResourcePath, path.extname(baseJsResourcePath)) : null;
|
|
162
|
+
const baseExtension = baseJsResourcePath ? path.extname(baseJsResourcePath) : '.properties';
|
|
163
|
+
|
|
164
|
+
try {
|
|
165
|
+
const files = fs.readdirSync(propertiesFolderPath);
|
|
166
|
+
files.forEach(file => {
|
|
167
|
+
const filePath = path.join(propertiesFolderPath, file);
|
|
168
|
+
|
|
169
|
+
if (fs.statSync(filePath).isFile()) {
|
|
170
|
+
let locale = null;
|
|
171
|
+
const ext = path.extname(file);
|
|
172
|
+
const nameWithoutExt = path.basename(file, ext);
|
|
173
|
+
|
|
174
|
+
if (baseFileName && nameWithoutExt.startsWith(baseFileName + '_')) {
|
|
175
|
+
locale = nameWithoutExt.substring((baseFileName + '_').length);
|
|
176
|
+
} else if (baseFileName && nameWithoutExt === baseFileName) {
|
|
177
|
+
locale = this.options.defaultLocaleForBaseFile || 'en';
|
|
178
|
+
} else if (!baseFileName && nameWithoutExt.includes('_')) {
|
|
179
|
+
// Generic pattern: messages_fr_CA.properties -> fr_CA
|
|
180
|
+
const parts = nameWithoutExt.split('_');
|
|
181
|
+
|
|
182
|
+
if (parts.length > 1) {
|
|
183
|
+
locale = parts.slice(1).join('_');
|
|
184
|
+
}
|
|
185
|
+
} else if (!baseFileName && !nameWithoutExt.includes('_') && ext.toLowerCase() === baseExtension.toLowerCase()) {
|
|
186
|
+
// Fallback for files like 'fr.properties' if no baseFileName is defined
|
|
187
|
+
locale = nameWithoutExt;
|
|
188
|
+
}
|
|
189
|
+
|
|
190
|
+
if (locale && ext.toLowerCase() === baseExtension.toLowerCase()) {
|
|
191
|
+
try {
|
|
192
|
+
const fileContent = fs.readFileSync(filePath, 'utf-8');
|
|
193
|
+
const parseResult = parseProperties(fileContent);
|
|
194
|
+
allI18nObject[locale] = parseResult.data;
|
|
195
|
+
discoveredLocales.add(locale);
|
|
196
|
+
} catch (readErr) {
|
|
197
|
+
compilation.errors.push(new Error(`${pluginName}: Error reading/parsing properties file ${filePath}: ${readErr.message}`));
|
|
198
|
+
}
|
|
199
|
+
}
|
|
200
|
+
}
|
|
201
|
+
});
|
|
202
|
+
} catch (err) {
|
|
203
|
+
compilation.errors.push(new Error(`${pluginName}: Error reading propertiesFolderPath ${propertiesFolderPath}: ${err.message}`));
|
|
204
|
+
}
|
|
205
|
+
|
|
206
|
+
return {
|
|
207
|
+
allI18nObject,
|
|
208
|
+
locales: Array.from(discoveredLocales)
|
|
209
|
+
};
|
|
210
|
+
}
|
|
211
|
+
|
|
212
|
+
validateAndSanitizeData(data, chunkType, locale, compilation) {
|
|
213
|
+
// Placeholder for any future validation or sanitization logic
|
|
214
|
+
return data;
|
|
215
|
+
}
|
|
216
|
+
|
|
217
|
+
emitChunkFile(compilation, filenameTemplate, locale, fileContentData, jsonpFunc, chunkType) {
|
|
218
|
+
const dataToStringify = this.validateAndSanitizeData(fileContentData, chunkType, locale, compilation);
|
|
219
|
+
let stringifiedData;
|
|
220
|
+
|
|
221
|
+
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
|
|
224
|
+
// This ensures the output file contains literal UTF-8 characters if desired.
|
|
225
|
+
|
|
226
|
+
if (this.options.emitLiteralUnicode) {
|
|
227
|
+
stringifiedData = decodeUnicodeEscapes(stringifiedData);
|
|
228
|
+
}
|
|
229
|
+
} catch (e) {
|
|
230
|
+
compilation.errors.push(new Error(`${pluginName}: Failed to stringify or post-process data for ${chunkType} chunk (${locale}): ${e.message}`));
|
|
231
|
+
return;
|
|
232
|
+
}
|
|
233
|
+
|
|
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
|
+
|
|
72
237
|
const source = new RawSource(fileContent);
|
|
73
|
-
const actualContentHash =
|
|
74
|
-
//
|
|
238
|
+
const actualContentHash = createHashFunction({
|
|
239
|
+
// Ensure createHashFunction is loaded
|
|
75
240
|
outputOptions: compilation.outputOptions,
|
|
76
|
-
content: fileContent
|
|
241
|
+
content: fileContent // Hash is based on the final content
|
|
242
|
+
|
|
77
243
|
});
|
|
78
|
-
let processedFilenameTemplate =
|
|
244
|
+
let processedFilenameTemplate = filenameTemplate.replace(/\[locale\]/g, locale).replace(/\[name\]/g, locale).replace(/\[id\]/g, locale);
|
|
79
245
|
const finalFileName = compilation.getAssetPath(processedFilenameTemplate, {
|
|
80
246
|
locale: locale,
|
|
81
247
|
contentHash: actualContentHash,
|
|
82
248
|
hash: compilation.hash,
|
|
249
|
+
// Webpack compilation hash
|
|
83
250
|
chunk: {
|
|
84
|
-
|
|
85
|
-
|
|
251
|
+
// Mock chunk object for placeholders
|
|
252
|
+
id: `i18n-${chunkType}-${locale}`,
|
|
253
|
+
name: `${chunkType}-${locale}`,
|
|
86
254
|
hash: actualContentHash,
|
|
255
|
+
// Chunk-specific hash
|
|
87
256
|
contentHash: {
|
|
88
|
-
[this.options.moduleType || 'i18n/
|
|
257
|
+
[this.options.moduleType || 'i18n/data-extract']: actualContentHash
|
|
89
258
|
}
|
|
90
259
|
},
|
|
91
|
-
contentHashType: this.options.moduleType || 'i18n/
|
|
260
|
+
contentHashType: this.options.moduleType || 'i18n/data-extract'
|
|
92
261
|
});
|
|
93
262
|
compilation.emitAsset(finalFileName, source);
|
|
94
|
-
compilation.logger.info(`${pluginName}: Emitted ${typeSuffix} i18n file for locale ${locale} to ${finalFileName}`);
|
|
95
263
|
}
|
|
96
264
|
|
|
97
265
|
apply(compiler) {
|
|
98
266
|
compiler.hooks.thisCompilation.tap(pluginName, compilation => {
|
|
99
267
|
compilation.hooks.processAssets.tapAsync({
|
|
100
268
|
name: pluginName,
|
|
101
|
-
stage: Compilation.PROCESS_ASSETS_STAGE_SUMMARIZE
|
|
269
|
+
stage: Compilation.PROCESS_ASSETS_STAGE_SUMMARIZE // Or a later stage if needed
|
|
270
|
+
|
|
102
271
|
}, (assets, callback) => {
|
|
272
|
+
if (!this.options.enable) {
|
|
273
|
+
return callback();
|
|
274
|
+
}
|
|
275
|
+
|
|
103
276
|
const {
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
} = this.
|
|
277
|
+
allI18nObject,
|
|
278
|
+
locales
|
|
279
|
+
} = this.loadAllI18nData(compilation);
|
|
107
280
|
|
|
108
|
-
if (
|
|
109
|
-
|
|
281
|
+
if (!locales || locales.length === 0) {
|
|
282
|
+
if (this.options.propertiesFolderPath) {
|
|
283
|
+
// Only warn if path was given
|
|
284
|
+
compilation.warnings.push(new Error(`${pluginName}: No locales found or no translation data loaded from ${this.options.propertiesFolderPath}.`));
|
|
285
|
+
}
|
|
286
|
+
|
|
287
|
+
return callback();
|
|
110
288
|
}
|
|
111
289
|
|
|
112
|
-
const
|
|
290
|
+
const numericMapData = this.loadNumericMapOnce(compilation);
|
|
291
|
+
const sortedOriginalKeys = numericMapData.sortedOriginalKeys || [];
|
|
292
|
+
const totalKeys = numericMapData.totalKeys || 0;
|
|
113
293
|
const globallyUsedCommentKeys = new Set();
|
|
114
294
|
|
|
115
295
|
for (const module of compilation.modules) {
|
|
116
|
-
if (module.buildInfo) {
|
|
117
|
-
|
|
118
|
-
module.buildInfo.loaderIdentifiedLiteralI18nKeys.forEach(key => globallyUsedLiteralKeys.add(key));
|
|
119
|
-
}
|
|
120
|
-
|
|
121
|
-
if (Array.isArray(module.buildInfo.loaderIdentifiedCommentI18nKeys)) {
|
|
122
|
-
module.buildInfo.loaderIdentifiedCommentI18nKeys.forEach(key => globallyUsedCommentKeys.add(key));
|
|
123
|
-
}
|
|
296
|
+
if (module.buildInfo && Array.isArray(module.buildInfo.loaderIdentifiedCommentI18nKeys)) {
|
|
297
|
+
module.buildInfo.loaderIdentifiedCommentI18nKeys.forEach(key => globallyUsedCommentKeys.add(key));
|
|
124
298
|
}
|
|
125
299
|
}
|
|
126
300
|
|
|
127
|
-
|
|
128
|
-
const
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
301
|
+
const numericFilenameTemplate = this.options.numericFilenameTemplate;
|
|
302
|
+
const dynamicFilenameTemplate = this.options.dynamicFilenameTemplate;
|
|
303
|
+
const numericJsonpFunction = this.options.numericJsonpFunction || 'loadI18nNumericChunk';
|
|
304
|
+
const dynamicJsonpFunction = this.options.dynamicJsonpFunction || 'loadI18nDynamicChunk';
|
|
305
|
+
|
|
306
|
+
if (!numericFilenameTemplate) {
|
|
307
|
+
compilation.errors.push(new Error(`${pluginName}: Missing required option 'numericFilenameTemplate' in plugin options.`)); // return callback(); // Allow processing other chunks if one template is missing
|
|
308
|
+
}
|
|
309
|
+
|
|
310
|
+
if (!dynamicFilenameTemplate) {
|
|
311
|
+
compilation.errors.push(new Error(`${pluginName}: Missing required option 'dynamicFilenameTemplate' in plugin options.`)); // return callback();
|
|
312
|
+
}
|
|
313
|
+
|
|
314
|
+
if (typeof numericFilenameTemplate === 'string' && !numericFilenameTemplate.includes('[locale]') && !numericFilenameTemplate.includes('[name]') && !numericFilenameTemplate.includes('[id]')) {
|
|
315
|
+
compilation.warnings.push(new Error( // Changed to warning as it might be intentional for a single combined file not per-locale
|
|
316
|
+
`${pluginName}: The 'numericFilenameTemplate' ("${numericFilenameTemplate}") ` + `does not include '[locale]', '[name]', or '[id]'. All locales will overwrite the same file if multiple locales exist.`));
|
|
140
317
|
}
|
|
141
318
|
|
|
142
|
-
if (
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
319
|
+
if (typeof dynamicFilenameTemplate === 'string' && !dynamicFilenameTemplate.includes('[locale]') && !dynamicFilenameTemplate.includes('[name]') && !dynamicFilenameTemplate.includes('[id]')) {
|
|
320
|
+
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
|
+
}
|
|
322
|
+
|
|
323
|
+
const numericKeysSet = new Set(sortedOriginalKeys);
|
|
324
|
+
|
|
325
|
+
for (const locale of locales) {
|
|
326
|
+
const localeTranslations = allI18nObject[locale] || {};
|
|
327
|
+
const numericDataForLocale = []; // Fill numericDataForLocale based on sortedOriginalKeys
|
|
146
328
|
|
|
329
|
+
if (totalKeys > 0 && sortedOriginalKeys.length > 0) {
|
|
147
330
|
for (let i = 0; i < totalKeys; i++) {
|
|
148
|
-
|
|
331
|
+
// Iterate up to totalKeys (max expected length)
|
|
332
|
+
const originalKey = sortedOriginalKeys[i]; // Get key if available
|
|
333
|
+
// 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.
|
|
149
335
|
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
} else {
|
|
153
|
-
orderedNumericTranslations[i] = 0; // Placeholder for keys not identified as literals or not used
|
|
154
|
-
}
|
|
336
|
+
const value = originalKey !== undefined && localeTranslations[originalKey] !== undefined ? localeTranslations[originalKey] : null;
|
|
337
|
+
numericDataForLocale.push(value);
|
|
155
338
|
}
|
|
156
|
-
|
|
157
|
-
this.emitFile(compilation, numericFilenameTemplate, locale, orderedNumericTranslations, numericJsonpFunc, 'numeric');
|
|
158
339
|
}
|
|
159
|
-
}
|
|
160
340
|
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
dynamicKeyTranslations[originalKey] = localeTranslations[originalKey];
|
|
168
|
-
} else {
|
|
169
|
-
dynamicKeyTranslations[originalKey] = null;
|
|
170
|
-
}
|
|
171
|
-
});
|
|
172
|
-
|
|
173
|
-
if (Object.keys(dynamicKeyTranslations).length > 0) {
|
|
174
|
-
this.emitFile(compilation, dynamicFilenameTemplate, locale, dynamicKeyTranslations, dynamicJsonpFunc, 'dynamic');
|
|
341
|
+
const dynamicDataForLocale = {}; // Process globally used comment keys first
|
|
342
|
+
|
|
343
|
+
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
|
|
175
347
|
}
|
|
348
|
+
}); // Process remaining keys from localeTranslations
|
|
349
|
+
|
|
350
|
+
Object.keys(localeTranslations).forEach(originalKey => {
|
|
351
|
+
// 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
|
+
});
|
|
356
|
+
|
|
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`);
|
|
371
|
+
}
|
|
372
|
+
|
|
373
|
+
if (numericFilenameTemplate && numericDataForLocale.length > 0) {
|
|
374
|
+
this.emitChunkFile(compilation, numericFilenameTemplate, locale, numericDataForLocale, numericJsonpFunction, 'numeric');
|
|
375
|
+
}
|
|
376
|
+
|
|
377
|
+
if (dynamicFilenameTemplate && Object.keys(dynamicDataForLocale).length > 0) {
|
|
378
|
+
this.emitChunkFile(compilation, dynamicFilenameTemplate, locale, dynamicDataForLocale, dynamicJsonpFunction, 'dynamic');
|
|
176
379
|
}
|
|
177
|
-
} else {
|
|
178
|
-
compilation.logger.info(`${pluginName}: No globally used comment keys found. Skipping dynamic file emission.`);
|
|
179
380
|
}
|
|
180
381
|
|
|
181
382
|
callback();
|
|
@@ -11,18 +11,20 @@ function loadJSResourcesOnce(options) {
|
|
|
11
11
|
return allI18nDataFromPropertiesCache;
|
|
12
12
|
}
|
|
13
13
|
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
}
|
|
21
|
-
|
|
22
|
-
|
|
14
|
+
if (!options.i18nIndexing || !options.i18nIndexing.enable) {
|
|
15
|
+
throw new Error('i18nIdReplaceLoader requires i18nIndexing to be enabled');
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
if (!options.i18nIndexing.jsResourcePath) {
|
|
19
|
+
throw new Error('Missing required jsResourcePath in i18nIndexing options');
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
const propertiesFilePath = path.resolve(process.cwd(), options.i18nIndexing.jsResourcePath);
|
|
23
|
+
|
|
24
|
+
if (!fs.existsSync(propertiesFilePath)) {
|
|
25
|
+
throw new Error(`JSResource file not found at: ${propertiesFilePath}`);
|
|
23
26
|
}
|
|
24
27
|
|
|
25
|
-
const propertiesFilePath = path.resolve(process.cwd(), resourcePathToLoad);
|
|
26
28
|
const i18nData = {};
|
|
27
29
|
|
|
28
30
|
try {
|
|
@@ -56,7 +58,12 @@ function loadJSResourcesOnce(options) {
|
|
|
56
58
|
}
|
|
57
59
|
}
|
|
58
60
|
});
|
|
59
|
-
} catch (err) {
|
|
61
|
+
} catch (err) {
|
|
62
|
+
throw new Error(`Error reading JSResource file ${propertiesFilePath}: ${err.message}`);
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
if (Object.keys(i18nData).length === 0) {
|
|
66
|
+
throw new Error(`No i18n data found in JSResource file: ${propertiesFilePath}`);
|
|
60
67
|
}
|
|
61
68
|
|
|
62
69
|
allI18nDataFromPropertiesCache = i18nData;
|
|
@@ -64,25 +71,25 @@ function loadJSResourcesOnce(options) {
|
|
|
64
71
|
}
|
|
65
72
|
|
|
66
73
|
function i18nIdReplaceLoaderConfig(options) {
|
|
74
|
+
if (!options.i18nIndexing || !options.i18nIndexing.enable) {
|
|
75
|
+
throw new Error('i18nIdReplaceLoader requires i18nIndexing to be enabled');
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
if (!options.i18nIndexing.numericMapPath) {
|
|
79
|
+
throw new Error('Missing required numericMapPath in i18nIndexing options');
|
|
80
|
+
}
|
|
81
|
+
|
|
67
82
|
const allI18nData = loadJSResourcesOnce(options);
|
|
68
83
|
|
|
69
84
|
const i18nKeyReplaceLoaderPath = require.resolve('../loaders/i18nIdReplaceLoader.js');
|
|
70
85
|
|
|
71
86
|
const loaderOptions = {
|
|
72
87
|
allI18nData: allI18nData,
|
|
73
|
-
sourceMaps: !!options.devtool && options.devtool.includes('source-map'),
|
|
74
|
-
isDebug: options.mode === 'development'
|
|
88
|
+
sourceMaps: !!(options.devtool && options.devtool.includes('source-map')),
|
|
89
|
+
isDebug: options.mode === 'development',
|
|
90
|
+
useNumericIndexing: true,
|
|
91
|
+
numericMapPath: options.i18nIndexing.numericMapPath
|
|
75
92
|
};
|
|
76
|
-
|
|
77
|
-
if (options.i18nIndexing && options.i18nIndexing.enable) {
|
|
78
|
-
loaderOptions.useNumericIndexing = true;
|
|
79
|
-
loaderOptions.numericMapPath = options.i18nIndexing.numericMapPath;
|
|
80
|
-
loaderOptions.fallbackToHash = options.i18nIndexing.fallbackToHash !== undefined ? options.i18nIndexing.fallbackToHash : true;
|
|
81
|
-
} else {
|
|
82
|
-
loaderOptions.useNumericIndexing = false;
|
|
83
|
-
loaderOptions.fallbackToHash = true;
|
|
84
|
-
}
|
|
85
|
-
|
|
86
93
|
return {
|
|
87
94
|
loader: i18nKeyReplaceLoaderPath,
|
|
88
95
|
options: loaderOptions
|
|
@@ -16,18 +16,12 @@ const {
|
|
|
16
16
|
getOptions
|
|
17
17
|
} = require('loader-utils');
|
|
18
18
|
|
|
19
|
-
let collectAndCategorizeUsedI18nKeys
|
|
19
|
+
let collectAndCategorizeUsedI18nKeys;
|
|
20
20
|
|
|
21
21
|
try {
|
|
22
22
|
collectAndCategorizeUsedI18nKeys = require('../custom_plugins/I18nSplitPlugin/utils/collectAstKeys').collectAndCategorizeUsedI18nKeys;
|
|
23
|
-
generateShortHash = require('../common/hashUtils').generateShortHash;
|
|
24
23
|
} catch (e) {
|
|
25
|
-
|
|
26
|
-
literalKeys: new Set(),
|
|
27
|
-
commentKeys: new Set()
|
|
28
|
-
});
|
|
29
|
-
|
|
30
|
-
generateShortHash = key => key;
|
|
24
|
+
throw new Error('[i18nIdReplaceLoader] Required dependency not found: ' + e.message);
|
|
31
25
|
}
|
|
32
26
|
|
|
33
27
|
const LOADER_PREFIX = '[i18nIdReplaceLoader]';
|
|
@@ -36,34 +30,32 @@ let mapLoadAttemptedForPath = {};
|
|
|
36
30
|
|
|
37
31
|
function loadNumericIdMap(loaderContext, mapPath) {
|
|
38
32
|
if (!mapPath) {
|
|
39
|
-
|
|
40
|
-
return null;
|
|
33
|
+
throw new Error(`${LOADER_PREFIX} Numeric map path not provided in loader options.`);
|
|
41
34
|
}
|
|
42
35
|
|
|
43
|
-
|
|
36
|
+
const absoluteMapPath = path.isAbsolute(mapPath) ? mapPath : path.resolve(loaderContext.rootContext || process.cwd(), mapPath);
|
|
37
|
+
|
|
38
|
+
if (numericIdMapDataCache && mapLoadAttemptedForPath[absoluteMapPath]) {
|
|
44
39
|
return numericIdMapDataCache;
|
|
45
40
|
}
|
|
46
41
|
|
|
47
|
-
mapLoadAttemptedForPath[
|
|
42
|
+
mapLoadAttemptedForPath[absoluteMapPath] = true;
|
|
43
|
+
|
|
44
|
+
if (!fs.existsSync(absoluteMapPath)) {
|
|
45
|
+
throw new Error(`${LOADER_PREFIX} Pre-generated i18n numeric map file NOT FOUND at: ${absoluteMapPath}.`);
|
|
46
|
+
}
|
|
48
47
|
|
|
49
48
|
try {
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
numericIdMapDataCache = parsedData.originalKeyToNumericId;
|
|
56
|
-
} else {
|
|
57
|
-
loaderContext.emitError(new Error(`${LOADER_PREFIX} Pre-generated map file (${mapPath}) is invalid or does not contain 'originalKeyToNumericId'.`));
|
|
58
|
-
numericIdMapDataCache = null;
|
|
59
|
-
}
|
|
60
|
-
} else {
|
|
61
|
-
loaderContext.emitError(new Error(`${LOADER_PREFIX} Pre-generated i18n numeric map file NOT FOUND at: ${mapPath}.`));
|
|
62
|
-
numericIdMapDataCache = null;
|
|
49
|
+
const fileContent = fs.readFileSync(absoluteMapPath, 'utf-8');
|
|
50
|
+
const parsedData = JSON.parse(fileContent);
|
|
51
|
+
|
|
52
|
+
if (!parsedData || !parsedData.originalKeyToNumericId || typeof parsedData.originalKeyToNumericId !== 'object') {
|
|
53
|
+
throw new Error(`${LOADER_PREFIX} Pre-generated map file (${absoluteMapPath}) is invalid or does not contain 'originalKeyToNumericId'.`);
|
|
63
54
|
}
|
|
55
|
+
|
|
56
|
+
numericIdMapDataCache = parsedData.originalKeyToNumericId;
|
|
64
57
|
} catch (err) {
|
|
65
|
-
|
|
66
|
-
numericIdMapDataCache = null;
|
|
58
|
+
throw new Error(`${LOADER_PREFIX} Error loading or parsing pre-generated i18n map from ${absoluteMapPath}: ${err.message}`);
|
|
67
59
|
}
|
|
68
60
|
|
|
69
61
|
return numericIdMapDataCache;
|
|
@@ -77,23 +69,19 @@ module.exports = function i18nIdReplaceLoader(source, map, meta) {
|
|
|
77
69
|
const loaderContext = this;
|
|
78
70
|
|
|
79
71
|
if (!options.allI18nData || typeof options.allI18nData !== 'object' || Object.keys(options.allI18nData).length === 0) {
|
|
80
|
-
|
|
81
|
-
return callback(null, source, map);
|
|
72
|
+
throw new Error(`${LOADER_PREFIX} [${resourcePath}] 'allI18nData' option is missing or empty.`);
|
|
82
73
|
}
|
|
83
74
|
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
if (useNumericIndexing) {
|
|
88
|
-
localOriginalKeyToNumericIdMap = loadNumericIdMap(this, options.numericMapPath);
|
|
75
|
+
if (!options.useNumericIndexing) {
|
|
76
|
+
throw new Error(`${LOADER_PREFIX} [${resourcePath}] 'useNumericIndexing' must be enabled.`);
|
|
89
77
|
}
|
|
90
78
|
|
|
91
|
-
const
|
|
79
|
+
const numericIdMap = loadNumericIdMap(this, options.numericMapPath);
|
|
92
80
|
|
|
93
81
|
try {
|
|
94
82
|
const parserOptions = {
|
|
95
83
|
sourceType: 'module',
|
|
96
|
-
plugins: ['jsx', 'typescript', 'classProperties', 'optionalChaining', 'nullishCoalescingOperator'],
|
|
84
|
+
plugins: ['jsx', 'typescript', 'classProperties', 'optionalChaining', 'nullishCoalescingOperator', 'objectRestSpread', 'dynamicImport'],
|
|
97
85
|
attachComment: true,
|
|
98
86
|
sourceFilename: resourcePath
|
|
99
87
|
};
|
|
@@ -119,9 +107,7 @@ module.exports = function i18nIdReplaceLoader(source, map, meta) {
|
|
|
119
107
|
}
|
|
120
108
|
}
|
|
121
109
|
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
if (keysToReplaceInLiterals.size === 0) {
|
|
110
|
+
if (literalKeys.size === 0) {
|
|
125
111
|
return callback(null, source, map);
|
|
126
112
|
}
|
|
127
113
|
|
|
@@ -133,44 +119,25 @@ module.exports = function i18nIdReplaceLoader(source, map, meta) {
|
|
|
133
119
|
if ((node.type === 'Literal' || node.type === 'StringLiteral') && typeof node.value === 'string') {
|
|
134
120
|
const originalValue = node.value;
|
|
135
121
|
|
|
136
|
-
if (
|
|
137
|
-
|
|
122
|
+
if (literalKeys.has(originalValue)) {
|
|
123
|
+
const numericId = numericIdMap[originalValue];
|
|
138
124
|
|
|
139
|
-
if (
|
|
140
|
-
const
|
|
125
|
+
if (numericId !== undefined) {
|
|
126
|
+
const numericLiteralNode = {
|
|
127
|
+
type: 'NumericLiteral',
|
|
128
|
+
value: numericId
|
|
129
|
+
};
|
|
130
|
+
let replacementNode = numericLiteralNode;
|
|
141
131
|
|
|
142
|
-
if (
|
|
143
|
-
|
|
144
|
-
type: '
|
|
145
|
-
|
|
132
|
+
if (parent && parent.type === 'JSXAttribute' && parent.value === node) {
|
|
133
|
+
replacementNode = {
|
|
134
|
+
type: 'JSXExpressionContainer',
|
|
135
|
+
expression: numericLiteralNode
|
|
146
136
|
};
|
|
147
|
-
let replacementNode = numericLiteralNode;
|
|
148
|
-
|
|
149
|
-
if (parent && parent.type === 'JSXAttribute' && parent.value === node) {
|
|
150
|
-
replacementNode = {
|
|
151
|
-
type: 'JSXExpressionContainer',
|
|
152
|
-
expression: numericLiteralNode
|
|
153
|
-
};
|
|
154
|
-
}
|
|
155
|
-
|
|
156
|
-
walkerControl.replace(replacementNode);
|
|
157
|
-
replacementMade = true;
|
|
158
|
-
replaced = true;
|
|
159
137
|
}
|
|
160
|
-
}
|
|
161
138
|
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
const hashValue = generateShortHash(originalValue);
|
|
165
|
-
const hashedStringLiteralNode = {
|
|
166
|
-
type: 'StringLiteral',
|
|
167
|
-
value: hashValue
|
|
168
|
-
};
|
|
169
|
-
walkerControl.replace(hashedStringLiteralNode);
|
|
170
|
-
replacementMade = true;
|
|
171
|
-
} catch (hashError) {
|
|
172
|
-
loaderContext.emitError(new Error(`${LOADER_PREFIX} [${resourcePath}] Error hashing key "${originalValue}": ${hashError.message}`));
|
|
173
|
-
}
|
|
139
|
+
walkerControl.replace(replacementNode);
|
|
140
|
+
replacementMade = true;
|
|
174
141
|
}
|
|
175
142
|
}
|
|
176
143
|
}
|
|
@@ -179,7 +146,7 @@ module.exports = function i18nIdReplaceLoader(source, map, meta) {
|
|
|
179
146
|
|
|
180
147
|
if (replacementMade) {
|
|
181
148
|
const generateOptions = {
|
|
182
|
-
sourceMaps: options.sourceMaps,
|
|
149
|
+
sourceMaps: !!options.sourceMaps,
|
|
183
150
|
sourceFileName: resourcePath,
|
|
184
151
|
retainLines: false,
|
|
185
152
|
comments: true
|
|
@@ -192,7 +159,7 @@ module.exports = function i18nIdReplaceLoader(source, map, meta) {
|
|
|
192
159
|
} catch (err) {
|
|
193
160
|
const detailedError = new Error(`${LOADER_PREFIX} [${resourcePath}] AST Processing Error: ${err.message} (Stack: ${err.stack})`);
|
|
194
161
|
|
|
195
|
-
if (err.loc) {
|
|
162
|
+
if (err.loc && err.loc.line) {
|
|
196
163
|
detailedError.message += ` at line ${err.loc.line}, column ${err.loc.column}`;
|
|
197
164
|
}
|
|
198
165
|
|
|
@@ -31,6 +31,18 @@ function urlJoin(...args) {
|
|
|
31
31
|
return args.map(part => typeof part === 'string' ? part.replace(/(^\/+|\/+$)/g, '') : '').filter(part => part !== '').join('/');
|
|
32
32
|
}
|
|
33
33
|
|
|
34
|
+
function validateAndGetFilenameTemplate(template, chunkType) {
|
|
35
|
+
if (!template || typeof template !== 'string') {
|
|
36
|
+
throw new Error(`Missing required ${chunkType} filename template in i18nIndexing options`);
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
if (!template.includes('[locale]')) {
|
|
40
|
+
throw new Error(`${chunkType} filename template must include '[locale]' placeholder: ${template}`);
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
return template;
|
|
44
|
+
}
|
|
45
|
+
|
|
34
46
|
function configI18nNumericIndexPlugin(options) {
|
|
35
47
|
if (!(options.i18nIndexing && options.i18nIndexing.enable)) {
|
|
36
48
|
return null;
|
|
@@ -38,43 +50,52 @@ function configI18nNumericIndexPlugin(options) {
|
|
|
38
50
|
|
|
39
51
|
const i18nOpts = options.i18nIndexing;
|
|
40
52
|
const cdnConfig = options.cdnMapping || {};
|
|
53
|
+
|
|
54
|
+
if (!i18nOpts.jsResourcePath) {
|
|
55
|
+
throw new Error('Missing required jsResourcePath in i18nIndexing options');
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
if (!i18nOpts.propertiesFolderPath) {
|
|
59
|
+
throw new Error('Missing required propertiesFolderPath in i18nIndexing options');
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
if (!i18nOpts.numericMapPath) {
|
|
63
|
+
throw new Error('Missing required numericMapPath in i18nIndexing options');
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
if (!i18nOpts.jsonpFunc) {
|
|
67
|
+
throw new Error('Missing required jsonpFunc in i18nIndexing options');
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
if (!i18nOpts.htmlTemplateLabel) {
|
|
71
|
+
throw new Error('Missing required htmlTemplateLabel in i18nIndexing options');
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
if (!i18nOpts.localeVarName) {
|
|
75
|
+
throw new Error('Missing required localeVarName in i18nIndexing options');
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
console.log('I18n Indexing Options:', {
|
|
79
|
+
enable: i18nOpts.enable,
|
|
80
|
+
jsResourcePath: i18nOpts.jsResourcePath,
|
|
81
|
+
propertiesFolderPath: i18nOpts.propertiesFolderPath,
|
|
82
|
+
numericMapPath: i18nOpts.numericMapPath,
|
|
83
|
+
numericFilenameTemplate: i18nOpts.numericFilenameTemplate,
|
|
84
|
+
dynamicFilenameTemplate: i18nOpts.dynamicFilenameTemplate,
|
|
85
|
+
jsonpFunc: i18nOpts.jsonpFunc,
|
|
86
|
+
htmlTemplateLabel: i18nOpts.htmlTemplateLabel,
|
|
87
|
+
localeVarName: i18nOpts.localeVarName
|
|
88
|
+
});
|
|
41
89
|
const {
|
|
42
90
|
locales,
|
|
43
91
|
allI18nObject
|
|
44
92
|
} = readI18nValues({
|
|
45
93
|
jsResource: i18nOpts.jsResourcePath,
|
|
46
94
|
propertiesFolder: i18nOpts.propertiesFolderPath,
|
|
47
|
-
disableDefault:
|
|
95
|
+
disableDefault: false
|
|
48
96
|
});
|
|
49
|
-
const
|
|
50
|
-
const
|
|
51
|
-
let numericFilenameTemplate = i18nOpts.numericFilenameTemplate || hardcodedDefaultNumericTemplate;
|
|
52
|
-
let dynamicFilenameTemplate = i18nOpts.dynamicFilenameTemplate || hardcodedDefaultDynamicTemplate;
|
|
53
|
-
|
|
54
|
-
if (i18nOpts.numericFilenameTemplate === undefined) {
|
|
55
|
-
// Check if explicitly undefined or missing
|
|
56
|
-
const i18nSplitFilename = options.i18nChunkSplit && options.i18nChunkSplit.filename;
|
|
57
|
-
|
|
58
|
-
if (i18nSplitFilename && typeof i18nSplitFilename === 'string') {
|
|
59
|
-
// This adaptation logic might need to be more robust
|
|
60
|
-
numericFilenameTemplate = `i18n-chunks/[locale]/${path.basename(i18nSplitFilename).replace(/(\.i18n)?\.js$/, '.numeric.i18n.js')}`;
|
|
61
|
-
} else {
|
|
62
|
-
numericFilenameTemplate = hardcodedDefaultNumericTemplate;
|
|
63
|
-
}
|
|
64
|
-
}
|
|
65
|
-
|
|
66
|
-
if (i18nOpts.dynamicFilenameTemplate === undefined) {
|
|
67
|
-
const i18nSplitFilename = options.i18nChunkSplit && options.i18nChunkSplit.filename;
|
|
68
|
-
|
|
69
|
-
if (i18nSplitFilename && typeof i18nSplitFilename === 'string') {
|
|
70
|
-
dynamicFilenameTemplate = `i18n-chunks/[locale]/${path.basename(i18nSplitFilename).replace(/(\.i18n)?\.js$/, '.dynamic.i18n.js')}`;
|
|
71
|
-
} else {
|
|
72
|
-
dynamicFilenameTemplate = hardcodedDefaultDynamicTemplate;
|
|
73
|
-
}
|
|
74
|
-
}
|
|
75
|
-
|
|
76
|
-
const htmlTemplateLabel = i18nOpts.htmlTemplateLabel || '{{--user-locale}}';
|
|
77
|
-
const defaultJsonpFunc = options.i18nChunkSplit && options.i18nChunkSplit.jsonpFunc || 'window.loadI18nChunk';
|
|
97
|
+
const numericFilenameTemplate = validateAndGetFilenameTemplate(i18nOpts.numericFilenameTemplate, 'numericFilenameTemplate');
|
|
98
|
+
const dynamicFilenameTemplate = validateAndGetFilenameTemplate(i18nOpts.dynamicFilenameTemplate, 'dynamicFilenameTemplate');
|
|
78
99
|
let i18nAssetsPublicPathPrefix = '';
|
|
79
100
|
|
|
80
101
|
if (cdnConfig.isCdnEnabled) {
|
|
@@ -90,24 +111,32 @@ function configI18nNumericIndexPlugin(options) {
|
|
|
90
111
|
i18nAssetsPublicPathPrefix = urlConcat(options.publicPath === undefined ? '' : options.publicPath);
|
|
91
112
|
}
|
|
92
113
|
|
|
93
|
-
const
|
|
114
|
+
const numericIndexPluginOptions = {
|
|
115
|
+
enable: i18nOpts.enable,
|
|
116
|
+
jsResourcePath: i18nOpts.jsResourcePath,
|
|
117
|
+
propertiesFolderPath: i18nOpts.propertiesFolderPath,
|
|
118
|
+
numericMapPath: i18nOpts.numericMapPath,
|
|
94
119
|
locales,
|
|
95
120
|
allI18nObject,
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
dynamicFilenameTemplate,
|
|
102
|
-
// This will now include "i18n-chunks/[locale]/"
|
|
103
|
-
moduleType: 'i18n/mini-extract',
|
|
104
|
-
mainChunkName: options.i18nChunkSplit && options.i18nChunkSplit.mainChunkName || 'main',
|
|
105
|
-
htmlTemplateLabel,
|
|
106
|
-
localeVarName: i18nOpts.localeVarName
|
|
121
|
+
numericFilenameTemplate: numericFilenameTemplate,
|
|
122
|
+
dynamicFilenameTemplate: dynamicFilenameTemplate,
|
|
123
|
+
numericJsonpFunction: i18nOpts.jsonpFunc,
|
|
124
|
+
dynamicJsonpFunction: i18nOpts.jsonpFunc,
|
|
125
|
+
moduleType: 'i18n/mini-extract'
|
|
107
126
|
};
|
|
108
|
-
const htmlInjectorOptions = {
|
|
127
|
+
const htmlInjectorOptions = {
|
|
128
|
+
locales,
|
|
129
|
+
numericFilenameTemplate: numericFilenameTemplate,
|
|
130
|
+
numericJsonpFunc: i18nOpts.jsonpFunc,
|
|
131
|
+
dynamicFilenameTemplate: dynamicFilenameTemplate,
|
|
132
|
+
dynamicJsonpFunc: i18nOpts.jsonpFunc,
|
|
133
|
+
mainChunkName: options.i18nChunkSplit && options.i18nChunkSplit.mainChunkName || 'main',
|
|
134
|
+
htmlTemplateLabel: i18nOpts.htmlTemplateLabel,
|
|
135
|
+
localeVarName: i18nOpts.localeVarName,
|
|
109
136
|
i18nAssetsPublicPathPrefix: i18nAssetsPublicPathPrefix,
|
|
110
137
|
cspNoncePlaceholder: i18nOpts.cspNoncePlaceholder || '{{--CSP-nonce}}'
|
|
111
138
|
};
|
|
112
|
-
|
|
139
|
+
const i18nNumericPluginInstance = new I18nNumericIndexPlugin(numericIndexPluginOptions);
|
|
140
|
+
const htmlInjectorPluginInstance = new I18nNumericIndexHtmlInjectorPlugin(htmlInjectorOptions);
|
|
141
|
+
return [i18nNumericPluginInstance, htmlInjectorPluginInstance];
|
|
113
142
|
}
|