@rushstack/webpack5-localization-plugin 0.15.14 → 0.16.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (112) hide show
  1. package/CHANGELOG.json +52 -0
  2. package/CHANGELOG.md +15 -1
  3. package/dist/tsdoc-metadata.json +1 -1
  4. package/lib-esm/AssetProcessor.js +306 -0
  5. package/lib-esm/AssetProcessor.js.map +1 -0
  6. package/lib-esm/LocalizationPlugin.js +636 -0
  7. package/lib-esm/LocalizationPlugin.js.map +1 -0
  8. package/lib-esm/TrueHashPlugin.js +27 -0
  9. package/lib-esm/TrueHashPlugin.js.map +1 -0
  10. package/lib-esm/index.js +6 -0
  11. package/lib-esm/index.js.map +1 -0
  12. package/lib-esm/interfaces.js +4 -0
  13. package/lib-esm/interfaces.js.map +1 -0
  14. package/lib-esm/loaders/IResxLoaderOptions.js +4 -0
  15. package/lib-esm/loaders/IResxLoaderOptions.js.map +1 -0
  16. package/lib-esm/loaders/LoaderFactory.js +25 -0
  17. package/lib-esm/loaders/LoaderFactory.js.map +1 -0
  18. package/lib-esm/loaders/default-locale-loader.js +25 -0
  19. package/lib-esm/loaders/default-locale-loader.js.map +1 -0
  20. package/lib-esm/loaders/loc-loader.js +21 -0
  21. package/lib-esm/loaders/loc-loader.js.map +1 -0
  22. package/lib-esm/loaders/locjson-loader.js +13 -0
  23. package/lib-esm/loaders/locjson-loader.js.map +1 -0
  24. package/lib-esm/loaders/resjson-loader.js +13 -0
  25. package/lib-esm/loaders/resjson-loader.js.map +1 -0
  26. package/lib-esm/loaders/resx-loader.js +20 -0
  27. package/lib-esm/loaders/resx-loader.js.map +1 -0
  28. package/lib-esm/trueHashes.js +213 -0
  29. package/lib-esm/trueHashes.js.map +1 -0
  30. package/lib-esm/utilities/Constants.js +17 -0
  31. package/lib-esm/utilities/Constants.js.map +1 -0
  32. package/lib-esm/utilities/EntityMarker.js +16 -0
  33. package/lib-esm/utilities/EntityMarker.js.map +1 -0
  34. package/lib-esm/utilities/LoaderTerminalProvider.js +24 -0
  35. package/lib-esm/utilities/LoaderTerminalProvider.js.map +1 -0
  36. package/lib-esm/utilities/chunkUtilities.js +6 -0
  37. package/lib-esm/utilities/chunkUtilities.js.map +1 -0
  38. package/lib-esm/webpackInterfaces.js +4 -0
  39. package/lib-esm/webpackInterfaces.js.map +1 -0
  40. package/package.json +33 -9
  41. /package/{lib → lib-commonjs}/AssetProcessor.js +0 -0
  42. /package/{lib → lib-commonjs}/AssetProcessor.js.map +0 -0
  43. /package/{lib → lib-commonjs}/LocalizationPlugin.js +0 -0
  44. /package/{lib → lib-commonjs}/LocalizationPlugin.js.map +0 -0
  45. /package/{lib → lib-commonjs}/TrueHashPlugin.js +0 -0
  46. /package/{lib → lib-commonjs}/TrueHashPlugin.js.map +0 -0
  47. /package/{lib → lib-commonjs}/index.js +0 -0
  48. /package/{lib → lib-commonjs}/index.js.map +0 -0
  49. /package/{lib → lib-commonjs}/interfaces.js +0 -0
  50. /package/{lib → lib-commonjs}/interfaces.js.map +0 -0
  51. /package/{lib → lib-commonjs}/loaders/IResxLoaderOptions.js +0 -0
  52. /package/{lib → lib-commonjs}/loaders/IResxLoaderOptions.js.map +0 -0
  53. /package/{lib → lib-commonjs}/loaders/LoaderFactory.js +0 -0
  54. /package/{lib → lib-commonjs}/loaders/LoaderFactory.js.map +0 -0
  55. /package/{lib → lib-commonjs}/loaders/default-locale-loader.js +0 -0
  56. /package/{lib → lib-commonjs}/loaders/default-locale-loader.js.map +0 -0
  57. /package/{lib → lib-commonjs}/loaders/loc-loader.js +0 -0
  58. /package/{lib → lib-commonjs}/loaders/loc-loader.js.map +0 -0
  59. /package/{lib → lib-commonjs}/loaders/locjson-loader.js +0 -0
  60. /package/{lib → lib-commonjs}/loaders/locjson-loader.js.map +0 -0
  61. /package/{lib → lib-commonjs}/loaders/resjson-loader.js +0 -0
  62. /package/{lib → lib-commonjs}/loaders/resjson-loader.js.map +0 -0
  63. /package/{lib → lib-commonjs}/loaders/resx-loader.js +0 -0
  64. /package/{lib → lib-commonjs}/loaders/resx-loader.js.map +0 -0
  65. /package/{lib → lib-commonjs}/trueHashes.js +0 -0
  66. /package/{lib → lib-commonjs}/trueHashes.js.map +0 -0
  67. /package/{lib → lib-commonjs}/utilities/Constants.js +0 -0
  68. /package/{lib → lib-commonjs}/utilities/Constants.js.map +0 -0
  69. /package/{lib → lib-commonjs}/utilities/EntityMarker.js +0 -0
  70. /package/{lib → lib-commonjs}/utilities/EntityMarker.js.map +0 -0
  71. /package/{lib → lib-commonjs}/utilities/LoaderTerminalProvider.js +0 -0
  72. /package/{lib → lib-commonjs}/utilities/LoaderTerminalProvider.js.map +0 -0
  73. /package/{lib → lib-commonjs}/utilities/chunkUtilities.js +0 -0
  74. /package/{lib → lib-commonjs}/utilities/chunkUtilities.js.map +0 -0
  75. /package/{lib → lib-commonjs}/webpackInterfaces.js +0 -0
  76. /package/{lib → lib-commonjs}/webpackInterfaces.js.map +0 -0
  77. /package/{lib → lib-dts}/AssetProcessor.d.ts +0 -0
  78. /package/{lib → lib-dts}/AssetProcessor.d.ts.map +0 -0
  79. /package/{lib → lib-dts}/LocalizationPlugin.d.ts +0 -0
  80. /package/{lib → lib-dts}/LocalizationPlugin.d.ts.map +0 -0
  81. /package/{lib → lib-dts}/TrueHashPlugin.d.ts +0 -0
  82. /package/{lib → lib-dts}/TrueHashPlugin.d.ts.map +0 -0
  83. /package/{lib → lib-dts}/index.d.ts +0 -0
  84. /package/{lib → lib-dts}/index.d.ts.map +0 -0
  85. /package/{lib → lib-dts}/interfaces.d.ts +0 -0
  86. /package/{lib → lib-dts}/interfaces.d.ts.map +0 -0
  87. /package/{lib → lib-dts}/loaders/IResxLoaderOptions.d.ts +0 -0
  88. /package/{lib → lib-dts}/loaders/IResxLoaderOptions.d.ts.map +0 -0
  89. /package/{lib → lib-dts}/loaders/LoaderFactory.d.ts +0 -0
  90. /package/{lib → lib-dts}/loaders/LoaderFactory.d.ts.map +0 -0
  91. /package/{lib → lib-dts}/loaders/default-locale-loader.d.ts +0 -0
  92. /package/{lib → lib-dts}/loaders/default-locale-loader.d.ts.map +0 -0
  93. /package/{lib → lib-dts}/loaders/loc-loader.d.ts +0 -0
  94. /package/{lib → lib-dts}/loaders/loc-loader.d.ts.map +0 -0
  95. /package/{lib → lib-dts}/loaders/locjson-loader.d.ts +0 -0
  96. /package/{lib → lib-dts}/loaders/locjson-loader.d.ts.map +0 -0
  97. /package/{lib → lib-dts}/loaders/resjson-loader.d.ts +0 -0
  98. /package/{lib → lib-dts}/loaders/resjson-loader.d.ts.map +0 -0
  99. /package/{lib → lib-dts}/loaders/resx-loader.d.ts +0 -0
  100. /package/{lib → lib-dts}/loaders/resx-loader.d.ts.map +0 -0
  101. /package/{lib → lib-dts}/trueHashes.d.ts +0 -0
  102. /package/{lib → lib-dts}/trueHashes.d.ts.map +0 -0
  103. /package/{lib → lib-dts}/utilities/Constants.d.ts +0 -0
  104. /package/{lib → lib-dts}/utilities/Constants.d.ts.map +0 -0
  105. /package/{lib → lib-dts}/utilities/EntityMarker.d.ts +0 -0
  106. /package/{lib → lib-dts}/utilities/EntityMarker.d.ts.map +0 -0
  107. /package/{lib → lib-dts}/utilities/LoaderTerminalProvider.d.ts +0 -0
  108. /package/{lib → lib-dts}/utilities/LoaderTerminalProvider.d.ts.map +0 -0
  109. /package/{lib → lib-dts}/utilities/chunkUtilities.d.ts +0 -0
  110. /package/{lib → lib-dts}/utilities/chunkUtilities.d.ts.map +0 -0
  111. /package/{lib → lib-dts}/webpackInterfaces.d.ts +0 -0
  112. /package/{lib → lib-dts}/webpackInterfaces.d.ts.map +0 -0
