@zohodesk/client_build_tool 0.0.11-exp.17.0 → 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.
@@ -193,6 +193,11 @@ var _default = {
193
193
  htmlTemplateLabel: '{{--user-locale}}',
194
194
  localeVarName: 'window.userLangCode',
195
195
  singleFile: false,
196
+ // When true, locales are restricted to keys present in base JSResources.properties
197
+ restrictToBaseKeys: {
198
+ value: false,
199
+ cli: 'i18n_restrict_to_base'
200
+ },
196
201
  includeContentHash: false,
197
202
  generateManifest: false,
198
203
  manifestPath: null,
@@ -109,6 +109,7 @@ var _default = {
109
109
  htmlTemplateLabel: '{{--user-locale}}',
110
110
  localeVarName: 'window.userLangCode',
111
111
  singleFile: false,
112
+ restrictToBaseKeys: false,
112
113
  includeContentHash: false,
113
114
  generateManifest: false,
114
115
  manifestPath: null,
@@ -29,7 +29,6 @@ class I18nGroupRuntimeModule extends _webpack.RuntimeModule {
29
29
  return `
30
30
  // I18n Group Loading Runtime
31
31
  (function() {
32
- console.log('[I18n Runtime] Initializing i18n group loader with groups:', ${JSON.stringify(chunkToGroup)});
33
32
  var loadedGroups = {};
34
33
  var chunkToGroup = ${JSON.stringify(chunkToGroup)};
35
34
 
@@ -52,7 +51,6 @@ class I18nGroupRuntimeModule extends _webpack.RuntimeModule {
52
51
  .replace('[locale]', locale)
53
52
  .replace('i18n-chunk/', __webpack_require__.p + 'i18n-chunk/');
54
53
 
55
- console.log('[I18n Runtime] Loading i18n group:', groupName, 'from:', i18nUrl);
56
54
 
57
55
  var script = document.createElement('script');
58
56
  script.src = i18nUrl;
@@ -60,12 +58,10 @@ class I18nGroupRuntimeModule extends _webpack.RuntimeModule {
60
58
 
61
59
  script.onload = function() {
62
60
  loadedGroups[groupName] = true;
63
- console.log('[I18n Runtime] Successfully loaded i18n group:', groupName);
64
61
  resolve();
65
62
  };
66
63
 
67
64
  script.onerror = function() {
68
- console.error('[I18n Runtime] Failed to load i18n group:', groupName);
69
65
  reject(new Error('Failed to load i18n group: ' + groupName));
70
66
  };
71
67
 
@@ -84,7 +80,6 @@ class I18nGroupRuntimeModule extends _webpack.RuntimeModule {
84
80
  // Check if this chunk needs an i18n group
85
81
  var groupName = chunkToGroup[chunkId];
86
82
  if (groupName && !loadedGroups[groupName]) {
87
- console.log('[I18n Runtime] Chunk', chunkId, 'requires i18n group:', groupName);
88
83
  // Load the i18n group before the chunk
89
84
  var i18nPromise = loadI18nGroup(groupName);
90
85
  // Chain the promises so i18n loads first
@@ -19,7 +19,6 @@ class I18nNumericIndexHtmlInjectorPlugin {
19
19
  HtmlWebpackPlugin.getHooks(compilation).beforeAssetTagGeneration.tapAsync(pluginName, (hookData, cb) => {
20
20
  // Skip HTML injection if injectI18nUrlInIndex is false
21
21
  if (!this.options.injectI18nUrlInIndex) {
22
- console.log('[I18nNumericIndexHtmlInjectorPlugin] Skipping HTML injection (injectI18nUrlInIndex is false)');
23
22
  return cb(null, hookData);
24
23
  }
25
24
 
@@ -26,7 +26,6 @@ const pluginName = 'I18nNumericIndexPlugin';
26
26
 
27
27
  class I18nNumericIndexPlugin {
28
28
  constructor(options) {
29
- console.log('[I18nNumericIndexPlugin] Plugin initialized with options:', JSON.stringify(options, null, 2));
30
29
  this.options = { ...options,
31
30
  singleFile: options.singleFile || false,
32
31
  singleFileTemplate: options.singleFileTemplate || '[locale].js',
@@ -34,8 +33,7 @@ class I18nNumericIndexPlugin {
34
33
  generateManifest: options.generateManifest || false,
35
34
  outputFolder: options.outputFolder || 'i18n-chunk',
36
35
  manifestPath: options.manifestPath || null,
37
- emitFiles: options.emitFiles !== undefined ? options.emitFiles : true // Default to true for backward compatibility
38
-
36
+ emitFiles: options.emitFiles !== undefined ? options.emitFiles : true
39
37
  };
40
38
  this.numericMap = {};
41
39
  this.customGroups = {};
@@ -48,11 +46,9 @@ class I18nNumericIndexPlugin {
48
46
  compiler.hooks.thisCompilation.tap(pluginName, compilation => {
49
47
  // Add runtime module for group-based loading
50
48
  if (this.options.customGroups) {
51
- // Add runtime module unconditionally to ensure it's always available
52
49
  compilation.hooks.additionalTreeRuntimeRequirements.tap(pluginName, (chunk, set) => {
53
50
  // Only add to the main/entry chunk to avoid duplication
54
51
  if (chunk.name === 'main' || chunk.hasRuntime()) {
55
- console.log('[I18nNumericIndexPlugin] Adding I18nGroupRuntimeModule to chunk:', chunk.name || chunk.id);
56
52
  compilation.addRuntimeModule(chunk, new _I18nGroupRuntimeModule.I18nGroupRuntimeModule({
57
53
  customGroups: this.options.customGroups,
58
54
  localeVarName: this.options.localeVarName,
@@ -104,7 +100,6 @@ class I18nNumericIndexPlugin {
104
100
  }
105
101
 
106
102
  processI18nFiles(compilation) {
107
- console.log('[I18nNumericIndexPlugin] Starting to process i18n files - plugin is active');
108
103
  const {
109
104
  jsResourcePath,
110
105
  propertiesFolderPath,
@@ -116,7 +111,6 @@ class I18nNumericIndexPlugin {
116
111
  } = this.options;
117
112
 
118
113
  if (!jsResourcePath || !propertiesFolderPath) {
119
- console.log('[I18nNumericIndexPlugin] Missing required paths - jsResourcePath:', jsResourcePath, 'propertiesFolderPath:', propertiesFolderPath);
120
114
  return;
121
115
  } // Load existing numeric map if available
122
116
 
@@ -135,7 +129,8 @@ class I18nNumericIndexPlugin {
135
129
  } // Read JSResources.properties
136
130
 
137
131
 
138
- const jsResourceKeys = (0, _propertiesUtils.getPropertiesAsJSON)(jsResourcePath); // Parse custom groups from banner markers
132
+ let jsResourceKeys = (0, _propertiesUtils.getPropertiesAsJSON)(jsResourcePath);
133
+ jsResourceKeys = this.normalizeObjectValues(jsResourceKeys); // Parse custom groups from banner markers
139
134
 
140
135
  if (customGroups) {
141
136
  this.parseCustomGroups(jsResourcePath, customGroups);
@@ -146,6 +141,10 @@ class I18nNumericIndexPlugin {
146
141
  folderPath: propertiesFolderPath,
147
142
  disableDefault: false,
148
143
  jsResourceI18nKeys: jsResourceKeys
144
+ }); // Normalize locale values
145
+
146
+ Object.keys(allI18nObject).forEach(loc => {
147
+ allI18nObject[loc] = this.normalizeObjectValues(allI18nObject[loc]);
149
148
  }); // For en_US, use only JSResources (don't merge with ApplicationResources_en_US)
150
149
 
151
150
  if (allI18nObject['en_US']) {
@@ -153,6 +152,21 @@ class I18nNumericIndexPlugin {
153
152
  } else {
154
153
  // If en_US doesn't exist in the folder, create it from JSResources
155
154
  allI18nObject['en_US'] = jsResourceKeys;
155
+ } // If requested, restrict all locales to base (English) keys only
156
+
157
+
158
+ if (this.options.restrictToBaseKeys) {
159
+ const baseKeys = Object.keys(jsResourceKeys);
160
+ Object.keys(allI18nObject).forEach(locale => {
161
+ const merged = { ...jsResourceKeys,
162
+ ...allI18nObject[locale]
163
+ };
164
+ const filtered = {};
165
+ baseKeys.forEach(k => {
166
+ filtered[k] = merged[k];
167
+ });
168
+ allI18nObject[locale] = filtered;
169
+ });
156
170
  }
157
171
 
158
172
  const locales = Object.keys(allI18nObject); // Process each locale
@@ -315,9 +329,8 @@ class I18nNumericIndexPlugin {
315
329
  emitChunk(compilation, filenameTemplate, locale, data, jsonpFunc, groupName = null, fileType = null) {
316
330
  if (!filenameTemplate || Object.keys(data).length === 0) {
317
331
  return null;
318
- }
332
+ } // Generate content regardless of emitFiles flag (needed for runtime)
319
333
 
320
- console.log('[I18nNumericIndexPlugin] Processing chunk for locale:', locale, 'fileType:', fileType, 'dataKeys:', Object.keys(data).length, 'groupName:', groupName, 'emitFiles:', this.options.emitFiles); // Generate content regardless of emitFiles flag (needed for runtime)
321
334
 
322
335
  const content = this.generateChunkContent(data, jsonpFunc, groupName);
323
336
  let outputPath = this.constructFilePath(filenameTemplate, locale); // Handle [contenthash] placeholder in template
@@ -350,10 +363,7 @@ class I18nNumericIndexPlugin {
350
363
 
351
364
 
352
365
  if (this.options.emitFiles) {
353
- console.log('[I18nNumericIndexPlugin] Emitting file:', outputPath);
354
366
  compilation.emitAsset(outputPath, new RawSource(content));
355
- } else {
356
- console.log('[I18nNumericIndexPlugin] Skipping file emission for:', outputPath, '(emitFiles is false)');
357
367
  }
358
368
 
359
369
  return outputPath;
@@ -371,6 +381,19 @@ class I18nNumericIndexPlugin {
371
381
  return `${jsonpFunc}(${jsonString});`;
372
382
  }
373
383
 
384
+ normalizeValue(value) {
385
+ if (typeof value !== 'string') return value;
386
+ return value.replace(/\\t/g, '\t').replace(/\\n/g, '\n').replace(/\\r/g, '\r').replace(/\\f/g, '\f').replace(/\\:/g, ':').replace(/\\=/g, '=').replace(/\\ /g, ' ');
387
+ }
388
+
389
+ normalizeObjectValues(obj) {
390
+ const out = {};
391
+ Object.keys(obj || {}).forEach(k => {
392
+ out[k] = this.normalizeValue(obj[k]);
393
+ });
394
+ return out;
395
+ }
396
+
374
397
  }
375
398
 
376
399
  exports.default = I18nNumericIndexPlugin;
@@ -3,10 +3,8 @@
3
3
  const {
4
4
  getPropertiesAsJSON
5
5
  } = require('../custom_plugins/I18nSplitPlugin/utils/propertiesUtils');
6
- /**
7
- * Load i18n data from JSResources file once for all chunks
8
- */
9
6
 
7
+ const fs = require('fs');
10
8
 
11
9
  function loadJSResourcesOnce(options) {
12
10
  let jsResourcePath; // Determine the JSResource path based on configuration
@@ -36,10 +34,20 @@ function loadJSResourcesOnce(options) {
36
34
  throw new Error(`Error reading JSResource file ${jsResourcePath}: ${err.message}`);
37
35
  }
38
36
  }
39
- /**
40
- * Configure the i18n ID replace loader
41
- */
42
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
+ }
43
51
 
44
52
  function i18nIdReplaceLoaderConfig(options, webpackContext) {
45
53
  let numericMapPath; // Determine the numeric map path based on configuration
@@ -61,10 +69,12 @@ function i18nIdReplaceLoaderConfig(options, webpackContext) {
61
69
 
62
70
  const i18nKeyReplaceLoaderPath = require.resolve('../loaders/i18nIdReplaceLoader.js');
63
71
 
72
+ const numericIdMap = readNumericMapOnce(numericMapPath);
64
73
  const loaderOptions = {
65
74
  allI18nData: allI18nData,
66
75
  sourceMaps: false,
67
76
  numericMapPath: numericMapPath,
77
+ numericIdMap: numericIdMap || undefined,
68
78
  devMode: options.i18nIndexing?.devMode || false,
69
79
  includePaths: options.i18nIndexing?.loaderOptions?.includePaths || [],
70
80
  excludePaths: options.i18nIndexing?.loaderOptions?.excludePaths || ['node_modules', 'tests']
@@ -20,7 +20,16 @@ module.exports = function i18nIdReplaceLoader(source, map) {
20
20
  const resourcePath = this.resourcePath;
21
21
  this.cacheable && this.cacheable();
22
22
  const options = getOptions(this) || {};
23
- const callback = this.async(); // Skip files in excluded paths
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
+
24
33
 
25
34
  if (options.excludePaths) {
26
35
  const shouldExclude = options.excludePaths.some(excludePath => resourcePath.includes(excludePath));
@@ -42,29 +51,46 @@ module.exports = function i18nIdReplaceLoader(source, map) {
42
51
 
43
52
  if (!options.allI18nData || Object.keys(options.allI18nData).length === 0) {
44
53
  return callback(new Error(`i18nIdReplaceLoader: 'allI18nData' option is missing or empty`));
45
- } // Load numeric ID mapping
54
+ } // Load numeric ID mapping (use injected map if provided; fallback to memoized file read)
46
55
 
47
56
 
48
- let numericIdMap = null;
57
+ let numericIdMap = options.numericIdMap || null;
49
58
 
50
- if (options.numericMapPath) {
51
- try {
52
- if (fs.existsSync(options.numericMapPath)) {
53
- const fileContent = fs.readFileSync(options.numericMapPath, 'utf-8');
54
- const parsedData = JSON.parse(fileContent); // Handle both wrapped and flat formats
55
-
56
- if (parsedData) {
57
- if (parsedData.originalKeyToNumericId) {
58
- // New format with wrapper
59
- numericIdMap = parsedData.originalKeyToNumericId;
60
- } else if (typeof parsedData === 'object') {
61
- // Flat format - use directly
62
- numericIdMap = parsedData;
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;
63
88
  }
64
89
  }
90
+ } catch (e) {// ignore and proceed without map
65
91
  }
66
- } catch (err) {// Silently continue without numeric mapping
67
92
  }
93
+ } catch (e) {// ignore
68
94
  } // If no numeric map available, return source as-is
69
95
 
70
96
 
@@ -11,8 +11,6 @@ var _I18nNumericIndexHtmlInjectorPlugin = require("../custom_plugins/I18nNumeric
11
11
 
12
12
  var _readI18nValues = require("../custom_plugins/I18nSplitPlugin/readI18nValues");
13
13
 
14
- var _path = _interopRequireDefault(require("path"));
15
-
16
14
  function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { default: obj }; }
17
15
 
18
16
  function configI18nNumericIndexPlugin(options) {
@@ -22,7 +20,17 @@ function configI18nNumericIndexPlugin(options) {
22
20
 
23
21
  const i18nOpts = options.i18nIndexing;
24
22
  const isDevelopment = options.isDevelopment || false; // Get isDevelopment from options
25
- // Validate required options
23
+ // If we are only doing numeric ID replacement (no files to emit and no HTML injection, and no custom groups),
24
+ // we can skip creating plugin instances to save build time.
25
+
26
+ const emitFiles = i18nOpts.emitFiles !== undefined ? i18nOpts.emitFiles : true;
27
+ const injectHtml = i18nOpts.injectI18nUrlInIndex !== undefined ? i18nOpts.injectI18nUrlInIndex : true;
28
+ const hasCustomGroups = !!(i18nOpts.customGroups && Object.keys(i18nOpts.customGroups || {}).length);
29
+
30
+ if (!emitFiles && !injectHtml && !hasCustomGroups) {
31
+ return [];
32
+ } // Validate required options
33
+
26
34
 
27
35
  const requiredOptions = ['jsResourcePath', 'propertiesFolderPath', 'numericMapPath', 'jsonpFunc', 'htmlTemplateLabel', 'localeVarName'];
28
36
  const missingOptions = requiredOptions.filter(opt => !i18nOpts[opt]);
@@ -77,14 +85,13 @@ function configI18nNumericIndexPlugin(options) {
77
85
  jsonpFunc: i18nOpts.jsonpFunc,
78
86
  localeVarName: i18nOpts.localeVarName,
79
87
  singleFile: i18nOpts.singleFile || false,
88
+ restrictToBaseKeys: i18nOpts.restrictToBaseKeys || false,
80
89
  includeContentHash: i18nOpts.includeContentHash || false,
81
90
  generateManifest: i18nOpts.generateManifest || false,
82
91
  manifestPath: i18nOpts.manifestPath || null,
83
92
  customGroups: i18nOpts.customGroups || null,
84
93
  emitFiles: i18nOpts.emitFiles !== undefined ? i18nOpts.emitFiles : true,
85
- // Control file emission
86
- isDevelopment: isDevelopment // Pass through for reference
87
-
94
+ isDevelopment: isDevelopment
88
95
  }; // HTML injector options
89
96
 
90
97
  const htmlInjectorOptions = {
@@ -95,7 +102,6 @@ function configI18nNumericIndexPlugin(options) {
95
102
  htmlTemplateLabel: i18nOpts.htmlTemplateLabel,
96
103
  singleFile: i18nOpts.singleFile || false,
97
104
  i18nAssetsPublicPathPrefix: '',
98
- // HtmlWebpackPlugin handles publicPath
99
105
  injectI18nUrlInIndex: i18nOpts.injectI18nUrlInIndex !== undefined ? i18nOpts.injectI18nUrlInIndex : true,
100
106
  // Control HTML injection
101
107
  isDevelopment: isDevelopment
@@ -1,12 +1,12 @@
1
1
  {
2
2
  "name": "@zohodesk/client_build_tool",
3
- "version": "0.0.11-exp.16.0",
3
+ "version": "0.0.11-exp.17.0",
4
4
  "lockfileVersion": 2,
5
5
  "requires": true,
6
6
  "packages": {
7
7
  "": {
8
8
  "name": "@zohodesk/client_build_tool",
9
- "version": "0.0.11-exp.16.0",
9
+ "version": "0.0.11-exp.17.0",
10
10
  "license": "ISC",
11
11
  "dependencies": {
12
12
  "@babel/cli": "7.17.10",
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@zohodesk/client_build_tool",
3
- "version": "0.0.11-exp.17.0",
3
+ "version": "0.0.11-exp.18.0",
4
4
  "description": "A CLI tool to build web applications and client libraries",
5
5
  "main": "lib/index.js",
6
6
  "bin": {
@@ -1,59 +0,0 @@
1
- // Example configuration for single-file i18n mode
2
- // When singleFile is true, output will be: i18n/en_US.js, i18n/fr_FR.js, etc.
3
-
4
- module.exports = {
5
- i18nIndexing: {
6
- enable: true,
7
-
8
- // Output folder for all i18n files
9
- outputFolder: 'i18n',
10
-
11
- // Input paths
12
- jsResourcePath: './resources/ApplicationResources.properties',
13
- propertiesFolderPath: './resources',
14
- numericMapPath: './numericMap.json',
15
-
16
- // Single-file mode configuration
17
- singleFile: true,
18
- singleFileTemplate: '[locale].js', // Will create: i18n/en_US.js, i18n/fr_FR.js, etc.
19
-
20
- // These are ignored in single-file mode but kept for backward compatibility
21
- numericFilenameTemplate: '[locale]/numeric.i18n.js',
22
- dynamicFilenameTemplate: '[locale]/dynamic.i18n.js',
23
-
24
- // Runtime configuration
25
- jsonpFunc: 'window.loadI18n',
26
- htmlTemplateLabel: '{{--user-locale}}',
27
- localeVarName: 'window.userLangCode',
28
-
29
- // Optimization options
30
- includeContentHash: true, // Will add hash: i18n/en_US.abc123.js
31
- generateManifest: true,
32
- manifestPath: 'i18n/manifest.json', // Custom manifest location
33
-
34
- // Loader options
35
- loaderOptions: {
36
- includePaths: ['src'],
37
- excludePaths: ['node_modules', 'tests']
38
- }
39
- }
40
- };
41
-
42
- /*
43
- Output structure with these settings:
44
-
45
- build/
46
- ├── i18n/
47
- │ ├── en_US.abc123.js # Combined numeric + dynamic keys with content hash
48
- │ ├── fr_FR.def456.js
49
- │ ├── de_DE.ghi789.js
50
- │ └── manifest.json # Maps original names to hashed versions
51
- └── index.html # HTML with injected script tag for locale
52
-
53
- Manifest content example:
54
- {
55
- "i18n/en_US.js": "en_US.abc123.js",
56
- "i18n/fr_FR.js": "fr_FR.def456.js",
57
- "i18n/de_DE.js": "de_DE.ghi789.js"
58
- }
59
- */
@@ -1,22 +0,0 @@
1
- // Test configuration for i18n indexing features
2
- module.exports = {
3
- i18nIndexing: {
4
- enable: true,
5
- outputFolder: 'i18n', // Changed from default 'i18n-chunk'
6
- jsResourcePath: './test-data/JSResources.properties',
7
- propertiesFolderPath: './test-data/properties',
8
- numericMapPath: './test-data/i18n-numeric-map.json',
9
- numericFilenameTemplate: '[locale]/numeric.js',
10
- dynamicFilenameTemplate: '[locale]/dynamic.js',
11
- jsonpFunc: 'window.loadI18n',
12
- htmlTemplateLabel: '{{locale}}',
13
- localeVarName: 'window.userLocale',
14
- singleFile: false,
15
- includeContentHash: true, // Enable content hashing
16
- generateManifest: true, // Enable manifest generation
17
- loaderOptions: {
18
- includePaths: ['src'],
19
- excludePaths: ['node_modules', 'tests', 'lib']
20
- }
21
- }
22
- };