@rushstack/webpack5-localization-plugin 0.1.0

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