@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.
- package/CHANGELOG.md +28 -0
- package/README.md +130 -0
- package/README_backup.md +102 -0
- package/docs/DYNAMIC_TEMPLATE_EXAMPLE.md +129 -0
- package/docs/I18N_NUMERIC_INDEXING_PLUGIN.md +225 -0
- package/docs/I18N_SINGLE_FILE_MODE.md +126 -0
- package/docs/client_build_tool_source_doc.md +390 -0
- package/lib/schemas/defaultConfigValues.js +46 -1
- package/lib/schemas/defaultConfigValuesOnly.js +31 -4
- package/lib/shared/babel/getBabelPlugin.js +3 -4
- package/lib/shared/babel/runBabelForTsFile.js +1 -1
- package/lib/shared/bundler/webpack/custom_plugins/I18nNumericIndexPlugin/I18nGroupRuntimeModule.js +139 -0
- package/lib/shared/bundler/webpack/custom_plugins/I18nNumericIndexPlugin/I18nNumericIndexHtmlInjectorPlugin.js +98 -0
- package/lib/shared/bundler/webpack/custom_plugins/I18nNumericIndexPlugin/I18nNumericIndexPlugin.js +399 -0
- package/lib/shared/bundler/webpack/custom_plugins/I18nNumericIndexPlugin/utils/i18nDataLoader.js +141 -0
- package/lib/shared/bundler/webpack/custom_plugins/I18nSplitPlugin/utils/propertiesUtils.js +19 -1
- package/lib/shared/bundler/webpack/jsLoaders.js +24 -1
- package/lib/shared/bundler/webpack/loaderConfigs/i18nIdReplaceLoaderConfig.js +90 -0
- package/lib/shared/bundler/webpack/loaders/i18nIdReplaceLoader.js +146 -0
- package/lib/shared/bundler/webpack/pluginConfigs/configI18nIndexingPlugin.js +42 -0
- package/lib/shared/bundler/webpack/pluginConfigs/configI18nNumericHtmlInjector.js +92 -0
- package/lib/shared/bundler/webpack/pluginConfigs/configI18nNumericIndexPlugin.js +112 -0
- package/lib/shared/bundler/webpack/plugins.js +3 -1
- package/lib/shared/postcss/custom_postcss_plugins/VariableModificationPlugin/index.js +1 -1
- package/lib/shared/server/mockApiHandler.js +7 -0
- package/lib/shared/server/urlConcat.js +4 -3
- package/npm-shrinkwrap.json +8225 -32
- package/package.json +6 -5
package/lib/shared/bundler/webpack/custom_plugins/I18nNumericIndexPlugin/utils/i18nDataLoader.js
ADDED
|
@@ -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
|
-
|
|
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:
|
|
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
|
+
}
|