@zohodesk/client_build_tool 0.0.1-0.exp.0.0.4 → 0.0.1-0.exp.0.0.8
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 +25 -2
- package/lib/schemas/defaultConfigValuesOnly.js +12 -4
- package/lib/shared/bundler/webpack/custom_plugins/I18nNumericIndexPlugin/CLAUDE.md +0 -0
- package/lib/shared/bundler/webpack/custom_plugins/I18nNumericIndexPlugin/I18nNumericIndexPlugin.js +199 -93
- package/lib/shared/bundler/webpack/custom_plugins/I18nSplitPlugin/I18nFilesEmitPlugin.js +66 -5
- package/lib/shared/bundler/webpack/custom_plugins/I18nSplitPlugin/optionsHandler.js +3 -0
- package/lib/shared/bundler/webpack/custom_plugins/getInitialI18nAssetsArrayStr.js +6 -1
- package/lib/shared/bundler/webpack/loaderConfigs/i18nIdReplaceLoaderConfig.js +72 -48
- package/lib/shared/bundler/webpack/loaders/i18nIdReplaceLoader.js +156 -95
- package/lib/shared/bundler/webpack/pluginConfigs/configI18nNumericIndexPlugin.js +1 -1
- package/lib/shared/bundler/webpack/pluginConfigs/configI18nSplitPlugin.js +4 -1
- package/lib/shared/bundler/webpack/utils/propertiesParser.js +103 -0
- package/npm-shrinkwrap.json +8086 -21
- package/package.json +1 -1
|
@@ -4,98 +4,122 @@ const path = require('path');
|
|
|
4
4
|
|
|
5
5
|
const fs = require('fs');
|
|
6
6
|
|
|
7
|
-
|
|
7
|
+
const {
|
|
8
|
+
parseProperties
|
|
9
|
+
} = require('../utils/propertiesParser'); // Improved caching with proper cleanup
|
|
8
10
|
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
11
|
+
|
|
12
|
+
class ConfigCache {
|
|
13
|
+
constructor() {
|
|
14
|
+
this.i18nDataCache = new Map();
|
|
15
|
+
this.maxCacheSize = 10; // Limit cache size
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
getI18nData(filePath) {
|
|
19
|
+
return this.i18nDataCache.get(filePath);
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
setI18nData(filePath, data) {
|
|
23
|
+
if (this.i18nDataCache.size >= this.maxCacheSize) {
|
|
24
|
+
// Clear oldest entries
|
|
25
|
+
const firstKey = this.i18nDataCache.keys().next().value;
|
|
26
|
+
this.i18nDataCache.delete(firstKey);
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
this.i18nDataCache.set(filePath, data);
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
clear() {
|
|
33
|
+
this.i18nDataCache.clear();
|
|
12
34
|
}
|
|
13
35
|
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
const configCache = new ConfigCache();
|
|
39
|
+
|
|
40
|
+
function loadJSResourcesOnce(options, webpackContext) {
|
|
14
41
|
if (!options.i18nIndexing || !options.i18nIndexing.enable) {
|
|
15
42
|
throw new Error('i18nIdReplaceLoader requires i18nIndexing to be enabled');
|
|
16
43
|
}
|
|
17
44
|
|
|
18
45
|
if (!options.i18nIndexing.jsResourcePath) {
|
|
19
46
|
throw new Error('Missing required jsResourcePath in i18nIndexing options');
|
|
20
|
-
}
|
|
47
|
+
} // Use webpack context instead of process.cwd() for better reliability
|
|
21
48
|
|
|
22
|
-
|
|
49
|
+
|
|
50
|
+
const contextPath = webpackContext || process.cwd();
|
|
51
|
+
const propertiesFilePath = path.isAbsolute(options.i18nIndexing.jsResourcePath) ? options.i18nIndexing.jsResourcePath : path.resolve(contextPath, options.i18nIndexing.jsResourcePath); // Check cache first
|
|
52
|
+
|
|
53
|
+
const cached = configCache.getI18nData(propertiesFilePath);
|
|
54
|
+
|
|
55
|
+
if (cached) {
|
|
56
|
+
return cached;
|
|
57
|
+
}
|
|
23
58
|
|
|
24
59
|
if (!fs.existsSync(propertiesFilePath)) {
|
|
25
60
|
throw new Error(`JSResource file not found at: ${propertiesFilePath}`);
|
|
26
61
|
}
|
|
27
62
|
|
|
28
|
-
const i18nData = {};
|
|
29
|
-
|
|
30
63
|
try {
|
|
31
64
|
const data = fs.readFileSync(propertiesFilePath, {
|
|
32
65
|
encoding: 'utf-8'
|
|
33
66
|
});
|
|
34
|
-
const
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
for (let i = 0; i < trimmedLine.length; i++) {
|
|
45
|
-
if ((trimmedLine[i] === '=' || trimmedLine[i] === ':') && (i === 0 || trimmedLine[i - 1] !== '\\')) {
|
|
46
|
-
separatorIndex = i;
|
|
47
|
-
break;
|
|
48
|
-
}
|
|
49
|
-
}
|
|
50
|
-
|
|
51
|
-
if (separatorIndex > 0) {
|
|
52
|
-
let key = trimmedLine.substring(0, separatorIndex).trim();
|
|
53
|
-
const value = trimmedLine.substring(separatorIndex + 1).trim();
|
|
54
|
-
key = key.replace(/\\ /g, ' ');
|
|
55
|
-
|
|
56
|
-
if (key) {
|
|
57
|
-
i18nData[key] = value;
|
|
58
|
-
}
|
|
59
|
-
}
|
|
60
|
-
});
|
|
67
|
+
const i18nData = parseProperties(data);
|
|
68
|
+
|
|
69
|
+
if (Object.keys(i18nData).length === 0) {
|
|
70
|
+
throw new Error(`No i18n data found in JSResource file: ${propertiesFilePath}`);
|
|
71
|
+
} // Cache the result
|
|
72
|
+
|
|
73
|
+
|
|
74
|
+
configCache.setI18nData(propertiesFilePath, i18nData);
|
|
75
|
+
return i18nData;
|
|
61
76
|
} catch (err) {
|
|
62
77
|
throw new Error(`Error reading JSResource file ${propertiesFilePath}: ${err.message}`);
|
|
63
78
|
}
|
|
64
|
-
|
|
65
|
-
if (Object.keys(i18nData).length === 0) {
|
|
66
|
-
throw new Error(`No i18n data found in JSResource file: ${propertiesFilePath}`);
|
|
67
|
-
}
|
|
68
|
-
|
|
69
|
-
allI18nDataFromPropertiesCache = i18nData;
|
|
70
|
-
return allI18nDataFromPropertiesCache;
|
|
71
79
|
}
|
|
72
80
|
|
|
73
|
-
function i18nIdReplaceLoaderConfig(options) {
|
|
81
|
+
function i18nIdReplaceLoaderConfig(options, webpackContext) {
|
|
82
|
+
// Validate required options
|
|
74
83
|
if (!options.i18nIndexing || !options.i18nIndexing.enable) {
|
|
75
84
|
throw new Error('i18nIdReplaceLoader requires i18nIndexing to be enabled');
|
|
76
85
|
}
|
|
77
86
|
|
|
78
87
|
if (!options.i18nIndexing.numericMapPath) {
|
|
79
88
|
throw new Error('Missing required numericMapPath in i18nIndexing options');
|
|
80
|
-
}
|
|
89
|
+
} // Load i18n data with proper context
|
|
90
|
+
|
|
81
91
|
|
|
82
|
-
const allI18nData = loadJSResourcesOnce(options);
|
|
92
|
+
const allI18nData = loadJSResourcesOnce(options, webpackContext);
|
|
93
|
+
|
|
94
|
+
const i18nKeyReplaceLoaderPath = require.resolve('../loaders/i18nIdReplaceLoader.js'); // Enhanced loader options with better defaults
|
|
83
95
|
|
|
84
|
-
const i18nKeyReplaceLoaderPath = require.resolve('../loaders/i18nIdReplaceLoader.js');
|
|
85
96
|
|
|
86
97
|
const loaderOptions = {
|
|
87
98
|
allI18nData: allI18nData,
|
|
88
99
|
sourceMaps: !!(options.devtool && options.devtool.includes('source-map')),
|
|
89
100
|
isDebug: options.mode === 'development',
|
|
90
101
|
useNumericIndexing: true,
|
|
91
|
-
numericMapPath: options.i18nIndexing.numericMapPath
|
|
102
|
+
numericMapPath: options.i18nIndexing.numericMapPath,
|
|
103
|
+
// Additional configurable options
|
|
104
|
+
retainLines: options.i18nIndexing.retainLines || false,
|
|
105
|
+
preserveComments: options.i18nIndexing.preserveComments !== false,
|
|
106
|
+
compact: options.mode === 'production',
|
|
107
|
+
minified: options.mode === 'production',
|
|
108
|
+
// Allow custom babel plugins
|
|
109
|
+
babelPlugins: options.i18nIndexing.babelPlugins || undefined
|
|
92
110
|
};
|
|
93
111
|
return {
|
|
94
112
|
loader: i18nKeyReplaceLoaderPath,
|
|
95
113
|
options: loaderOptions
|
|
96
114
|
};
|
|
115
|
+
} // Export cache for potential cleanup
|
|
116
|
+
|
|
117
|
+
|
|
118
|
+
function clearCache() {
|
|
119
|
+
configCache.clear();
|
|
97
120
|
}
|
|
98
121
|
|
|
99
122
|
module.exports = {
|
|
100
|
-
i18nIdReplaceLoaderConfig
|
|
123
|
+
i18nIdReplaceLoaderConfig,
|
|
124
|
+
clearCache
|
|
101
125
|
};
|
|
@@ -1,6 +1,8 @@
|
|
|
1
1
|
"use strict";
|
|
2
2
|
|
|
3
|
-
const fs = require('fs');
|
|
3
|
+
const fs = require('fs').promises;
|
|
4
|
+
|
|
5
|
+
const fsSync = require('fs');
|
|
4
6
|
|
|
5
7
|
const path = require('path');
|
|
6
8
|
|
|
@@ -24,41 +26,72 @@ try {
|
|
|
24
26
|
throw new Error('[i18nIdReplaceLoader] Required dependency not found: ' + e.message);
|
|
25
27
|
}
|
|
26
28
|
|
|
27
|
-
const LOADER_PREFIX = '[i18nIdReplaceLoader]';
|
|
28
|
-
let numericIdMapDataCache = null;
|
|
29
|
-
let mapLoadAttemptedForPath = {};
|
|
29
|
+
const LOADER_PREFIX = '[i18nIdReplaceLoader]'; // Improved caching with proper cleanup
|
|
30
30
|
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
31
|
+
class LoaderCache {
|
|
32
|
+
constructor() {
|
|
33
|
+
this.numericMapCache = new Map();
|
|
34
|
+
this.astCache = new Map();
|
|
35
|
+
this.maxCacheSize = 100; // Prevent memory leaks
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
getNumericMap(mapPath) {
|
|
39
|
+
return this.numericMapCache.get(mapPath);
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
setNumericMap(mapPath, data) {
|
|
43
|
+
if (this.numericMapCache.size >= this.maxCacheSize) {
|
|
44
|
+
// Clear oldest entries
|
|
45
|
+
const firstKey = this.numericMapCache.keys().next().value;
|
|
46
|
+
this.numericMapCache.delete(firstKey);
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
this.numericMapCache.set(mapPath, data);
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
clear() {
|
|
53
|
+
this.numericMapCache.clear();
|
|
54
|
+
this.astCache.clear();
|
|
34
55
|
}
|
|
35
56
|
|
|
36
|
-
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
const loaderCache = new LoaderCache();
|
|
37
60
|
|
|
38
|
-
|
|
39
|
-
|
|
61
|
+
async function loadNumericIdMap(loaderContext, mapPath) {
|
|
62
|
+
if (!mapPath) {
|
|
63
|
+
throw new Error(`${LOADER_PREFIX} Numeric map path not provided in loader options.`);
|
|
40
64
|
}
|
|
41
65
|
|
|
42
|
-
|
|
66
|
+
const absoluteMapPath = path.isAbsolute(mapPath) ? mapPath : path.resolve(loaderContext.rootContext || process.cwd(), mapPath); // Check cache first
|
|
67
|
+
|
|
68
|
+
const cached = loaderCache.getNumericMap(absoluteMapPath);
|
|
43
69
|
|
|
44
|
-
if (
|
|
45
|
-
|
|
70
|
+
if (cached) {
|
|
71
|
+
return cached;
|
|
46
72
|
}
|
|
47
73
|
|
|
48
74
|
try {
|
|
49
|
-
|
|
50
|
-
|
|
75
|
+
// Check if file exists
|
|
76
|
+
if (!fsSync.existsSync(absoluteMapPath)) {
|
|
77
|
+
throw new Error(`Pre-generated i18n numeric map file NOT FOUND at: ${absoluteMapPath}`);
|
|
78
|
+
} // Read file asynchronously for better performance
|
|
79
|
+
|
|
80
|
+
|
|
81
|
+
const fileContent = await fs.readFile(absoluteMapPath, 'utf-8');
|
|
82
|
+
const parsedData = JSON.parse(fileContent); // Validate map structure
|
|
51
83
|
|
|
52
84
|
if (!parsedData || !parsedData.originalKeyToNumericId || typeof parsedData.originalKeyToNumericId !== 'object') {
|
|
53
|
-
throw new Error(
|
|
85
|
+
throw new Error(`Pre-generated map file (${absoluteMapPath}) is invalid or does not contain 'originalKeyToNumericId'`);
|
|
54
86
|
}
|
|
55
87
|
|
|
56
|
-
|
|
88
|
+
const numericIdMap = parsedData.originalKeyToNumericId; // Cache the result
|
|
89
|
+
|
|
90
|
+
loaderCache.setNumericMap(absoluteMapPath, numericIdMap);
|
|
91
|
+
return numericIdMap;
|
|
57
92
|
} catch (err) {
|
|
58
93
|
throw new Error(`${LOADER_PREFIX} Error loading or parsing pre-generated i18n map from ${absoluteMapPath}: ${err.message}`);
|
|
59
94
|
}
|
|
60
|
-
|
|
61
|
-
return numericIdMapDataCache;
|
|
62
95
|
}
|
|
63
96
|
|
|
64
97
|
module.exports = function i18nIdReplaceLoader(source, map, meta) {
|
|
@@ -66,103 +99,131 @@ module.exports = function i18nIdReplaceLoader(source, map, meta) {
|
|
|
66
99
|
this.cacheable && this.cacheable();
|
|
67
100
|
const options = getOptions(this) || {};
|
|
68
101
|
const callback = this.async();
|
|
69
|
-
const loaderContext = this;
|
|
102
|
+
const loaderContext = this; // Validate required options
|
|
70
103
|
|
|
71
104
|
if (!options.allI18nData || typeof options.allI18nData !== 'object' || Object.keys(options.allI18nData).length === 0) {
|
|
72
|
-
|
|
105
|
+
return callback(new Error(`${LOADER_PREFIX} [${resourcePath}] 'allI18nData' option is missing or empty.`));
|
|
73
106
|
}
|
|
74
107
|
|
|
75
108
|
if (!options.useNumericIndexing) {
|
|
76
|
-
|
|
77
|
-
}
|
|
109
|
+
return callback(new Error(`${LOADER_PREFIX} [${resourcePath}] 'useNumericIndexing' must be enabled.`));
|
|
110
|
+
} // Load numeric map asynchronously
|
|
111
|
+
|
|
112
|
+
|
|
113
|
+
loadNumericIdMap(this, options.numericMapPath).then(numericIdMap => {
|
|
114
|
+
try {
|
|
115
|
+
// Configure parser with better defaults and configurability
|
|
116
|
+
const parserOptions = {
|
|
117
|
+
sourceType: 'module',
|
|
118
|
+
plugins: options.babelPlugins || ['jsx', 'typescript', 'classProperties', 'optionalChaining', 'nullishCoalescingOperator', 'objectRestSpread', 'dynamicImport', 'decorators-legacy', 'asyncGenerators', 'bigInt', 'dynamicImport', 'exportDefaultFrom', 'exportNamespaceFrom', 'functionBind', 'importMeta', 'numericSeparator', 'optionalCatchBinding', 'throwExpressions', 'topLevelAwait'],
|
|
119
|
+
attachComment: true,
|
|
120
|
+
sourceFilename: resourcePath,
|
|
121
|
+
allowImportExportEverywhere: true,
|
|
122
|
+
allowAwaitOutsideFunction: true,
|
|
123
|
+
allowReturnOutsideFunction: true,
|
|
124
|
+
ranges: false,
|
|
125
|
+
tokens: false
|
|
126
|
+
};
|
|
127
|
+
const astFile = parser.parse(source, parserOptions);
|
|
128
|
+
const astProgram = astFile.program;
|
|
129
|
+
const comments = astFile.comments || [];
|
|
130
|
+
const {
|
|
131
|
+
literalKeys,
|
|
132
|
+
commentKeys
|
|
133
|
+
} = collectAndCategorizeUsedI18nKeys(astProgram, comments, options.allI18nData, options.isDebug); // Store keys in module build info for plugin consumption
|
|
134
|
+
|
|
135
|
+
if (this._module) {
|
|
136
|
+
if (!this._module.buildInfo) {
|
|
137
|
+
this._module.buildInfo = {};
|
|
138
|
+
}
|
|
78
139
|
|
|
79
|
-
|
|
140
|
+
if (literalKeys.size > 0) {
|
|
141
|
+
this._module.buildInfo.loaderIdentifiedLiteralI18nKeys = Array.from(literalKeys);
|
|
142
|
+
}
|
|
80
143
|
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
attachComment: true,
|
|
86
|
-
sourceFilename: resourcePath
|
|
87
|
-
};
|
|
88
|
-
const astFile = parser.parse(source, parserOptions);
|
|
89
|
-
const astProgram = astFile.program;
|
|
90
|
-
const comments = astFile.comments || [];
|
|
91
|
-
const {
|
|
92
|
-
literalKeys,
|
|
93
|
-
commentKeys
|
|
94
|
-
} = collectAndCategorizeUsedI18nKeys(astProgram, comments, options.allI18nData, options.isDebug);
|
|
95
|
-
|
|
96
|
-
if (this._module) {
|
|
97
|
-
if (!this._module.buildInfo) {
|
|
98
|
-
this._module.buildInfo = {};
|
|
99
|
-
}
|
|
144
|
+
if (commentKeys.size > 0) {
|
|
145
|
+
this._module.buildInfo.loaderIdentifiedCommentI18nKeys = Array.from(commentKeys);
|
|
146
|
+
}
|
|
147
|
+
} // Early return if no replacements needed
|
|
100
148
|
|
|
101
|
-
if (literalKeys.size > 0) {
|
|
102
|
-
this._module.buildInfo.loaderIdentifiedLiteralI18nKeys = Array.from(literalKeys);
|
|
103
|
-
}
|
|
104
149
|
|
|
105
|
-
if (
|
|
106
|
-
|
|
150
|
+
if (literalKeys.size === 0) {
|
|
151
|
+
return callback(null, source, map);
|
|
107
152
|
}
|
|
108
|
-
}
|
|
109
|
-
|
|
110
|
-
if (literalKeys.size === 0) {
|
|
111
|
-
return callback(null, source, map);
|
|
112
|
-
}
|
|
113
153
|
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
enter: function (node, parent, prop, index) {
|
|
117
|
-
const walkerControl = this;
|
|
154
|
+
let replacementMade = false;
|
|
155
|
+
let replacementCount = 0; // Process AST and replace string literals with numeric IDs
|
|
118
156
|
|
|
119
|
-
|
|
120
|
-
|
|
157
|
+
walk(astProgram, {
|
|
158
|
+
enter: function (node, parent, prop, index) {
|
|
159
|
+
const walkerControl = this;
|
|
121
160
|
|
|
122
|
-
if (
|
|
123
|
-
const
|
|
161
|
+
if ((node.type === 'Literal' || node.type === 'StringLiteral') && typeof node.value === 'string') {
|
|
162
|
+
const originalValue = node.value;
|
|
124
163
|
|
|
125
|
-
if (
|
|
126
|
-
const
|
|
127
|
-
type: 'NumericLiteral',
|
|
128
|
-
value: numericId
|
|
129
|
-
};
|
|
130
|
-
let replacementNode = numericLiteralNode;
|
|
164
|
+
if (literalKeys.has(originalValue)) {
|
|
165
|
+
const numericId = numericIdMap[originalValue];
|
|
131
166
|
|
|
132
|
-
if (
|
|
133
|
-
|
|
134
|
-
type: '
|
|
135
|
-
|
|
167
|
+
if (numericId !== undefined) {
|
|
168
|
+
const numericLiteralNode = {
|
|
169
|
+
type: 'NumericLiteral',
|
|
170
|
+
value: numericId,
|
|
171
|
+
raw: String(numericId)
|
|
136
172
|
};
|
|
173
|
+
let replacementNode = numericLiteralNode; // Handle JSX attributes specially
|
|
174
|
+
|
|
175
|
+
if (parent && parent.type === 'JSXAttribute' && parent.value === node) {
|
|
176
|
+
replacementNode = {
|
|
177
|
+
type: 'JSXExpressionContainer',
|
|
178
|
+
expression: numericLiteralNode
|
|
179
|
+
};
|
|
180
|
+
}
|
|
181
|
+
|
|
182
|
+
walkerControl.replace(replacementNode);
|
|
183
|
+
replacementMade = true;
|
|
184
|
+
replacementCount++;
|
|
137
185
|
}
|
|
138
|
-
|
|
139
|
-
walkerControl.replace(replacementNode);
|
|
140
|
-
replacementMade = true;
|
|
141
186
|
}
|
|
142
187
|
}
|
|
143
188
|
}
|
|
189
|
+
}); // Generate output only if replacements were made
|
|
190
|
+
|
|
191
|
+
if (replacementMade) {
|
|
192
|
+
const generateOptions = {
|
|
193
|
+
sourceMaps: !!options.sourceMaps,
|
|
194
|
+
sourceFileName: resourcePath,
|
|
195
|
+
retainLines: options.retainLines || false,
|
|
196
|
+
comments: options.preserveComments !== false,
|
|
197
|
+
compact: options.compact || false,
|
|
198
|
+
minified: options.minified || false
|
|
199
|
+
};
|
|
200
|
+
const output = generator(astFile, generateOptions, source); // Debug logging if enabled
|
|
201
|
+
|
|
202
|
+
if (options.isDebug) {
|
|
203
|
+
console.log(`${LOADER_PREFIX} [${resourcePath}] Replaced ${replacementCount} i18n keys with numeric IDs`);
|
|
204
|
+
}
|
|
205
|
+
|
|
206
|
+
callback(null, output.code, options.sourceMaps && output.map ? output.map : map);
|
|
207
|
+
} else {
|
|
208
|
+
callback(null, source, map);
|
|
144
209
|
}
|
|
145
|
-
})
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
const generateOptions = {
|
|
149
|
-
sourceMaps: !!options.sourceMaps,
|
|
150
|
-
sourceFileName: resourcePath,
|
|
151
|
-
retainLines: false,
|
|
152
|
-
comments: true
|
|
153
|
-
};
|
|
154
|
-
const output = generator(astFile, generateOptions, source);
|
|
155
|
-
callback(null, output.code, options.sourceMaps && output.map ? output.map : map);
|
|
156
|
-
} else {
|
|
157
|
-
callback(null, source, map);
|
|
158
|
-
}
|
|
159
|
-
} catch (err) {
|
|
160
|
-
const detailedError = new Error(`${LOADER_PREFIX} [${resourcePath}] AST Processing Error: ${err.message} (Stack: ${err.stack})`);
|
|
210
|
+
} catch (err) {
|
|
211
|
+
// Enhanced error handling with better context
|
|
212
|
+
const detailedError = new Error(`${LOADER_PREFIX} [${resourcePath}] AST Processing Error: ${err.message}`);
|
|
161
213
|
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
|
|
214
|
+
if (err.loc && err.loc.line) {
|
|
215
|
+
detailedError.message += ` at line ${err.loc.line}, column ${err.loc.column}`;
|
|
216
|
+
} // Add stack trace in debug mode
|
|
165
217
|
|
|
166
|
-
|
|
167
|
-
|
|
218
|
+
|
|
219
|
+
if (options.isDebug && err.stack) {
|
|
220
|
+
detailedError.message += `\nStack: ${err.stack}`;
|
|
221
|
+
}
|
|
222
|
+
|
|
223
|
+
callback(detailedError);
|
|
224
|
+
}
|
|
225
|
+
}).catch(err => {
|
|
226
|
+
// Handle async errors from map loading
|
|
227
|
+
callback(new Error(`${LOADER_PREFIX} [${resourcePath}] Failed to load numeric map: ${err.message}`));
|
|
228
|
+
});
|
|
168
229
|
};
|
|
@@ -7,7 +7,7 @@ exports.configI18nNumericIndexPlugin = configI18nNumericIndexPlugin;
|
|
|
7
7
|
|
|
8
8
|
var {
|
|
9
9
|
I18nNumericIndexPlugin
|
|
10
|
-
} = require("
|
|
10
|
+
} = require("../custom_plugins/I18nNumericIndexPlugin/I18nNumericIndexPlugin");
|
|
11
11
|
|
|
12
12
|
var {
|
|
13
13
|
I18nNumericIndexHtmlInjectorPlugin
|
|
@@ -36,6 +36,9 @@ function configI18nSplitPlugin(options) {
|
|
|
36
36
|
publicPath: i18nPublicPath,
|
|
37
37
|
i18nManifestFileName: (0, _nameTemplates.nameTemplates)('i18nmanifest', options),
|
|
38
38
|
// template: (object, locale) => `window.loadI18n(${JSON.stringify(object)}, ${JSON.stringify(locale)})`,
|
|
39
|
-
propertiesFolder: i18nChunkSplit.propertiesFolder
|
|
39
|
+
propertiesFolder: i18nChunkSplit.propertiesFolder,
|
|
40
|
+
// NEW OPTIONS FOR NUMERIC INDEXING
|
|
41
|
+
useNumericIndexing: i18nChunkSplit.useNumericIndexing,
|
|
42
|
+
numericMapPath: i18nChunkSplit.numericMapPath
|
|
40
43
|
});
|
|
41
44
|
}
|
|
@@ -0,0 +1,103 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
/**
|
|
3
|
+
* Shared properties file parsing utility
|
|
4
|
+
* Handles consistent parsing across all i18n tools
|
|
5
|
+
*/
|
|
6
|
+
// Decode Unicode escape sequences (for values only)
|
|
7
|
+
|
|
8
|
+
function decodeUnicodeEscapes(str) {
|
|
9
|
+
if (typeof str !== 'string') {
|
|
10
|
+
return str;
|
|
11
|
+
}
|
|
12
|
+
|
|
13
|
+
return str.replace(/\\u([0-9a-fA-F]{4})/g, (match, hex) => {
|
|
14
|
+
return String.fromCharCode(parseInt(hex, 16));
|
|
15
|
+
});
|
|
16
|
+
}
|
|
17
|
+
/**
|
|
18
|
+
* Parse properties file content into key-value pairs
|
|
19
|
+
* @param {string} content - Properties file content
|
|
20
|
+
* @returns {Object} Parsed key-value pairs
|
|
21
|
+
*/
|
|
22
|
+
|
|
23
|
+
|
|
24
|
+
function parseProperties(content) {
|
|
25
|
+
const lines = content.split(/\r?\n/);
|
|
26
|
+
const data = {};
|
|
27
|
+
lines.forEach(line => {
|
|
28
|
+
const trimmedLine = line.trim();
|
|
29
|
+
|
|
30
|
+
if (trimmedLine.startsWith('#') || trimmedLine.startsWith('!') || trimmedLine === '') {
|
|
31
|
+
return;
|
|
32
|
+
} // Find unescaped separator (= or :)
|
|
33
|
+
|
|
34
|
+
|
|
35
|
+
let separatorIndex = -1;
|
|
36
|
+
|
|
37
|
+
for (let i = 0; i < trimmedLine.length; i++) {
|
|
38
|
+
if ((trimmedLine[i] === '=' || trimmedLine[i] === ':') && (i === 0 || trimmedLine[i - 1] !== '\\')) {
|
|
39
|
+
separatorIndex = i;
|
|
40
|
+
break;
|
|
41
|
+
}
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
if (separatorIndex > 0) {
|
|
45
|
+
let key = trimmedLine.substring(0, separatorIndex).trim();
|
|
46
|
+
let value = trimmedLine.substring(separatorIndex + 1).trim();
|
|
47
|
+
|
|
48
|
+
if (key) {
|
|
49
|
+
// Handle escaped spaces in keys only
|
|
50
|
+
key = key.replace(/\\ /g, ' '); // Decode Unicode escape sequences ONLY in values, not keys
|
|
51
|
+
|
|
52
|
+
value = decodeUnicodeEscapes(value);
|
|
53
|
+
data[key] = value;
|
|
54
|
+
}
|
|
55
|
+
}
|
|
56
|
+
});
|
|
57
|
+
return data;
|
|
58
|
+
}
|
|
59
|
+
/**
|
|
60
|
+
* Parse properties file content into a Set of keys only
|
|
61
|
+
* @param {string} content - Properties file content
|
|
62
|
+
* @returns {Set<string>} Set of keys
|
|
63
|
+
*/
|
|
64
|
+
|
|
65
|
+
|
|
66
|
+
function parsePropertiesToKeySet(content) {
|
|
67
|
+
const lines = content.split(/\r?\n/);
|
|
68
|
+
const keys = new Set();
|
|
69
|
+
lines.forEach(line => {
|
|
70
|
+
const trimmedLine = line.trim();
|
|
71
|
+
|
|
72
|
+
if (trimmedLine.startsWith('#') || trimmedLine.startsWith('!') || trimmedLine === '') {
|
|
73
|
+
return;
|
|
74
|
+
} // Find unescaped separator (= or :)
|
|
75
|
+
|
|
76
|
+
|
|
77
|
+
let separatorIndex = -1;
|
|
78
|
+
|
|
79
|
+
for (let i = 0; i < trimmedLine.length; i++) {
|
|
80
|
+
if ((trimmedLine[i] === '=' || trimmedLine[i] === ':') && (i === 0 || trimmedLine[i - 1] !== '\\')) {
|
|
81
|
+
separatorIndex = i;
|
|
82
|
+
break;
|
|
83
|
+
}
|
|
84
|
+
}
|
|
85
|
+
|
|
86
|
+
if (separatorIndex > 0) {
|
|
87
|
+
let key = trimmedLine.substring(0, separatorIndex).trim();
|
|
88
|
+
|
|
89
|
+
if (key) {
|
|
90
|
+
// Handle escaped spaces in keys only
|
|
91
|
+
key = key.replace(/\\ /g, ' ');
|
|
92
|
+
keys.add(key);
|
|
93
|
+
}
|
|
94
|
+
}
|
|
95
|
+
});
|
|
96
|
+
return keys;
|
|
97
|
+
}
|
|
98
|
+
|
|
99
|
+
module.exports = {
|
|
100
|
+
parseProperties,
|
|
101
|
+
parsePropertiesToKeySet,
|
|
102
|
+
decodeUnicodeEscapes
|
|
103
|
+
};
|