@@ -0,0 +1,636 @@
1
+ // Copyright (c) Microsoft Corporation. All rights reserved. Licensed under the MIT license.
2
+ // See LICENSE in the project root for license information.
3
+ import * as path from 'node:path';
4
+ import { getPseudolocalizer, parseResJson } from '@rushstack/localization-utilities';
5
+ import { Async } from '@rushstack/node-core-library/lib/Async';
6
+ import * as Constants from './utilities/Constants';
7
+ import { markEntity, getMark } from './utilities/EntityMarker';
8
+ import { processLocalizedAssetCachedAsync, processNonLocalizedAssetCachedAsync } from './AssetProcessor';
9
+ import { getHashFunction, updateAssetHashes } from './trueHashes';
10
+ import { chunkIsJs } from './utilities/chunkUtilities';
11
+ const PLUGIN_NAME = 'localization';
12
+ const pluginForCompiler = new WeakMap();
13
+ /**
14
+ * Gets the instance of the LocalizationPlugin bound to the specified webpack compiler.
15
+ * Used by loaders.
16
+ */
17
+ export function getPluginInstance(compiler) {
18
+ const instance = compiler && pluginForCompiler.get(compiler);
19
+ if (!instance) {
20
+ throw new Error(`Could not find a LocalizationPlugin instance for the current webpack compiler!`);
21
+ }
22
+ return instance;
23
+ }
24
+ /**
25
+ * This plugin facilitates localization in webpack.
26
+ *
27
+ * @public
28
+ */
29
+ export class LocalizationPlugin {
30
+ constructor(options) {
31
+ this._locFiles = new Map();
32
+ this._resolvedTranslatedStringsFromOptions = new Map();
33
+ this._stringPlaceholderBySuffix = new Map();
34
+ this._customDataPlaceholderBySuffix = new Map();
35
+ this._customDataPlaceholderByUniqueId = new Map();
36
+ this._pseudolocalizers = new Map();
37
+ /**
38
+ * The set of locales that have translations provided.
39
+ */
40
+ this._translatedLocales = new Set();
41
+ this._options = options;
42
+ }
43
+ /**
44
+ * Apply this plugin to the specified webpack compiler.
45
+ */
46
+ apply(compiler) {
47
+ pluginForCompiler.set(compiler, this);
48
+ // https://github.com/webpack/webpack-dev-server/pull/1929/files#diff-15fb51940da53816af13330d8ce69b4eR66
49
+ const isWebpackDevServer = process.env.WEBPACK_DEV_SERVER === 'true';
50
+ const { errors, warnings } = this._initializeAndValidateOptions(compiler, isWebpackDevServer);
51
+ if (errors.length > 0 || warnings.length > 0) {
52
+ compiler.hooks.compilation.tap(PLUGIN_NAME, (compilation) => {
53
+ compilation.errors.push(...errors);
54
+ compilation.warnings.push(...warnings);
55
+ });
56
+ if (errors.length > 0) {
57
+ // If there are any errors, just pass through the resources in source and don't do any
58
+ // additional configuration
59
+ return;
60
+ }
61
+ }
62
+ const { webpack: thisWebpack } = compiler;
63
+ const { WebpackError, runtime: { GetChunkFilenameRuntimeModule } } = thisWebpack;
64
+ // Side-channel for async chunk URL generator chunk, since the actual chunk is completely inaccessible
65
+ // from the assetPath hook below when invoked to build the async URL generator
66
+ let chunkWithAsyncURLGenerator;
67
+ const originalGenerate = GetChunkFilenameRuntimeModule.prototype.generate;
68
+ GetChunkFilenameRuntimeModule.prototype.generate = function () {
69
+ // `originalGenerate` will invoke `getAssetPath` to produce the async URL generator
70
+ // Need to know the identity of the containing chunk to correctly produce the asset path expression
71
+ chunkWithAsyncURLGenerator = this.chunk;
72
+ const result = originalGenerate.call(this);
73
+ // Unset after the call finishes because we are no longer generating async URL generators
74
+ chunkWithAsyncURLGenerator = undefined;
75
+ return result;
76
+ };
77
+ const asyncGeneratorTest = /^\" \+/;
78
+ const { runtimeLocaleExpression } = this._options;
79
+ compiler.hooks.thisCompilation.tap(PLUGIN_NAME, (compilation) => {
80
+ var _a;
81
+ let hashFn;
82
+ if (this._options.realContentHash) {
83
+ if (runtimeLocaleExpression) {
84
+ compilation.errors.push(new WebpackError(`The "realContentHash" option cannot be used in conjunction with "runtimeLocaleExpression".`));
85
+ }
86
+ else {
87
+ hashFn = getHashFunction({ thisWebpack, compilation });
88
+ }
89
+ }
90
+ else if ((_a = compiler.options.optimization) === null || _a === void 0 ? void 0 : _a.realContentHash) {
91
+ compilation.errors.push(new thisWebpack.WebpackError(`The \`optimization.realContentHash\` option is set and the ${LocalizationPlugin.name}'s ` +
92
+ '`realContentHash` option is not set. This will likely produce invalid results. Consider setting the ' +
93
+ `\`realContentHash\` option in the ${LocalizationPlugin.name} plugin.`));
94
+ }
95
+ const chunksWithUrlGenerators = new WeakSet();
96
+ compilation.hooks.assetPath.tap(PLUGIN_NAME, (assetPath, options) => {
97
+ var _a;
98
+ const { chunkGraph } = compilation;
99
+ if (options.contentHashType === 'javascript' &&
100
+ assetPath.match(Constants.LOCALE_FILENAME_TOKEN_REGEX)) {
101
+ // Does this look like an async chunk URL generator?
102
+ if (typeof ((_a = options.chunk) === null || _a === void 0 ? void 0 : _a.id) === 'string' && options.chunk.id.match(asyncGeneratorTest)) {
103
+ const chunkIdsWithStrings = new Set();
104
+ const chunkIdsWithoutStrings = new Set();
105
+ const activeChunkWithAsyncUrlGenerator = chunkWithAsyncURLGenerator;
106
+ if (!activeChunkWithAsyncUrlGenerator) {
107
+ compilation.errors.push(new WebpackError(`No active chunk while constructing async chunk URL generator!`));
108
+ return assetPath;
109
+ }
110
+ const asyncChunks = activeChunkWithAsyncUrlGenerator.getAllAsyncChunks();
111
+ for (const asyncChunk of asyncChunks) {
112
+ const chunkId = asyncChunk.id;
113
+ if (chunkId === null || chunkId === undefined) {
114
+ throw new Error(`Chunk "${asyncChunk.name}"'s ID is null or undefined.`);
115
+ }
116
+ if (_chunkHasLocalizedModules(chunkGraph, asyncChunk, runtimeLocaleExpression)) {
117
+ chunkIdsWithStrings.add(chunkId);
118
+ }
119
+ else {
120
+ chunkIdsWithoutStrings.add(chunkId);
121
+ }
122
+ }
123
+ return assetPath.replace(Constants.LOCALE_FILENAME_TOKEN_REGEX, () => {
124
+ // Use a replacer function so that we don't need to escape anything in the return value
125
+ // If the runtime chunk is itself localized, forcibly match the locale of the runtime chunk
126
+ // Otherwise prefer the runtime expression if specified
127
+ const localeExpression = (!_chunkHasLocalizedModules(chunkGraph, activeChunkWithAsyncUrlGenerator, runtimeLocaleExpression) &&
128
+ runtimeLocaleExpression) ||
129
+ Constants.JSONP_PLACEHOLDER;
130
+ if (localeExpression === Constants.JSONP_PLACEHOLDER) {
131
+ chunksWithUrlGenerators.add(activeChunkWithAsyncUrlGenerator);
132
+ }
133
+ if (chunkIdsWithStrings.size === 0) {
134
+ return this._formatLocaleForFilename(this._noStringsLocaleName, undefined);
135
+ }
136
+ else if (chunkIdsWithoutStrings.size === 0) {
137
+ return `" + ${localeExpression} + "`;
138
+ }
139
+ else {
140
+ // Generate an object that is used to select between <locale> and <nostrings locale> for each chunk ID
141
+ // Method: pick the smaller set of (localized, non-localized) and map that to 1 (a truthy value)
142
+ // All other IDs map to `undefined` (a falsy value), so we then use the ternary operator to select
143
+ // the appropriate token
144
+ //
145
+ // This can be improved in the future. We can maybe sort the chunks such that the chunks below a certain ID
146
+ // number are localized and the those above are not.
147
+ const chunkMapping = {};
148
+ // Use the map with the fewest values to shorten the expression
149
+ const isLocalizedSmaller = chunkIdsWithStrings.size <= chunkIdsWithoutStrings.size;
150
+ // These are the ids for which the expression should evaluate to a truthy value
151
+ const smallerSet = isLocalizedSmaller
152
+ ? chunkIdsWithStrings
153
+ : chunkIdsWithoutStrings;
154
+ for (const id of smallerSet) {
155
+ chunkMapping[id] = 1;
156
+ }
157
+ const noLocaleExpression = JSON.stringify(this._formatLocaleForFilename(this._noStringsLocaleName, undefined));
158
+ return `" + (${JSON.stringify(chunkMapping)}[chunkId]?${isLocalizedSmaller ? localeExpression : noLocaleExpression}:${isLocalizedSmaller ? noLocaleExpression : localeExpression}) + "`;
159
+ }
160
+ });
161
+ }
162
+ else {
163
+ let locale = options.locale;
164
+ if (!locale) {
165
+ const isLocalized = _chunkHasLocalizedModules(chunkGraph, options.chunk, runtimeLocaleExpression);
166
+ // Ensure that the initial name maps to a file that should exist in the final output
167
+ locale = isLocalized ? this._defaultLocale : this._noStringsLocaleName;
168
+ }
169
+ return assetPath.replace(Constants.LOCALE_FILENAME_TOKEN_REGEX, this._formatLocaleForFilename(locale, undefined));
170
+ }
171
+ }
172
+ else {
173
+ return assetPath;
174
+ }
175
+ });
176
+ const { outputOptions } = compilation;
177
+ // For compatibility with minifiers, need to generate the additional assets after the optimize process runs
178
+ compilation.hooks.processAssets.tapPromise({
179
+ name: PLUGIN_NAME,
180
+ // Generating derived assets, but explicitly want to create them *after* asset optimization
181
+ stage: compiler.webpack.Compilation.PROCESS_ASSETS_STAGE_DEV_TOOLING - 1
182
+ }, async () => {
183
+ const locales = this._translatedLocales;
184
+ const { chunkGraph, chunks } = compilation;
185
+ const { localizationStats: statsOptions } = this._options;
186
+ const filesByChunkName = statsOptions
187
+ ? new Map()
188
+ : undefined;
189
+ const localizedEntryPointNames = [];
190
+ const localizedChunkNames = [];
191
+ const cache = compilation.getCache(PLUGIN_NAME);
192
+ await Async.forEachAsync(chunks, async (chunk) => {
193
+ if (!chunkIsJs(chunk, chunkGraph)) {
194
+ return;
195
+ }
196
+ const isLocalized = _chunkHasLocalizedModules(chunkGraph, chunk, runtimeLocaleExpression);
197
+ const template = chunk.filenameTemplate ||
198
+ (chunk.hasRuntime() ? outputOptions.filename : outputOptions.chunkFilename);
199
+ const defaultAssetName = compilation.getPath(template, {
200
+ chunk,
201
+ contentHashType: 'javascript'
202
+ // Without locale this should return the name of the default asset
203
+ });
204
+ const asset = compilation.getAsset(defaultAssetName);
205
+ if (!asset) {
206
+ compilation.errors.push(new WebpackError(`Missing expected chunk asset ${defaultAssetName}`));
207
+ return;
208
+ }
209
+ if (isLocalized) {
210
+ const localizedAssets = await processLocalizedAssetCachedAsync({
211
+ // Global values
212
+ plugin: this,
213
+ compilation,
214
+ cache,
215
+ locales,
216
+ defaultLocale: this._defaultLocale,
217
+ passthroughLocaleName: this._passthroughLocaleName,
218
+ fillMissingTranslationStrings: this._fillMissingTranslationStrings,
219
+ formatLocaleForFilenameFn: this._formatLocaleForFilename,
220
+ // Chunk-specific values
221
+ chunk,
222
+ asset,
223
+ filenameTemplate: template
224
+ });
225
+ if (filesByChunkName && chunk.name) {
226
+ filesByChunkName.set(chunk.name, localizedAssets);
227
+ (chunk.hasRuntime() ? localizedEntryPointNames : localizedChunkNames).push(chunk.name);
228
+ }
229
+ }
230
+ else {
231
+ await processNonLocalizedAssetCachedAsync({
232
+ // Global values
233
+ plugin: this,
234
+ compilation,
235
+ cache,
236
+ hasUrlGenerator: chunksWithUrlGenerators.has(chunk),
237
+ noStringsLocaleName: this._noStringsLocaleName,
238
+ formatLocaleForFilenameFn: this._formatLocaleForFilename,
239
+ // Chunk-specific values
240
+ chunk,
241
+ asset,
242
+ fileName: defaultAssetName
243
+ });
244
+ }
245
+ }, {
246
+ // Only async component is the cache layer
247
+ concurrency: 20
248
+ });
249
+ if (hashFn) {
250
+ updateAssetHashes({
251
+ thisWebpack,
252
+ compilation,
253
+ hashFn,
254
+ filesByChunkName
255
+ });
256
+ }
257
+ // Since the stats generation doesn't depend on content, do it immediately
258
+ if (statsOptions && filesByChunkName) {
259
+ const localizationStats = {
260
+ entrypoints: {},
261
+ namedChunkGroups: {}
262
+ };
263
+ // Sort in lexicographic order to ensure stable output
264
+ localizedChunkNames.sort();
265
+ for (const chunkName of localizedChunkNames) {
266
+ localizationStats.namedChunkGroups[chunkName] = {
267
+ localizedAssets: filesByChunkName.get(chunkName)
268
+ };
269
+ }
270
+ // Sort in lexicographic order to ensure stable output
271
+ localizedEntryPointNames.sort();
272
+ for (const chunkName of localizedEntryPointNames) {
273
+ localizationStats.entrypoints[chunkName] = {
274
+ localizedAssets: filesByChunkName.get(chunkName)
275
+ };
276
+ }
277
+ const { dropPath, callback } = statsOptions;
278
+ if (dropPath) {
279
+ compilation.emitAsset(dropPath, new compiler.webpack.sources.RawSource(JSON.stringify(localizationStats)));
280
+ }
281
+ if (callback) {
282
+ try {
283
+ callback(localizationStats, compilation);
284
+ }
285
+ catch (e) {
286
+ /* swallow errors from the callback */
287
+ }
288
+ }
289
+ }
290
+ });
291
+ });
292
+ }
293
+ /**
294
+ * @public
295
+ *
296
+ * @returns An object mapping the string keys to placeholders
297
+ */
298
+ async addDefaultLocFileAsync(context, localizedFileKey, localizedResourceData) {
299
+ const locFileData = convertLocalizationFileToLocData(localizedResourceData);
300
+ const fileInfo = this._addLocFileAndGetPlaceholders(this._defaultLocale, localizedFileKey, locFileData);
301
+ const missingLocales = [];
302
+ for (const [translatedLocaleName, translatedStrings] of this._resolvedTranslatedStringsFromOptions) {
303
+ const translatedLocFileFromOptions = translatedStrings.get(localizedFileKey);
304
+ if (!translatedLocFileFromOptions) {
305
+ missingLocales.push(translatedLocaleName);
306
+ }
307
+ else {
308
+ const translatedLocFileData = await normalizeLocalizedDataAsync(context, translatedLocFileFromOptions);
309
+ fileInfo.translations.set(translatedLocaleName, translatedLocFileData);
310
+ }
311
+ }
312
+ const { resolveMissingTranslatedStrings } = this._options.localizedData;
313
+ if (missingLocales.length > 0 && resolveMissingTranslatedStrings) {
314
+ let resolvedTranslatedData = undefined;
315
+ try {
316
+ resolvedTranslatedData = await resolveMissingTranslatedStrings(missingLocales, localizedFileKey, context);
317
+ }
318
+ catch (e) {
319
+ context.emitError(e);
320
+ }
321
+ if (resolvedTranslatedData) {
322
+ const iterable = resolvedTranslatedData instanceof Map
323
+ ? resolvedTranslatedData.entries()
324
+ : Object.entries(resolvedTranslatedData);
325
+ for (const [resolvedLocaleName, resolvedLocaleData] of iterable) {
326
+ if (resolvedLocaleData) {
327
+ const translatedLocFileData = await normalizeLocalizedDataAsync(context, resolvedLocaleData);
328
+ fileInfo.translations.set(resolvedLocaleName, translatedLocFileData);
329
+ }
330
+ }
331
+ }
332
+ }
333
+ for (const [pseudolocaleName, pseudolocalizer] of this._pseudolocalizers) {
334
+ const pseudolocFileData = new Map();
335
+ for (const [stringName, stringValue] of locFileData) {
336
+ pseudolocFileData.set(stringName, pseudolocalizer(stringValue));
337
+ }
338
+ fileInfo.translations.set(pseudolocaleName, pseudolocFileData);
339
+ }
340
+ markEntity(context._module, true);
341
+ return fileInfo.renderedPlaceholders;
342
+ }
343
+ /**
344
+ * @public
345
+ */
346
+ getPlaceholder(localizedFileKey, stringName) {
347
+ const file = this._locFiles.get(localizedFileKey);
348
+ if (!file) {
349
+ return undefined;
350
+ }
351
+ return file.placeholders.get(stringName);
352
+ }
353
+ /**
354
+ * @beta
355
+ */
356
+ getCustomDataPlaceholderForValueFunction(valueForLocaleFn, placeholderUniqueId) {
357
+ let placeholder = this._customDataPlaceholderByUniqueId.get(placeholderUniqueId);
358
+ if (!placeholder) {
359
+ // Get a hash of the unique ID to make sure its value doesn't interfere with our placeholder tokens
360
+ const suffix = Buffer.from(placeholderUniqueId, 'utf-8').toString('hex');
361
+ placeholder = {
362
+ value: `${Constants.STRING_PLACEHOLDER_PREFIX}_${Constants.CUSTOM_PLACEHOLDER_LABEL}_${suffix}_`,
363
+ suffix,
364
+ valueForLocaleFn
365
+ };
366
+ this._customDataPlaceholderBySuffix.set(suffix, placeholder);
367
+ this._customDataPlaceholderByUniqueId.set(placeholderUniqueId, placeholder);
368
+ }
369
+ else if (placeholder.valueForLocaleFn !== valueForLocaleFn) {
370
+ throw new Error(`${this.getCustomDataPlaceholderForValueFunction.name} has already been called with "${placeholderUniqueId}" ` +
371
+ `and a different valueForLocaleFn.`);
372
+ }
373
+ return placeholder.value;
374
+ }
375
+ /**
376
+ * @internal
377
+ */
378
+ _getStringDataForSerialNumber(suffix) {
379
+ return this._stringPlaceholderBySuffix.get(suffix);
380
+ }
381
+ /**
382
+ * @internal
383
+ */
384
+ _getCustomDataForSerialNumber(suffix) {
385
+ return this._customDataPlaceholderBySuffix.get(suffix);
386
+ }
387
+ _addLocFileAndGetPlaceholders(localeName, localizedFileKey, localizedFileData) {
388
+ let fileInfo = this._locFiles.get(localizedFileKey);
389
+ if (!fileInfo) {
390
+ fileInfo = {
391
+ placeholders: new Map(),
392
+ translations: new Map(),
393
+ renderedPlaceholders: {}
394
+ };
395
+ this._locFiles.set(localizedFileKey, fileInfo);
396
+ }
397
+ const { placeholders, translations } = fileInfo;
398
+ const locFilePrefix = Buffer.from(localizedFileKey, 'utf-8').toString('hex') + '$';
399
+ const resultObject = {};
400
+ for (const stringName of localizedFileData.keys()) {
401
+ let placeholder = placeholders.get(stringName);
402
+ if (!placeholder) {
403
+ const suffix = `${locFilePrefix}${Buffer.from(stringName, 'utf-8').toString('hex')}`;
404
+ placeholder = {
405
+ value: `${Constants.STRING_PLACEHOLDER_PREFIX}_${Constants.STRING_PLACEHOLDER_LABEL}_\\_${suffix}_`,
406
+ suffix,
407
+ translations: translations,
408
+ locFilePath: localizedFileKey,
409
+ stringName
410
+ };
411
+ placeholders.set(stringName, placeholder);
412
+ this._stringPlaceholderBySuffix.set(suffix, placeholder);
413
+ }
414
+ resultObject[stringName] = placeholder.value;
415
+ }
416
+ translations.set(localeName, localizedFileData);
417
+ fileInfo.renderedPlaceholders = resultObject;
418
+ return fileInfo;
419
+ }
420
+ _initializeAndValidateOptions(compiler, isWebpackDevServer) {
421
+ var _a;
422
+ const errors = [];
423
+ const warnings = [];
424
+ const { WebpackError } = compiler.webpack;
425
+ const { options: configuration } = compiler;
426
+ const LOCALE_NAME_REGEX = /[a-z-]/i;
427
+ function ensureValidLocaleName(localeName) {
428
+ if (!localeName.match(LOCALE_NAME_REGEX)) {
429
+ errors.push(new WebpackError(`Invalid locale name: ${localeName}. Locale names may only contain letters and hyphens.`));
430
+ return false;
431
+ }
432
+ else {
433
+ return true;
434
+ }
435
+ }
436
+ // START configuration
437
+ if (!configuration.output ||
438
+ !configuration.output.filename ||
439
+ typeof configuration.output.filename !== 'string' ||
440
+ configuration.output.filename.indexOf(Constants.LOCALE_FILENAME_TOKEN) === -1) {
441
+ errors.push(new WebpackError('The configuration.output.filename property must be provided, must be a string, and must include ' +
442
+ `the ${Constants.LOCALE_FILENAME_TOKEN} placeholder`));
443
+ }
444
+ // END configuration
445
+ // START misc options
446
+ // START options.localizedData
447
+ const { localizedData } = this._options;
448
+ if (localizedData) {
449
+ // START options.localizedData.passthroughLocale
450
+ const { passthroughLocale } = localizedData;
451
+ if (passthroughLocale) {
452
+ const { usePassthroughLocale, passthroughLocaleName = 'passthrough' } = passthroughLocale;
453
+ if (usePassthroughLocale) {
454
+ this._passthroughLocaleName = passthroughLocaleName;
455
+ this._translatedLocales.add(passthroughLocaleName);
456
+ }
457
+ }
458
+ // END options.localizedData.passthroughLocale
459
+ // START options.localizedData.translatedStrings
460
+ const resolveRelativeToContext = (((_a = configuration.context) === null || _a === void 0 ? void 0 : _a.startsWith('/')) ? path.posix.resolve : path.resolve).bind(0, configuration.context);
461
+ const { translatedStrings } = localizedData;
462
+ this._resolvedTranslatedStringsFromOptions.clear();
463
+ if (translatedStrings) {
464
+ for (const [localeName, locale] of Object.entries(translatedStrings)) {
465
+ if (this._translatedLocales.has(localeName)) {
466
+ errors.push(new WebpackError(`The locale "${localeName}" appears multiple times. ` +
467
+ 'There may be multiple instances with different casing.'));
468
+ return { errors, warnings };
469
+ }
470
+ if (!ensureValidLocaleName(localeName)) {
471
+ return { errors, warnings };
472
+ }
473
+ this._translatedLocales.add(localeName);
474
+ const resolvedFromOptionsForLocale = new Map();
475
+ this._resolvedTranslatedStringsFromOptions.set(localeName, resolvedFromOptionsForLocale);
476
+ for (const [locFilePath, locFileDataFromOptions] of Object.entries(locale)) {
477
+ const normalizedLocFilePath = resolveRelativeToContext(locFilePath);
478
+ if (resolvedFromOptionsForLocale.has(normalizedLocFilePath)) {
479
+ errors.push(new WebpackError(`The localization file path "${locFilePath}" appears multiple times in locale ${localeName}. ` +
480
+ 'There may be multiple instances with different casing.'));
481
+ return { errors, warnings };
482
+ }
483
+ const normalizedLocFileDataFromOptions = typeof locFileDataFromOptions === 'string'
484
+ ? resolveRelativeToContext(locFileDataFromOptions)
485
+ : locFileDataFromOptions;
486
+ resolvedFromOptionsForLocale.set(normalizedLocFilePath, normalizedLocFileDataFromOptions);
487
+ }
488
+ }
489
+ }
490
+ // END options.localizedData.translatedStrings
491
+ // START options.localizedData.defaultLocale
492
+ const { defaultLocale } = localizedData;
493
+ if (defaultLocale) {
494
+ const { localeName, fillMissingTranslationStrings } = defaultLocale;
495
+ if (localeName) {
496
+ if (this._translatedLocales.has(localeName)) {
497
+ errors.push(new WebpackError('The default locale is also specified in the translated strings.'));
498
+ return { errors, warnings };
499
+ }
500
+ else if (!ensureValidLocaleName(localeName)) {
501
+ return { errors, warnings };
502
+ }
503
+ this._translatedLocales.add(localeName);
504
+ this._defaultLocale = localeName;
505
+ this._fillMissingTranslationStrings = !!fillMissingTranslationStrings;
506
+ }
507
+ else {
508
+ errors.push(new WebpackError('Missing default locale name'));
509
+ return { errors, warnings };
510
+ }
511
+ }
512
+ else {
513
+ errors.push(new WebpackError('Missing default locale options.'));
514
+ return { errors, warnings };
515
+ }
516
+ // END options.localizedData.defaultLocale
517
+ // START options.localizedData.pseudoLocales
518
+ const { pseudolocales } = localizedData;
519
+ if (pseudolocales) {
520
+ for (const [pseudolocaleName, pseudoLocaleOpts] of Object.entries(pseudolocales)) {
521
+ if (this._defaultLocale === pseudolocaleName) {
522
+ errors.push(new WebpackError(`A pseudolocale (${pseudolocaleName}) name is also the default locale name.`));
523
+ return { errors, warnings };
524
+ }
525
+ if (this._translatedLocales.has(pseudolocaleName)) {
526
+ errors.push(new WebpackError(`A pseudolocale (${pseudolocaleName}) name is also specified in the translated strings.`));
527
+ return { errors, warnings };
528
+ }
529
+ this._pseudolocalizers.set(pseudolocaleName, getPseudolocalizer(pseudoLocaleOpts));
530
+ this._translatedLocales.add(pseudolocaleName);
531
+ }
532
+ }
533
+ // END options.localizedData.pseudoLocales
534
+ }
535
+ else if (!isWebpackDevServer) {
536
+ throw new Error('Localized data must be provided unless webpack dev server is running.');
537
+ }
538
+ // END options.localizedData
539
+ // START options.noStringsLocaleName
540
+ const { noStringsLocaleName } = this._options;
541
+ if (noStringsLocaleName === undefined ||
542
+ noStringsLocaleName === null ||
543
+ !ensureValidLocaleName(noStringsLocaleName)) {
544
+ this._noStringsLocaleName = 'none';
545
+ }
546
+ else {
547
+ this._noStringsLocaleName = noStringsLocaleName;
548
+ }
549
+ // END options.noStringsLocaleName
550
+ // START options.formatLocaleForFilename
551
+ const { formatLocaleForFilename = (localeName) => localeName } = this._options;
552
+ this._formatLocaleForFilename = formatLocaleForFilename;
553
+ // END options.formatLocaleForFilename
554
+ return { errors, warnings };
555
+ }
556
+ }
557
+ function _chunkHasLocalizedModules(chunkGraph, chunk, runtimeLocaleExpression) {
558
+ let chunkHasAnyLocModules = getMark(chunk);
559
+ if (chunkHasAnyLocModules === undefined) {
560
+ chunkHasAnyLocModules = false;
561
+ const candidateModules = chunkGraph.getChunkModulesIterableBySourceType(chunk, 'javascript');
562
+ if (candidateModules) {
563
+ outer: for (const module of candidateModules) {
564
+ const moduleMark = getMark(module);
565
+ if (moduleMark) {
566
+ chunkHasAnyLocModules = true;
567
+ break;
568
+ }
569
+ else if (moduleMark === false) {
570
+ continue;
571
+ }
572
+ // Is this a concatenated module?
573
+ const { _modules: modules } = module;
574
+ if (modules) {
575
+ for (const nestedModule of modules) {
576
+ if (getMark(nestedModule)) {
577
+ markEntity(module, true);
578
+ chunkHasAnyLocModules = true;
579
+ break outer;
580
+ }
581
+ }
582
+ markEntity(module, false);
583
+ }
584
+ }
585
+ }
586
+ // If this chunk doesn't directly contain any localized resources, it still
587
+ // needs to be localized if it's an entrypoint chunk (i.e. - it has a runtime)
588
+ // and it loads localized async chunks.
589
+ // In that case, the generated chunk URL generation code needs to contain
590
+ // the locale name.
591
+ if (!chunkHasAnyLocModules && !runtimeLocaleExpression && chunk.hasRuntime()) {
592
+ for (const asyncChunk of chunk.getAllAsyncChunks()) {
593
+ if (_chunkHasLocalizedModules(chunkGraph, asyncChunk, runtimeLocaleExpression)) {
594
+ chunkHasAnyLocModules = true;
595
+ break;
596
+ }
597
+ }
598
+ }
599
+ markEntity(chunk, chunkHasAnyLocModules);
600
+ }
601
+ return chunkHasAnyLocModules;
602
+ }
603
+ function convertLocalizationFileToLocData(locFile) {
604
+ const locFileData = new Map();
605
+ for (const [stringName, locFileEntry] of Object.entries(locFile)) {
606
+ locFileData.set(stringName, locFileEntry.value);
607
+ }
608
+ return locFileData;
609
+ }
610
+ async function normalizeLocalizedDataAsync(context, localizedData) {
611
+ if (typeof localizedData === 'string') {
612
+ // The value is the path to a file. Add it as a file dependency
613
+ context.addDependency(localizedData);
614
+ const content = await new Promise((resolve, reject) => {
615
+ // Use context.fs so that the plugin is compatible with overriding compiler.inputFileSystem
616
+ context.fs.readFile(localizedData, (err, data) => {
617
+ if (err) {
618
+ return reject(err);
619
+ }
620
+ else if (!data) {
621
+ return reject(new Error(`No data in ${localizedData}`));
622
+ }
623
+ resolve(data.toString());
624
+ });
625
+ });
626
+ const localizationFile = parseResJson({
627
+ filePath: localizedData,
628
+ content
629
+ });
630
+ return convertLocalizationFileToLocData(localizationFile);
631
+ }
632
+ else {
633
+ return localizedData instanceof Map ? localizedData : new Map(Object.entries(localizedData));
634
+ }
635
+ }
636
+ //# sourceMappingURL=LocalizationPlugin.js.map