@zohodesk/client_build_tool 0.0.10 → 0.0.11-exp.7
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/CHANGELOG.md +0 -10
- package/README.md +0 -10
- package/lib/schemas/defaultConfigValues.js +17 -5
- package/lib/schemas/defaultConfigValuesOnly.js +16 -1
- package/lib/shared/babel/getBabelPlugin.js +4 -9
- package/lib/shared/babel/runBabelForTsFile.js +1 -1
- package/lib/shared/bundler/webpack/custom_plugins/I18nNumericIndexPlugin/I18nNumericIndexHtmlInjectorPlugin.js +49 -0
- package/lib/shared/bundler/webpack/custom_plugins/I18nNumericIndexPlugin/I18nNumericIndexPlugin.js +163 -0
- package/lib/shared/bundler/webpack/custom_plugins/I18nNumericIndexPlugin/utils/i18nDataLoader.js +113 -0
- package/lib/shared/bundler/webpack/custom_plugins/I18nSplitPlugin/utils/collectAstKeys.js +96 -0
- package/lib/shared/bundler/webpack/jsLoaders.js +20 -2
- package/lib/shared/bundler/webpack/loaderConfigs/i18nIdReplaceLoaderConfig.js +74 -0
- package/lib/shared/bundler/webpack/loaders/i18nIdReplaceLoader.js +105 -0
- package/lib/shared/bundler/webpack/pluginConfigs/configI18nNumericIndexPlugin.js +70 -0
- package/lib/shared/bundler/webpack/plugins.js +3 -1
- package/lib/shared/bundler/webpack/utils/propertiesParser.js +103 -0
- package/lib/shared/server/mockApiHandler.js +0 -7
- package/lib/shared/server/urlConcat.js +1 -1
- package/npm-shrinkwrap.json +24 -341
- package/package.json +5 -6
package/CHANGELOG.md
CHANGED
|
@@ -1,16 +1,6 @@
|
|
|
1
1
|
# Changelog and Release Notes
|
|
2
|
-
# v0.0.10 (12-05-2025)
|
|
3
|
-
**Feature:-**
|
|
4
|
-
- `alias` support for `build:es` and `build:lib`
|
|
5
|
-
- Add babel-plugin-module-resolver dependencies
|
|
6
|
-
- Modify getBabelPlugin to include module resolver with aliases
|
|
7
2
|
|
|
8
|
-
**Bug Fix:-**
|
|
9
|
-
- Enhance runBabelForTSFile to handle both .tsx and .ts file extensions
|
|
10
|
-
- Update mockApiHandler to ensure mock function is called correctly
|
|
11
3
|
|
|
12
|
-
**Change:-**
|
|
13
|
-
- Refactor defaultConfigValues.js to include cli options for enableRTLSplit
|
|
14
4
|
|
|
15
5
|
**Feature:-**
|
|
16
6
|
- externals was added to Prevent bundling of certain imported packages and retrieve these external dependencies at runtime.
|
package/README.md
CHANGED
|
@@ -99,18 +99,8 @@ fixes :-
|
|
|
99
99
|
- preload plc undefined url fixed
|
|
100
100
|
|
|
101
101
|
# Changelog and Release Notes
|
|
102
|
-
# v0.0.10 (12-05-2025)
|
|
103
|
-
**Feature:-**
|
|
104
|
-
- `alias` support for `build:es` and `build:lib`
|
|
105
|
-
- Add babel-plugin-module-resolver dependencies
|
|
106
|
-
- Modify getBabelPlugin to include module resolver with aliases
|
|
107
102
|
|
|
108
|
-
**Bug Fix:-**
|
|
109
|
-
- Enhance runBabelForTSFile to handle both .tsx and .ts file extensions
|
|
110
|
-
- Update mockApiHandler to ensure mock function is called correctly
|
|
111
103
|
|
|
112
|
-
**Change:-**
|
|
113
|
-
- Refactor defaultConfigValues.js to include cli options for enableRTLSplit
|
|
114
104
|
|
|
115
105
|
**Feature:-**
|
|
116
106
|
- externals was added to Prevent bundling of certain imported packages and retrieve these external dependencies at runtime.
|
|
@@ -115,10 +115,7 @@ var _default = {
|
|
|
115
115
|
// Name Suggestions `customizations` this will be easier then `plugin` to understand for developers
|
|
116
116
|
plugins: {
|
|
117
117
|
rtlSplit: {
|
|
118
|
-
enableRTLSplit:
|
|
119
|
-
value: false,
|
|
120
|
-
cli: 'enable_rtl_split'
|
|
121
|
-
},
|
|
118
|
+
enableRTLSplit: false,
|
|
122
119
|
templateLabel: '{{--dir}}',
|
|
123
120
|
disableMinifySelector: false,
|
|
124
121
|
dirVarName: 'document.dir'
|
|
@@ -174,7 +171,22 @@ var _default = {
|
|
|
174
171
|
localeVarName: 'document.documentElement.lang',
|
|
175
172
|
jsonpFunc: 'console.log',
|
|
176
173
|
jsResource: null,
|
|
177
|
-
propertiesFolder: null
|
|
174
|
+
propertiesFolder: null,
|
|
175
|
+
useNumericIndexing: false
|
|
176
|
+
},
|
|
177
|
+
i18nIndexing: {
|
|
178
|
+
enable: false,
|
|
179
|
+
jsResourcePath: './deskapp/properties/JSResources.properties',
|
|
180
|
+
propertiesFolderPath: './deskapp/properties',
|
|
181
|
+
numericMapPath: './deskapp/properties/i18n-numeric-map.json',
|
|
182
|
+
numericFilenameTemplate: 'i18n-chunk/[locale]/numeric.i18n.js',
|
|
183
|
+
dynamicFilenameTemplate: 'i18n-chunk/[locale]/dynamic.i18n.js',
|
|
184
|
+
jsonpFunc: 'window.loadI18nChunk',
|
|
185
|
+
htmlTemplateLabel: '{{--user-locale}}',
|
|
186
|
+
localeVarName: 'window.userLangCode',
|
|
187
|
+
singleFile: false,
|
|
188
|
+
includeContentHash: false,
|
|
189
|
+
generateManifest: false
|
|
178
190
|
},
|
|
179
191
|
publicFolders: ['...'],
|
|
180
192
|
app: {
|
|
@@ -94,7 +94,22 @@ var _default = {
|
|
|
94
94
|
localeVarName: 'document.documentElement.lang',
|
|
95
95
|
jsonpFunc: 'console.log',
|
|
96
96
|
jsResource: null,
|
|
97
|
-
propertiesFolder: null
|
|
97
|
+
propertiesFolder: null,
|
|
98
|
+
useNumericIndexing: false
|
|
99
|
+
},
|
|
100
|
+
i18nIndexing: {
|
|
101
|
+
enable: false,
|
|
102
|
+
jsResourcePath: './deskapp/properties/JSResources.properties',
|
|
103
|
+
propertiesFolderPath: './deskapp/properties',
|
|
104
|
+
numericMapPath: './deskapp/properties/i18n-numeric-map.json',
|
|
105
|
+
numericFilenameTemplate: 'i18n-chunk/[locale]/numeric.i18n.js',
|
|
106
|
+
dynamicFilenameTemplate: 'i18n-chunk/[locale]/dynamic.i18n.js',
|
|
107
|
+
jsonpFunc: 'window.loadI18nChunk',
|
|
108
|
+
htmlTemplateLabel: '{{--user-locale}}',
|
|
109
|
+
localeVarName: 'window.userLangCode',
|
|
110
|
+
singleFile: false,
|
|
111
|
+
includeContentHash: false,
|
|
112
|
+
generateManifest: false
|
|
98
113
|
},
|
|
99
114
|
publicFolders: ['...', {
|
|
100
115
|
source: './deskapp/tp/',
|
|
@@ -20,14 +20,7 @@ function getBabelPlugin(options) {
|
|
|
20
20
|
const {
|
|
21
21
|
mode
|
|
22
22
|
} = options;
|
|
23
|
-
|
|
24
|
-
alias
|
|
25
|
-
} = options.resolve; // let customPlugins = [];
|
|
26
|
-
|
|
27
|
-
let customPlugins = [[require.resolve('babel-plugin-module-resolver'), {
|
|
28
|
-
root: ['./'],
|
|
29
|
-
alias
|
|
30
|
-
}]];
|
|
23
|
+
let customPlugins = [];
|
|
31
24
|
const {
|
|
32
25
|
babelCustomizations
|
|
33
26
|
} = options;
|
|
@@ -39,4 +32,6 @@ function getBabelPlugin(options) {
|
|
|
39
32
|
}
|
|
40
33
|
|
|
41
34
|
return customPlugins.filter(Boolean);
|
|
42
|
-
}
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
;
|
|
@@ -25,6 +25,6 @@ function runBabelForTSFile({
|
|
|
25
25
|
// const jsSourceCode = readFileSync(filename).toString();
|
|
26
26
|
const babelConfig = (0, _babelWebConfig.babelWebConfig)(options, mode);
|
|
27
27
|
const result = (0, _core.transformFileSync)(filename, babelConfig);
|
|
28
|
-
(0, _copyFile.writeFile)(outputFile.replace('.tsx', '.js')
|
|
28
|
+
(0, _copyFile.writeFile)(outputFile.replace('.tsx', '.js'), result.code);
|
|
29
29
|
}
|
|
30
30
|
}
|
|
@@ -0,0 +1,49 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
|
|
3
|
+
const HtmlWebpackPlugin = require('html-webpack-plugin');
|
|
4
|
+
|
|
5
|
+
const pluginName = 'I18nNumericIndexHtmlInjectorPlugin';
|
|
6
|
+
|
|
7
|
+
class I18nNumericIndexHtmlInjectorPlugin {
|
|
8
|
+
constructor(options) {
|
|
9
|
+
this.options = options;
|
|
10
|
+
}
|
|
11
|
+
|
|
12
|
+
apply(compiler) {
|
|
13
|
+
compiler.hooks.thisCompilation.tap(pluginName, compilation => {
|
|
14
|
+
HtmlWebpackPlugin.getHooks(compilation).beforeAssetTagGeneration.tapAsync(pluginName, (hookData, cb) => {
|
|
15
|
+
const {
|
|
16
|
+
assets
|
|
17
|
+
} = hookData;
|
|
18
|
+
const {
|
|
19
|
+
numericFilenameTemplate,
|
|
20
|
+
dynamicFilenameTemplate,
|
|
21
|
+
htmlTemplateLabel,
|
|
22
|
+
i18nAssetsPublicPathPrefix = ''
|
|
23
|
+
} = this.options;
|
|
24
|
+
const newI18nAssetUrlsToAdd = [];
|
|
25
|
+
|
|
26
|
+
if (numericFilenameTemplate) {
|
|
27
|
+
const numericFilename = numericFilenameTemplate.replace(/\[locale\]/g, htmlTemplateLabel);
|
|
28
|
+
newI18nAssetUrlsToAdd.push(numericFilename);
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
if (dynamicFilenameTemplate) {
|
|
32
|
+
const dynamicFilename = dynamicFilenameTemplate.replace(/\[locale\]/g, htmlTemplateLabel);
|
|
33
|
+
newI18nAssetUrlsToAdd.push(dynamicFilename);
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
if (newI18nAssetUrlsToAdd.length > 0) {
|
|
37
|
+
assets.js = [...assets.js, ...newI18nAssetUrlsToAdd];
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
cb(null, hookData);
|
|
41
|
+
});
|
|
42
|
+
});
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
module.exports = {
|
|
48
|
+
I18nNumericIndexHtmlInjectorPlugin
|
|
49
|
+
};
|
package/lib/shared/bundler/webpack/custom_plugins/I18nNumericIndexPlugin/I18nNumericIndexPlugin.js
ADDED
|
@@ -0,0 +1,163 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
|
|
3
|
+
const path = require('path');
|
|
4
|
+
|
|
5
|
+
const {
|
|
6
|
+
sources,
|
|
7
|
+
Compilation,
|
|
8
|
+
util
|
|
9
|
+
} = require('webpack');
|
|
10
|
+
|
|
11
|
+
const {
|
|
12
|
+
decodeUnicodeEscapes
|
|
13
|
+
} = require('../../utils/propertiesParser');
|
|
14
|
+
|
|
15
|
+
const {
|
|
16
|
+
loadNumericMap,
|
|
17
|
+
loadI18nData
|
|
18
|
+
} = require('./utils/i18nDataLoader');
|
|
19
|
+
|
|
20
|
+
const {
|
|
21
|
+
RawSource
|
|
22
|
+
} = sources;
|
|
23
|
+
const pluginName = 'I18nNumericIndexPlugin';
|
|
24
|
+
|
|
25
|
+
class I18nNumericIndexPlugin {
|
|
26
|
+
constructor(options = {}) {
|
|
27
|
+
this.options = { ...options,
|
|
28
|
+
singleFile: options.singleFile || false,
|
|
29
|
+
includeContentHash: options.includeContentHash || false,
|
|
30
|
+
generateManifest: options.generateManifest || false
|
|
31
|
+
};
|
|
32
|
+
this.numericMap = null;
|
|
33
|
+
this.i18nData = null;
|
|
34
|
+
this.manifest = {};
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
getNumericMap(compilation) {
|
|
38
|
+
if (!this.numericMap) {
|
|
39
|
+
const mapPath = path.resolve(compilation.compiler.context, this.options.numericMapPath);
|
|
40
|
+
this.numericMap = loadNumericMap(mapPath, compilation);
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
return this.numericMap;
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
getI18nData(compilation) {
|
|
47
|
+
if (!this.i18nData) {
|
|
48
|
+
this.i18nData = loadI18nData(this.options, compilation);
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
return this.i18nData;
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
generateContentHash(content, compilation) {
|
|
55
|
+
const {
|
|
56
|
+
hashFunction,
|
|
57
|
+
hashDigest,
|
|
58
|
+
hashDigestLength
|
|
59
|
+
} = compilation.outputOptions;
|
|
60
|
+
const hash = util.createHash(hashFunction || 'xxhash64');
|
|
61
|
+
hash.update(content);
|
|
62
|
+
return hash.digest(hashDigest || 'hex').substring(0, hashDigestLength || 20);
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
emitChunk(compilation, filename, locale, data, fileType = null) {
|
|
66
|
+
const content = decodeUnicodeEscapes(JSON.stringify(data));
|
|
67
|
+
const fileContent = `${this.options.jsonpFunc}(${content});`;
|
|
68
|
+
let outputPath = filename.replace(/\[locale\]/g, locale);
|
|
69
|
+
|
|
70
|
+
if (this.options.includeContentHash) {
|
|
71
|
+
const contentHash = this.generateContentHash(fileContent, compilation);
|
|
72
|
+
outputPath = outputPath.replace(/\.js$/, `.${contentHash}.js`);
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
if (this.options.generateManifest) {
|
|
76
|
+
const cleanName = filename.replace(/\[locale\]/g, locale).replace(/\.js$/, '.js');
|
|
77
|
+
const cleanNameWithType = fileType ? cleanName.replace(/\.js$/, `.${fileType}.js`) : cleanName;
|
|
78
|
+
const manifestKey = cleanNameWithType.split('/').pop(); // Extract just the filename for the key
|
|
79
|
+
|
|
80
|
+
this.manifest[manifestKey] = outputPath.split('/').pop();
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
compilation.emitAsset(outputPath, new RawSource(fileContent));
|
|
84
|
+
return outputPath;
|
|
85
|
+
}
|
|
86
|
+
|
|
87
|
+
apply(compiler) {
|
|
88
|
+
compiler.hooks.thisCompilation.tap(pluginName, compilation => {
|
|
89
|
+
compilation.hooks.processAssets.tapAsync({
|
|
90
|
+
name: pluginName,
|
|
91
|
+
stage: Compilation.PROCESS_ASSETS_STAGE_SUMMARIZE
|
|
92
|
+
}, (assets, callback) => {
|
|
93
|
+
if (!this.options.enable) {
|
|
94
|
+
return callback();
|
|
95
|
+
}
|
|
96
|
+
|
|
97
|
+
const {
|
|
98
|
+
sortedKeys,
|
|
99
|
+
totalKeys
|
|
100
|
+
} = this.getNumericMap(compilation);
|
|
101
|
+
const {
|
|
102
|
+
jsResourceBase,
|
|
103
|
+
allI18n,
|
|
104
|
+
locales
|
|
105
|
+
} = this.getI18nData(compilation);
|
|
106
|
+
if (!locales.length) return callback();
|
|
107
|
+
const numericKeysSet = new Set(sortedKeys);
|
|
108
|
+
const englishData = allI18n.en_US || jsResourceBase;
|
|
109
|
+
locales.forEach(locale => {
|
|
110
|
+
const localeData = allI18n[locale] || {};
|
|
111
|
+
const numericData = {};
|
|
112
|
+
|
|
113
|
+
for (let i = 0; i < totalKeys; i++) {
|
|
114
|
+
const key = sortedKeys[i];
|
|
115
|
+
|
|
116
|
+
if (key && jsResourceBase[key] !== undefined) {
|
|
117
|
+
numericData[i] = localeData[key] ?? englishData[key];
|
|
118
|
+
}
|
|
119
|
+
}
|
|
120
|
+
|
|
121
|
+
const dynamicData = {};
|
|
122
|
+
Object.keys(jsResourceBase).forEach(key => {
|
|
123
|
+
if (!numericKeysSet.has(key)) {
|
|
124
|
+
dynamicData[key] = localeData[key] ?? englishData[key];
|
|
125
|
+
}
|
|
126
|
+
});
|
|
127
|
+
|
|
128
|
+
if (this.options.singleFile) {
|
|
129
|
+
const combinedData = { ...numericData,
|
|
130
|
+
...dynamicData
|
|
131
|
+
};
|
|
132
|
+
|
|
133
|
+
if (Object.keys(combinedData).length > 0) {
|
|
134
|
+
const filename = this.options.numericFilenameTemplate || this.options.dynamicFilenameTemplate;
|
|
135
|
+
this.emitChunk(compilation, filename, locale, combinedData);
|
|
136
|
+
}
|
|
137
|
+
} else {
|
|
138
|
+
if (Object.keys(numericData).length > 0) {
|
|
139
|
+
this.emitChunk(compilation, this.options.numericFilenameTemplate, locale, numericData, 'numeric');
|
|
140
|
+
}
|
|
141
|
+
|
|
142
|
+
if (Object.keys(dynamicData).length > 0) {
|
|
143
|
+
this.emitChunk(compilation, this.options.dynamicFilenameTemplate, locale, dynamicData, 'dynamic');
|
|
144
|
+
}
|
|
145
|
+
}
|
|
146
|
+
});
|
|
147
|
+
|
|
148
|
+
if (this.options.generateManifest && Object.keys(this.manifest).length > 0) {
|
|
149
|
+
const manifestPath = path.dirname(this.options.numericFilenameTemplate) + '/manifest.json';
|
|
150
|
+
const manifestContent = JSON.stringify(this.manifest, null, 2);
|
|
151
|
+
compilation.emitAsset(manifestPath, new RawSource(manifestContent));
|
|
152
|
+
}
|
|
153
|
+
|
|
154
|
+
callback();
|
|
155
|
+
});
|
|
156
|
+
});
|
|
157
|
+
}
|
|
158
|
+
|
|
159
|
+
}
|
|
160
|
+
|
|
161
|
+
module.exports = {
|
|
162
|
+
I18nNumericIndexPlugin
|
|
163
|
+
};
|
package/lib/shared/bundler/webpack/custom_plugins/I18nNumericIndexPlugin/utils/i18nDataLoader.js
ADDED
|
@@ -0,0 +1,113 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
|
|
3
|
+
const fs = require('fs');
|
|
4
|
+
|
|
5
|
+
const path = require('path');
|
|
6
|
+
|
|
7
|
+
const {
|
|
8
|
+
parseProperties
|
|
9
|
+
} = require('../../../utils/propertiesParser');
|
|
10
|
+
|
|
11
|
+
function loadPropertiesFile(filePath, compilation, description) {
|
|
12
|
+
try {
|
|
13
|
+
const content = fs.readFileSync(filePath, 'utf-8');
|
|
14
|
+
const parsed = parseProperties(content);
|
|
15
|
+
return parsed;
|
|
16
|
+
} catch (err) {
|
|
17
|
+
if (compilation) {
|
|
18
|
+
compilation.errors.push(new Error(`I18nNumericIndexPlugin: Error loading ${description}: ${err.message}`));
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
return {};
|
|
22
|
+
}
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
function loadNumericMap(numericMapPath, compilation) {
|
|
26
|
+
try {
|
|
27
|
+
const fileContent = fs.readFileSync(numericMapPath, 'utf-8');
|
|
28
|
+
const parsedData = JSON.parse(fileContent);
|
|
29
|
+
const sortedKeys = new Array(parsedData.totalKeysInMap);
|
|
30
|
+
Object.entries(parsedData.originalKeyToNumericId).forEach(([key, id]) => {
|
|
31
|
+
sortedKeys[id] = key;
|
|
32
|
+
});
|
|
33
|
+
return {
|
|
34
|
+
sortedKeys,
|
|
35
|
+
totalKeys: parsedData.totalKeysInMap
|
|
36
|
+
};
|
|
37
|
+
} catch (err) {
|
|
38
|
+
if (compilation) {
|
|
39
|
+
compilation.errors.push(new Error(`I18nNumericIndexPlugin: Error loading numeric map: ${err.message}`));
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
return {
|
|
43
|
+
sortedKeys: [],
|
|
44
|
+
totalKeys: 0
|
|
45
|
+
};
|
|
46
|
+
}
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
function loadAllLocaleFiles(propertiesPath, baseFileName, compilation) {
|
|
50
|
+
const allI18n = {};
|
|
51
|
+
const locales = [];
|
|
52
|
+
|
|
53
|
+
try {
|
|
54
|
+
const files = fs.readdirSync(propertiesPath);
|
|
55
|
+
files.forEach(file => {
|
|
56
|
+
if (file === baseFileName + '.properties') {
|
|
57
|
+
const filePath = path.join(propertiesPath, file);
|
|
58
|
+
const baseData = loadPropertiesFile(filePath, compilation, 'JSResources base');
|
|
59
|
+
allI18n['en_US'] = baseData;
|
|
60
|
+
locales.push('en_US');
|
|
61
|
+
}
|
|
62
|
+
});
|
|
63
|
+
files.forEach(file => {
|
|
64
|
+
if (!file.endsWith('.properties')) return;
|
|
65
|
+
const match = file.match(/^ApplicationResources_([a-z]{2}_[A-Z]{2})\.properties$/);
|
|
66
|
+
|
|
67
|
+
if (match) {
|
|
68
|
+
const locale = match[1];
|
|
69
|
+
const filePath = path.join(propertiesPath, file);
|
|
70
|
+
const localeData = loadPropertiesFile(filePath, compilation, `locale ${locale}`);
|
|
71
|
+
allI18n[locale] = { ...allI18n['en_US'],
|
|
72
|
+
...localeData
|
|
73
|
+
};
|
|
74
|
+
|
|
75
|
+
if (!locales.includes(locale)) {
|
|
76
|
+
locales.push(locale);
|
|
77
|
+
}
|
|
78
|
+
}
|
|
79
|
+
});
|
|
80
|
+
} catch (err) {
|
|
81
|
+
if (compilation) {
|
|
82
|
+
compilation.errors.push(new Error(`I18nNumericIndexPlugin: Error reading properties folder: ${err.message}`));
|
|
83
|
+
}
|
|
84
|
+
}
|
|
85
|
+
|
|
86
|
+
return {
|
|
87
|
+
allI18n,
|
|
88
|
+
locales
|
|
89
|
+
};
|
|
90
|
+
}
|
|
91
|
+
|
|
92
|
+
function loadI18nData(options, compilation) {
|
|
93
|
+
const jsResourcePath = path.resolve(compilation.compiler.context, options.jsResourcePath);
|
|
94
|
+
const propertiesPath = path.resolve(compilation.compiler.context, options.propertiesFolderPath);
|
|
95
|
+
const baseFileName = path.basename(options.jsResourcePath, '.properties');
|
|
96
|
+
const jsResourceBase = loadPropertiesFile(jsResourcePath, compilation, 'JS resources');
|
|
97
|
+
const {
|
|
98
|
+
allI18n,
|
|
99
|
+
locales
|
|
100
|
+
} = loadAllLocaleFiles(propertiesPath, baseFileName, compilation);
|
|
101
|
+
return {
|
|
102
|
+
jsResourceBase,
|
|
103
|
+
allI18n,
|
|
104
|
+
locales
|
|
105
|
+
};
|
|
106
|
+
}
|
|
107
|
+
|
|
108
|
+
module.exports = {
|
|
109
|
+
loadPropertiesFile,
|
|
110
|
+
loadNumericMap,
|
|
111
|
+
loadAllLocaleFiles,
|
|
112
|
+
loadI18nData
|
|
113
|
+
};
|
|
@@ -0,0 +1,96 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
|
|
3
|
+
const {
|
|
4
|
+
walk
|
|
5
|
+
} = require('estree-walker');
|
|
6
|
+
|
|
7
|
+
const PREFIX_I18N_COMMENT = 'I18N';
|
|
8
|
+
const PREFIX_I18N_COMMENT_DYNAMIC = 'dynamic-i18n-key';
|
|
9
|
+
|
|
10
|
+
function getI18nKeysFromSingleComment(commentNode, validKeysSet) {
|
|
11
|
+
const foundKeysInComment = [];
|
|
12
|
+
|
|
13
|
+
if (!commentNode || typeof commentNode.value !== 'string') {
|
|
14
|
+
return foundKeysInComment;
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
const commentString = commentNode.value.trim();
|
|
18
|
+
let i18nKeyStr;
|
|
19
|
+
|
|
20
|
+
if (commentString.startsWith(PREFIX_I18N_COMMENT)) {
|
|
21
|
+
i18nKeyStr = commentString.slice(PREFIX_I18N_COMMENT.length).trim();
|
|
22
|
+
} else if (commentString.startsWith(PREFIX_I18N_COMMENT_DYNAMIC)) {
|
|
23
|
+
i18nKeyStr = commentString.slice(PREFIX_I18N_COMMENT_DYNAMIC.length).trim();
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
if (!i18nKeyStr) {
|
|
27
|
+
return foundKeysInComment;
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
const potentialKeys = i18nKeyStr.split(',').map(key => key.trim()).filter(key => key);
|
|
31
|
+
potentialKeys.forEach(key => {
|
|
32
|
+
if (validKeysSet.has(key)) {
|
|
33
|
+
foundKeysInComment.push(key);
|
|
34
|
+
}
|
|
35
|
+
});
|
|
36
|
+
return foundKeysInComment;
|
|
37
|
+
}
|
|
38
|
+
/**
|
|
39
|
+
* Traverses an AST and its comments to collect and categorize i18n keys.
|
|
40
|
+
*
|
|
41
|
+
* @param {object} astProgramNode - The Program node of the AST.
|
|
42
|
+
* @param {object[]} commentsArray - An array of comment nodes from the AST.
|
|
43
|
+
* @param {object} allI18nKeysMasterMap - Object map of all valid i18n keys (from JSResources).
|
|
44
|
+
* @param {boolean} [isDebug=false] - Flag for verbose logging.
|
|
45
|
+
* @returns {{literalKeys: Set<string>, commentKeys: Set<string>}}
|
|
46
|
+
* literalKeys: Set of valid i18n keys found as string literals.
|
|
47
|
+
* commentKeys: Set of valid i18n keys found in comments.
|
|
48
|
+
*/
|
|
49
|
+
|
|
50
|
+
|
|
51
|
+
function collectAndCategorizeUsedI18nKeys(astProgramNode, commentsArray, allI18nKeysMasterMap, isDebug = false) {
|
|
52
|
+
const foundLiteralKeys = new Set();
|
|
53
|
+
const foundCommentKeys = new Set();
|
|
54
|
+
const validKeysSet = new Set(Object.keys(allI18nKeysMasterMap || {}));
|
|
55
|
+
|
|
56
|
+
if (validKeysSet.size === 0 && isDebug) {
|
|
57
|
+
console.warn('[collectAndCategorizeUsedI18nKeys] allI18nKeysMasterMap is empty. No keys can be collected.');
|
|
58
|
+
} // 1. Collect keys from AST string literals
|
|
59
|
+
|
|
60
|
+
|
|
61
|
+
if (astProgramNode) {
|
|
62
|
+
try {
|
|
63
|
+
walk(astProgramNode, {
|
|
64
|
+
enter(node) {
|
|
65
|
+
if ((node.type === 'Literal' || node.type === 'StringLiteral') && typeof node.value === 'string') {
|
|
66
|
+
if (validKeysSet.has(node.value)) {
|
|
67
|
+
foundLiteralKeys.add(node.value);
|
|
68
|
+
}
|
|
69
|
+
}
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
});
|
|
73
|
+
} catch (error) {
|
|
74
|
+
console.error('[collectAndCategorizeUsedI18nKeys] Error during AST walk:', error);
|
|
75
|
+
}
|
|
76
|
+
} // 2. Collect keys from comments
|
|
77
|
+
|
|
78
|
+
|
|
79
|
+
if (commentsArray && Array.isArray(commentsArray)) {
|
|
80
|
+
commentsArray.forEach(commentNode => {
|
|
81
|
+
const keysFromComment = getI18nKeysFromSingleComment(commentNode, validKeysSet);
|
|
82
|
+
keysFromComment.forEach(key => {
|
|
83
|
+
foundCommentKeys.add(key);
|
|
84
|
+
});
|
|
85
|
+
});
|
|
86
|
+
}
|
|
87
|
+
|
|
88
|
+
return {
|
|
89
|
+
literalKeys: foundLiteralKeys,
|
|
90
|
+
commentKeys: foundCommentKeys
|
|
91
|
+
};
|
|
92
|
+
}
|
|
93
|
+
|
|
94
|
+
module.exports = {
|
|
95
|
+
collectAndCategorizeUsedI18nKeys: collectAndCategorizeUsedI18nKeys
|
|
96
|
+
};
|
|
@@ -7,11 +7,29 @@ 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 = [];
|
|
16
|
+
useLoaders.push((0, _babelLoaderConfig.babelLoaderConfig)(options));
|
|
17
|
+
const shouldUseNumericIndexing = options.i18nIndexing && options.i18nIndexing.enable || options.i18nChunkSplit && options.i18nChunkSplit.chunkSplitEnable && options.i18nChunkSplit.useNumericIndexing;
|
|
18
|
+
|
|
19
|
+
if (shouldUseNumericIndexing) {
|
|
20
|
+
try {
|
|
21
|
+
const loaderConfig = i18nIdReplaceLoaderConfig(options, options.context);
|
|
22
|
+
|
|
23
|
+
if (loaderConfig) {
|
|
24
|
+
useLoaders.push(loaderConfig);
|
|
25
|
+
}
|
|
26
|
+
} catch (err) {// Silently skip if configuration fails
|
|
27
|
+
}
|
|
28
|
+
}
|
|
29
|
+
|
|
11
30
|
return [{
|
|
12
31
|
test: /\.js$/,
|
|
13
32
|
exclude: /node_modules/,
|
|
14
|
-
use:
|
|
15
|
-
|
|
33
|
+
use: useLoaders
|
|
16
34
|
}];
|
|
17
35
|
}
|
|
@@ -0,0 +1,74 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
|
|
3
|
+
const path = require('path');
|
|
4
|
+
|
|
5
|
+
const fs = require('fs');
|
|
6
|
+
|
|
7
|
+
const {
|
|
8
|
+
getPropertiesAsJSON
|
|
9
|
+
} = require('../custom_plugins/I18nSplitPlugin/utils/propertiesUtils');
|
|
10
|
+
|
|
11
|
+
function loadJSResourcesOnce(options) {
|
|
12
|
+
let jsResourcePath;
|
|
13
|
+
|
|
14
|
+
if (options.i18nIndexing && options.i18nIndexing.enable) {
|
|
15
|
+
jsResourcePath = options.i18nIndexing.jsResourcePath;
|
|
16
|
+
} else if (options.i18nChunkSplit && options.i18nChunkSplit.chunkSplitEnable && options.i18nChunkSplit.useNumericIndexing) {
|
|
17
|
+
jsResourcePath = options.i18nChunkSplit.jsResource;
|
|
18
|
+
} else {
|
|
19
|
+
throw new Error('i18nIdReplaceLoader requires either i18nIndexing to be enabled or i18nChunkSplit with useNumericIndexing');
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
if (!jsResourcePath) {
|
|
23
|
+
throw new Error('Missing required jsResourcePath in i18n options');
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
try {
|
|
27
|
+
const i18nData = getPropertiesAsJSON(jsResourcePath);
|
|
28
|
+
|
|
29
|
+
if (Object.keys(i18nData).length === 0) {
|
|
30
|
+
console.warn(`[i18nIdReplaceLoaderConfig] Warning: No i18n data found in JSResource file: ${jsResourcePath}`);
|
|
31
|
+
return {};
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
return i18nData;
|
|
35
|
+
} catch (err) {
|
|
36
|
+
throw new Error(`Error reading JSResource file ${jsResourcePath}: ${err.message}`);
|
|
37
|
+
}
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
function i18nIdReplaceLoaderConfig(options, webpackContext) {
|
|
41
|
+
let numericMapPath;
|
|
42
|
+
|
|
43
|
+
if (options.i18nIndexing && options.i18nIndexing.enable) {
|
|
44
|
+
numericMapPath = options.i18nIndexing.numericMapPath;
|
|
45
|
+
} else if (options.i18nChunkSplit && options.i18nChunkSplit.chunkSplitEnable && options.i18nChunkSplit.useNumericIndexing) {
|
|
46
|
+
numericMapPath = options.i18nChunkSplit.numericMapPath;
|
|
47
|
+
} else {
|
|
48
|
+
throw new Error('i18nIdReplaceLoader requires either i18nIndexing to be enabled or i18nChunkSplit with useNumericIndexing');
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
if (!numericMapPath) {
|
|
52
|
+
throw new Error('numericMapPath is required in i18nIndexing or i18nChunkSplit config');
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
const allI18nData = loadJSResourcesOnce(options);
|
|
56
|
+
|
|
57
|
+
const i18nKeyReplaceLoaderPath = require.resolve('../loaders/i18nIdReplaceLoader.js');
|
|
58
|
+
|
|
59
|
+
const loaderOptions = {
|
|
60
|
+
allI18nData: allI18nData,
|
|
61
|
+
sourceMaps: false,
|
|
62
|
+
numericMapPath: numericMapPath,
|
|
63
|
+
includePaths: options.i18nIndexing?.loaderOptions?.includePaths || [],
|
|
64
|
+
excludePaths: options.i18nIndexing?.loaderOptions?.excludePaths || ['node_modules', 'tests']
|
|
65
|
+
};
|
|
66
|
+
return {
|
|
67
|
+
loader: i18nKeyReplaceLoaderPath,
|
|
68
|
+
options: loaderOptions
|
|
69
|
+
};
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
module.exports = {
|
|
73
|
+
i18nIdReplaceLoaderConfig
|
|
74
|
+
};
|