@zohodesk/client_build_tool 0.0.11-exp.18 → 0.0.11-exp.18.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.
Files changed (28) hide show
  1. package/CHANGELOG.md +28 -0
  2. package/README.md +130 -0
  3. package/README_backup.md +102 -0
  4. package/docs/DYNAMIC_TEMPLATE_EXAMPLE.md +129 -0
  5. package/docs/I18N_NUMERIC_INDEXING_PLUGIN.md +225 -0
  6. package/docs/I18N_SINGLE_FILE_MODE.md +126 -0
  7. package/docs/client_build_tool_source_doc.md +390 -0
  8. package/lib/schemas/defaultConfigValues.js +46 -1
  9. package/lib/schemas/defaultConfigValuesOnly.js +31 -4
  10. package/lib/shared/babel/getBabelPlugin.js +3 -4
  11. package/lib/shared/babel/runBabelForTsFile.js +1 -1
  12. package/lib/shared/bundler/webpack/custom_plugins/I18nNumericIndexPlugin/I18nGroupRuntimeModule.js +139 -0
  13. package/lib/shared/bundler/webpack/custom_plugins/I18nNumericIndexPlugin/I18nNumericIndexHtmlInjectorPlugin.js +98 -0
  14. package/lib/shared/bundler/webpack/custom_plugins/I18nNumericIndexPlugin/I18nNumericIndexPlugin.js +399 -0
  15. package/lib/shared/bundler/webpack/custom_plugins/I18nNumericIndexPlugin/utils/i18nDataLoader.js +141 -0
  16. package/lib/shared/bundler/webpack/custom_plugins/I18nSplitPlugin/utils/propertiesUtils.js +19 -1
  17. package/lib/shared/bundler/webpack/jsLoaders.js +24 -1
  18. package/lib/shared/bundler/webpack/loaderConfigs/i18nIdReplaceLoaderConfig.js +90 -0
  19. package/lib/shared/bundler/webpack/loaders/i18nIdReplaceLoader.js +146 -0
  20. package/lib/shared/bundler/webpack/pluginConfigs/configI18nIndexingPlugin.js +42 -0
  21. package/lib/shared/bundler/webpack/pluginConfigs/configI18nNumericHtmlInjector.js +92 -0
  22. package/lib/shared/bundler/webpack/pluginConfigs/configI18nNumericIndexPlugin.js +112 -0
  23. package/lib/shared/bundler/webpack/plugins.js +3 -1
  24. package/lib/shared/postcss/custom_postcss_plugins/VariableModificationPlugin/index.js +1 -1
  25. package/lib/shared/server/mockApiHandler.js +7 -0
  26. package/lib/shared/server/urlConcat.js +4 -3
  27. package/npm-shrinkwrap.json +8225 -32
  28. package/package.json +6 -5
@@ -0,0 +1,141 @@
1
+ "use strict";
2
+
3
+ const fs = require('fs');
4
+
5
+ const path = require('path');
6
+
7
+ const {
8
+ getPropertiesAsJSON
9
+ } = require('../../I18nSplitPlugin/utils/propertiesUtils');
10
+ /**
11
+ * Load and parse a properties file
12
+ */
13
+
14
+
15
+ function loadPropertiesFile(filePath, compilation, description) {
16
+ try {
17
+ const parsed = getPropertiesAsJSON(filePath);
18
+ return parsed;
19
+ } catch (err) {
20
+ if (compilation) {
21
+ compilation.errors.push(new Error(`I18nNumericIndexPlugin: Error loading ${description}: ${err.message}`));
22
+ }
23
+
24
+ return {};
25
+ }
26
+ }
27
+ /**
28
+ * Load numeric mapping from JSON file
29
+ */
30
+
31
+
32
+ function loadNumericMap(numericMapPath, compilation) {
33
+ try {
34
+ const fileContent = fs.readFileSync(numericMapPath, 'utf-8');
35
+ const parsedData = JSON.parse(fileContent);
36
+ let numericMap;
37
+ let totalKeys; // Handle both wrapped and flat formats
38
+
39
+ if (parsedData.originalKeyToNumericId) {
40
+ // New format with metadata
41
+ numericMap = parsedData.originalKeyToNumericId;
42
+ totalKeys = parsedData.totalKeysInMap || Object.keys(numericMap).length;
43
+ } else {
44
+ // Flat format - use directly
45
+ numericMap = parsedData;
46
+ totalKeys = Object.keys(numericMap).length;
47
+ } // Create sorted array for numeric ID lookups
48
+
49
+
50
+ const maxId = Math.max(...Object.values(numericMap));
51
+ const sortedKeys = new Array(maxId + 1);
52
+ Object.entries(numericMap).forEach(([key, id]) => {
53
+ sortedKeys[id] = key;
54
+ });
55
+ return {
56
+ sortedKeys,
57
+ totalKeys
58
+ };
59
+ } catch (err) {
60
+ if (compilation) {
61
+ compilation.errors.push(new Error(`I18nNumericIndexPlugin: Error loading numeric map: ${err.message}`));
62
+ }
63
+
64
+ return {
65
+ sortedKeys: [],
66
+ totalKeys: 0
67
+ };
68
+ }
69
+ }
70
+ /**
71
+ * Load all locale files from properties directory
72
+ */
73
+
74
+
75
+ function loadAllLocaleFiles(propertiesPath, compilation, jsResourceBase) {
76
+ const allI18n = {};
77
+ const locales = []; // Start with English base
78
+
79
+ allI18n['en_US'] = jsResourceBase;
80
+ locales.push('en_US');
81
+
82
+ try {
83
+ const files = fs.readdirSync(propertiesPath);
84
+ files.forEach(file => {
85
+ if (!file.endsWith('.properties')) return; // Match locale-specific property files
86
+
87
+ const match = file.match(/^ApplicationResources_([a-z]{2}_[A-Z]{2})\.properties$/);
88
+
89
+ if (match) {
90
+ const locale = match[1];
91
+ const filePath = path.join(propertiesPath, file);
92
+ const localeData = loadPropertiesFile(filePath, compilation, `locale ${locale}`); // Merge with base resources
93
+
94
+ allI18n[locale] = { ...jsResourceBase,
95
+ ...localeData
96
+ };
97
+
98
+ if (!locales.includes(locale)) {
99
+ locales.push(locale);
100
+ }
101
+ }
102
+ });
103
+ } catch (err) {
104
+ if (compilation) {
105
+ compilation.errors.push(new Error(`I18nNumericIndexPlugin: Error reading properties folder: ${err.message}`));
106
+ }
107
+ }
108
+
109
+ return {
110
+ allI18n,
111
+ locales
112
+ };
113
+ }
114
+ /**
115
+ * Main loader function for i18n data
116
+ */
117
+
118
+
119
+ function loadI18nData(options, compilation) {
120
+ const jsResourcePath = path.resolve(compilation.compiler.context, options.jsResourcePath);
121
+ const propertiesPath = path.resolve(compilation.compiler.context, options.propertiesFolderPath); // Load base JS resources
122
+
123
+ const jsResourceBase = loadPropertiesFile(jsResourcePath, compilation, 'JS resources'); // Load all locale files
124
+
125
+ const {
126
+ allI18n,
127
+ locales
128
+ } = loadAllLocaleFiles(propertiesPath, compilation, jsResourceBase);
129
+ return {
130
+ jsResourceBase,
131
+ allI18n,
132
+ locales
133
+ };
134
+ }
135
+
136
+ module.exports = {
137
+ loadPropertiesFile,
138
+ loadNumericMap,
139
+ loadAllLocaleFiles,
140
+ loadI18nData
141
+ };
@@ -3,6 +3,7 @@
3
3
  Object.defineProperty(exports, "__esModule", {
4
4
  value: true
5
5
  });
6
+ exports.decodeUnicodeEscapes = decodeUnicodeEscapes;
6
7
  exports.getAllI18n = getAllI18n;
7
8
  exports.getPropertiesAsJSON = getPropertiesAsJSON;
8
9
  exports.jsonToString = jsonToString;
@@ -16,6 +17,21 @@ var _constants = require("../../../../../constants");
16
17
  function isComment(line) {
17
18
  return line[0] === '#';
18
19
  }
20
+ /**
21
+ * Decode Unicode escape sequences in a string
22
+ * Converts \uXXXX to the actual character
23
+ */
24
+
25
+
26
+ function decodeUnicodeEscapes(str) {
27
+ if (typeof str !== 'string') {
28
+ return str;
29
+ }
30
+
31
+ return str.replace(/\\u([0-9a-fA-F]{4})/g, (match, hex) => {
32
+ return String.fromCharCode(parseInt(hex, 16));
33
+ });
34
+ }
19
35
 
20
36
  function getPropertiesAsJSON(filePath) {
21
37
  try {
@@ -31,9 +47,11 @@ function getPropertiesAsJSON(filePath) {
31
47
 
32
48
  const ind = line.indexOf('=');
33
49
  const key = line.slice(0, ind).replace(/\\ /g, ' ');
34
- const value = line.slice(ind + 1);
50
+ let value = line.slice(ind + 1);
35
51
 
36
52
  if (key && value) {
53
+ // Decode Unicode escapes in the value
54
+ value = decodeUnicodeEscapes(value);
37
55
  i18nObj[key] = value;
38
56
  }
39
57
  }, {});
@@ -7,11 +7,34 @@ exports.jsLoaders = jsLoaders;
7
7
 
8
8
  var _babelLoaderConfig = require("./loaderConfigs/babelLoaderConfig");
9
9
 
10
+ const {
11
+ i18nIdReplaceLoaderConfig
12
+ } = require('./loaderConfigs/i18nIdReplaceLoaderConfig');
13
+
10
14
  function jsLoaders(options) {
15
+ const useLoaders = []; // Always add babel loader first
16
+
17
+ useLoaders.push((0, _babelLoaderConfig.babelLoaderConfig)(options)); // Add i18n ID replace loader if numeric indexing is enabled
18
+
19
+ const shouldUseNumericIndexing = options.i18nIndexing && options.i18nIndexing.enable || options.i18nChunkSplit && options.i18nChunkSplit.chunkSplitEnable && options.i18nChunkSplit.useNumericIndexing;
20
+
21
+ if (shouldUseNumericIndexing) {
22
+ try {
23
+ const loaderConfig = i18nIdReplaceLoaderConfig(options, options.context);
24
+
25
+ if (loaderConfig) {
26
+ useLoaders.push(loaderConfig);
27
+ }
28
+ } catch (err) {
29
+ // Silently skip if configuration fails
30
+ console.warn('[jsLoaders] Failed to configure i18n ID replace loader:', err.message);
31
+ }
32
+ }
33
+
11
34
  return [{
12
35
  test: /\.js$/,
13
36
  exclude: /node_modules/,
14
- use: [(0, _babelLoaderConfig.babelLoaderConfig)(options)] // include: path.join(appPath, folder)
37
+ use: useLoaders // include: path.join(appPath, folder)
15
38
 
16
39
  }];
17
40
  }
@@ -0,0 +1,90 @@
1
+ "use strict";
2
+
3
+ const {
4
+ getPropertiesAsJSON
5
+ } = require('../custom_plugins/I18nSplitPlugin/utils/propertiesUtils');
6
+
7
+ const fs = require('fs');
8
+
9
+ function loadJSResourcesOnce(options) {
10
+ let jsResourcePath; // Determine the JSResource path based on configuration
11
+
12
+ if (options.i18nIndexing && options.i18nIndexing.enable) {
13
+ jsResourcePath = options.i18nIndexing.jsResourcePath;
14
+ } else if (options.i18nChunkSplit && options.i18nChunkSplit.chunkSplitEnable && options.i18nChunkSplit.useNumericIndexing) {
15
+ jsResourcePath = options.i18nChunkSplit.jsResource;
16
+ } else {
17
+ throw new Error('i18nIdReplaceLoader requires either i18nIndexing to be enabled or i18nChunkSplit with useNumericIndexing');
18
+ }
19
+
20
+ if (!jsResourcePath) {
21
+ throw new Error('Missing required jsResourcePath in i18n options');
22
+ }
23
+
24
+ try {
25
+ const i18nData = getPropertiesAsJSON(jsResourcePath);
26
+
27
+ if (Object.keys(i18nData).length === 0) {
28
+ console.warn(`[i18nIdReplaceLoaderConfig] Warning: No i18n data found in JSResource file: ${jsResourcePath}`);
29
+ return {};
30
+ }
31
+
32
+ return i18nData;
33
+ } catch (err) {
34
+ throw new Error(`Error reading JSResource file ${jsResourcePath}: ${err.message}`);
35
+ }
36
+ }
37
+
38
+ function readNumericMapOnce(numericMapPath) {
39
+ if (!numericMapPath) return null;
40
+
41
+ try {
42
+ if (!fs.existsSync(numericMapPath)) return null;
43
+ const fileContent = fs.readFileSync(numericMapPath, 'utf-8');
44
+ const parsed = JSON.parse(fileContent);
45
+ if (!parsed) return null;
46
+ return parsed.originalKeyToNumericId ? parsed.originalKeyToNumericId : parsed;
47
+ } catch (err) {
48
+ return null;
49
+ }
50
+ }
51
+
52
+ function i18nIdReplaceLoaderConfig(options, webpackContext) {
53
+ let numericMapPath; // Determine the numeric map path based on configuration
54
+
55
+ if (options.i18nIndexing && options.i18nIndexing.enable) {
56
+ numericMapPath = options.i18nIndexing.numericMapPath;
57
+ } else if (options.i18nChunkSplit && options.i18nChunkSplit.chunkSplitEnable && options.i18nChunkSplit.useNumericIndexing) {
58
+ numericMapPath = options.i18nChunkSplit.numericMapPath;
59
+ } else {
60
+ throw new Error('i18nIdReplaceLoader requires either i18nIndexing to be enabled or i18nChunkSplit with useNumericIndexing');
61
+ }
62
+
63
+ if (!numericMapPath) {
64
+ throw new Error('numericMapPath is required in i18nIndexing or i18nChunkSplit config');
65
+ } // Load all i18n data for key detection
66
+
67
+
68
+ const allI18nData = loadJSResourcesOnce(options);
69
+
70
+ const i18nKeyReplaceLoaderPath = require.resolve('../loaders/i18nIdReplaceLoader.js');
71
+
72
+ const numericIdMap = readNumericMapOnce(numericMapPath);
73
+ const loaderOptions = {
74
+ allI18nData: allI18nData,
75
+ sourceMaps: false,
76
+ numericMapPath: numericMapPath,
77
+ numericIdMap: numericIdMap || undefined,
78
+ devMode: options.i18nIndexing?.devMode || false,
79
+ includePaths: options.i18nIndexing?.loaderOptions?.includePaths || [],
80
+ excludePaths: options.i18nIndexing?.loaderOptions?.excludePaths || ['node_modules', 'tests']
81
+ };
82
+ return {
83
+ loader: i18nKeyReplaceLoaderPath,
84
+ options: loaderOptions
85
+ };
86
+ }
87
+
88
+ module.exports = {
89
+ i18nIdReplaceLoaderConfig
90
+ };
@@ -0,0 +1,146 @@
1
+ "use strict";
2
+
3
+ const fs = require('fs');
4
+
5
+ const path = require('path');
6
+
7
+ const parser = require('@babel/parser');
8
+
9
+ const traverse = require('@babel/traverse').default;
10
+
11
+ const generator = require('@babel/generator').default;
12
+
13
+ const t = require('@babel/types');
14
+
15
+ const {
16
+ getOptions
17
+ } = require('loader-utils');
18
+
19
+ module.exports = function i18nIdReplaceLoader(source, map) {
20
+ const resourcePath = this.resourcePath;
21
+ this.cacheable && this.cacheable();
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
+
33
+
34
+ if (options.excludePaths) {
35
+ const shouldExclude = options.excludePaths.some(excludePath => resourcePath.includes(excludePath));
36
+
37
+ if (shouldExclude) {
38
+ return callback(null, source, map);
39
+ }
40
+ } // Only process files in included paths if specified
41
+
42
+
43
+ if (options.includePaths && options.includePaths.length > 0) {
44
+ const shouldInclude = options.includePaths.some(includePath => resourcePath.includes(includePath));
45
+
46
+ if (!shouldInclude) {
47
+ return callback(null, source, map);
48
+ }
49
+ } // Validate i18n data exists
50
+
51
+
52
+ if (!options.allI18nData || Object.keys(options.allI18nData).length === 0) {
53
+ 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)
55
+
56
+
57
+ let numericIdMap = options.numericIdMap || null;
58
+
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
+ }
65
+
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
91
+ }
92
+ }
93
+ } catch (e) {// ignore
94
+ } // If no numeric map available, return source as-is
95
+
96
+
97
+ if (!numericIdMap) {
98
+ return callback(null, source, map);
99
+ }
100
+
101
+ const isDevMode = options.devMode || process.env.NODE_ENV === 'development';
102
+
103
+ try {
104
+ // Parse the JavaScript/TypeScript source code
105
+ const ast = parser.parse(source, {
106
+ sourceType: 'module',
107
+ plugins: ['jsx', 'typescript', 'classProperties', 'optionalChaining', 'nullishCoalescingOperator'],
108
+ sourceFilename: resourcePath
109
+ });
110
+ let hasTransformations = false; // Traverse AST and replace i18n keys with numeric IDs
111
+
112
+ traverse(ast, {
113
+ StringLiteral(path) {
114
+ const {
115
+ 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
+
122
+
123
+ if (numericIdMap.hasOwnProperty(node.value)) {
124
+ const numericId = String(numericIdMap[node.value]);
125
+ path.replaceWith(t.stringLiteral(numericId));
126
+ hasTransformations = true;
127
+ }
128
+ }
129
+
130
+ }); // Generate code if transformations were made
131
+
132
+ if (hasTransformations) {
133
+ const output = generator(ast, {
134
+ sourceMaps: !!options.sourceMaps,
135
+ sourceFileName: resourcePath,
136
+ retainLines: false,
137
+ comments: true
138
+ }, source);
139
+ callback(null, output.code, output.map);
140
+ } else {
141
+ callback(null, source, map);
142
+ }
143
+ } catch (err) {
144
+ callback(err);
145
+ }
146
+ };
@@ -0,0 +1,42 @@
1
+ "use strict";
2
+
3
+ Object.defineProperty(exports, "__esModule", {
4
+ value: true
5
+ });
6
+ exports.default = configI18nIndexingPlugin;
7
+
8
+ var _I18nNumericIndexPlugin = _interopRequireDefault(require("../custom_plugins/I18nNumericIndexPlugin/I18nNumericIndexPlugin"));
9
+
10
+ var _resourceBasedPublicPath = require("../common/resourceBasedPublicPath");
11
+
12
+ function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { default: obj }; }
13
+
14
+ function configI18nIndexingPlugin(config) {
15
+ const {
16
+ i18nIndexing,
17
+ cdnMapping
18
+ } = config;
19
+
20
+ if (!i18nIndexing || !i18nIndexing.enable) {
21
+ return null;
22
+ } // Get the public path for i18n resources
23
+
24
+
25
+ const i18nPublicPath = (0, _resourceBasedPublicPath.resourceBasedPublicPath)('i18n', config); // Get the CDN template for i18n resources if CDN is enabled
26
+
27
+ const i18nCdnTemplate = cdnMapping && cdnMapping.isCdnEnabled ? cdnMapping.i18nTemplate || cdnMapping.jsTemplate : '';
28
+ const options = {
29
+ jsResourcePath: i18nIndexing.jsResourcePath,
30
+ propertiesFolderPath: i18nIndexing.propertiesFolderPath,
31
+ numericMapPath: i18nIndexing.numericMapPath,
32
+ numericFilenameTemplate: i18nIndexing.numericFilenameTemplate || 'i18n-chunk/[locale]/numeric.i18n.js',
33
+ dynamicFilenameTemplate: i18nIndexing.dynamicFilenameTemplate || 'i18n-chunk/[locale]/dynamic.i18n.js',
34
+ jsonpFunc: i18nIndexing.jsonpFunc || 'window.loadI18nChunk',
35
+ htmlTemplateLabel: i18nIndexing.htmlTemplateLabel || '{{--user-locale}}',
36
+ localeVarName: i18nIndexing.localeVarName || 'window.userLangCode',
37
+ customGroups: i18nIndexing.customGroups || {},
38
+ publicPath: i18nPublicPath || '',
39
+ i18nCdnTemplate
40
+ };
41
+ return new _I18nNumericIndexPlugin.default(options);
42
+ }
@@ -0,0 +1,92 @@
1
+ "use strict";
2
+
3
+ Object.defineProperty(exports, "__esModule", {
4
+ value: true
5
+ });
6
+ exports.default = configI18nNumericHtmlInjector;
7
+
8
+ var _htmlWebpackPlugin = _interopRequireDefault(require("html-webpack-plugin"));
9
+
10
+ function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { default: obj }; }
11
+
12
+ const pluginName = 'I18nNumericHtmlInjectorPlugin';
13
+
14
+ class I18nNumericHtmlInjectorPlugin {
15
+ constructor(options) {
16
+ this.options = options;
17
+ }
18
+
19
+ apply(compiler) {
20
+ compiler.hooks.thisCompilation.tap(pluginName, compilation => {
21
+ // Hook into HtmlWebpackPlugin to inject i18n script tags
22
+ _htmlWebpackPlugin.default.getHooks(compilation).beforeAssetTagGeneration.tap(pluginName, hookData => {
23
+ const {
24
+ assets
25
+ } = hookData;
26
+ const {
27
+ numericFilenameTemplate,
28
+ dynamicFilenameTemplate,
29
+ htmlTemplateLabel,
30
+ i18nCdnTemplate,
31
+ customGroups
32
+ } = this.options;
33
+ const newI18nAssets = []; // Add numeric i18n chunk
34
+
35
+ if (numericFilenameTemplate) {
36
+ const numericFilename = numericFilenameTemplate.replace(/\[locale\]/g, htmlTemplateLabel).replace(/%5Blocale%5D/g, htmlTemplateLabel); // Don't add CDN template - HtmlWebpackPlugin handles it
37
+
38
+ newI18nAssets.push(numericFilename);
39
+ } // Add dynamic i18n chunk
40
+
41
+
42
+ if (dynamicFilenameTemplate) {
43
+ const dynamicFilename = dynamicFilenameTemplate.replace(/\[locale\]/g, htmlTemplateLabel).replace(/%5Blocale%5D/g, htmlTemplateLabel); // Don't add CDN template - HtmlWebpackPlugin handles it
44
+
45
+ newI18nAssets.push(dynamicFilename);
46
+ } // Add custom group chunks if they should be in initial HTML
47
+
48
+
49
+ if (customGroups) {
50
+ Object.entries(customGroups).forEach(([groupName, groupConfig]) => {
51
+ // Only add to initial HTML if preload is true
52
+ if (groupConfig.preload && groupConfig.filenameTemplate) {
53
+ const groupFilename = groupConfig.filenameTemplate.replace(/\[locale\]/g, htmlTemplateLabel).replace(/%5Blocale%5D/g, htmlTemplateLabel); // Don't add CDN template - HtmlWebpackPlugin handles it
54
+
55
+ newI18nAssets.push(groupFilename);
56
+ }
57
+ });
58
+ } // Prepend i18n assets to ensure they load before main bundle
59
+
60
+
61
+ if (newI18nAssets.length > 0) {
62
+ assets.js = [...newI18nAssets, ...assets.js];
63
+ }
64
+
65
+ return hookData;
66
+ });
67
+ });
68
+ }
69
+
70
+ }
71
+
72
+ function configI18nNumericHtmlInjector(config) {
73
+ const {
74
+ i18nIndexing,
75
+ cdnMapping
76
+ } = config; // Only create this plugin if i18nIndexing is enabled
77
+
78
+ if (!i18nIndexing || !i18nIndexing.enable) {
79
+ return null;
80
+ } // Get the CDN template for i18n resources if CDN is enabled
81
+
82
+
83
+ const i18nCdnTemplate = cdnMapping && cdnMapping.isCdnEnabled ? cdnMapping.i18nTemplate || cdnMapping.jsTemplate : '';
84
+ const options = {
85
+ numericFilenameTemplate: i18nIndexing.numericFilenameTemplate || 'i18n-chunk/[locale]/numeric.i18n.js',
86
+ dynamicFilenameTemplate: i18nIndexing.dynamicFilenameTemplate || 'i18n-chunk/[locale]/dynamic.i18n.js',
87
+ htmlTemplateLabel: i18nIndexing.htmlTemplateLabel || '{{--user-locale}}',
88
+ customGroups: i18nIndexing.customGroups || {},
89
+ i18nCdnTemplate
90
+ };
91
+ return new I18nNumericHtmlInjectorPlugin(options);
92
+ }