@rushstack/webpack4-localization-plugin 0.19.14 → 0.20.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.
Files changed (76) hide show
  1. package/CHANGELOG.json +26 -0
  2. package/CHANGELOG.md +8 -1
  3. package/dist/tsdoc-metadata.json +1 -1
  4. package/lib-esm/AssetProcessor.js +277 -0
  5. package/lib-esm/AssetProcessor.js.map +1 -0
  6. package/lib-esm/LocalizationPlugin.js +580 -0
  7. package/lib-esm/LocalizationPlugin.js.map +1 -0
  8. package/lib-esm/WebpackConfigurationUpdater.js +106 -0
  9. package/lib-esm/WebpackConfigurationUpdater.js.map +1 -0
  10. package/lib-esm/index.js +4 -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/InPlaceLocFileLoader.js +20 -0
  15. package/lib-esm/loaders/InPlaceLocFileLoader.js.map +1 -0
  16. package/lib-esm/loaders/LoaderFactory.js +14 -0
  17. package/lib-esm/loaders/LoaderFactory.js.map +1 -0
  18. package/lib-esm/loaders/LocLoader.js +38 -0
  19. package/lib-esm/loaders/LocLoader.js.map +1 -0
  20. package/lib-esm/utilities/Constants.js +17 -0
  21. package/lib-esm/utilities/Constants.js.map +1 -0
  22. package/lib-esm/utilities/EntityMarker.js +15 -0
  23. package/lib-esm/utilities/EntityMarker.js.map +1 -0
  24. package/lib-esm/utilities/LoaderTerminalProvider.js +24 -0
  25. package/lib-esm/utilities/LoaderTerminalProvider.js.map +1 -0
  26. package/lib-esm/webpackInterfaces.js +4 -0
  27. package/lib-esm/webpackInterfaces.js.map +1 -0
  28. package/package.json +29 -7
  29. /package/{lib → lib-commonjs}/AssetProcessor.js +0 -0
  30. /package/{lib → lib-commonjs}/AssetProcessor.js.map +0 -0
  31. /package/{lib → lib-commonjs}/LocalizationPlugin.js +0 -0
  32. /package/{lib → lib-commonjs}/LocalizationPlugin.js.map +0 -0
  33. /package/{lib → lib-commonjs}/WebpackConfigurationUpdater.js +0 -0
  34. /package/{lib → lib-commonjs}/WebpackConfigurationUpdater.js.map +0 -0
  35. /package/{lib → lib-commonjs}/index.js +0 -0
  36. /package/{lib → lib-commonjs}/index.js.map +0 -0
  37. /package/{lib → lib-commonjs}/interfaces.js +0 -0
  38. /package/{lib → lib-commonjs}/interfaces.js.map +0 -0
  39. /package/{lib → lib-commonjs}/loaders/InPlaceLocFileLoader.js +0 -0
  40. /package/{lib → lib-commonjs}/loaders/InPlaceLocFileLoader.js.map +0 -0
  41. /package/{lib → lib-commonjs}/loaders/LoaderFactory.js +0 -0
  42. /package/{lib → lib-commonjs}/loaders/LoaderFactory.js.map +0 -0
  43. /package/{lib → lib-commonjs}/loaders/LocLoader.js +0 -0
  44. /package/{lib → lib-commonjs}/loaders/LocLoader.js.map +0 -0
  45. /package/{lib → lib-commonjs}/utilities/Constants.js +0 -0
  46. /package/{lib → lib-commonjs}/utilities/Constants.js.map +0 -0
  47. /package/{lib → lib-commonjs}/utilities/EntityMarker.js +0 -0
  48. /package/{lib → lib-commonjs}/utilities/EntityMarker.js.map +0 -0
  49. /package/{lib → lib-commonjs}/utilities/LoaderTerminalProvider.js +0 -0
  50. /package/{lib → lib-commonjs}/utilities/LoaderTerminalProvider.js.map +0 -0
  51. /package/{lib → lib-commonjs}/webpackInterfaces.js +0 -0
  52. /package/{lib → lib-commonjs}/webpackInterfaces.js.map +0 -0
  53. /package/{lib → lib-dts}/AssetProcessor.d.ts +0 -0
  54. /package/{lib → lib-dts}/AssetProcessor.d.ts.map +0 -0
  55. /package/{lib → lib-dts}/LocalizationPlugin.d.ts +0 -0
  56. /package/{lib → lib-dts}/LocalizationPlugin.d.ts.map +0 -0
  57. /package/{lib → lib-dts}/WebpackConfigurationUpdater.d.ts +0 -0
  58. /package/{lib → lib-dts}/WebpackConfigurationUpdater.d.ts.map +0 -0
  59. /package/{lib → lib-dts}/index.d.ts +0 -0
  60. /package/{lib → lib-dts}/index.d.ts.map +0 -0
  61. /package/{lib → lib-dts}/interfaces.d.ts +0 -0
  62. /package/{lib → lib-dts}/interfaces.d.ts.map +0 -0
  63. /package/{lib → lib-dts}/loaders/InPlaceLocFileLoader.d.ts +0 -0
  64. /package/{lib → lib-dts}/loaders/InPlaceLocFileLoader.d.ts.map +0 -0
  65. /package/{lib → lib-dts}/loaders/LoaderFactory.d.ts +0 -0
  66. /package/{lib → lib-dts}/loaders/LoaderFactory.d.ts.map +0 -0
  67. /package/{lib → lib-dts}/loaders/LocLoader.d.ts +0 -0
  68. /package/{lib → lib-dts}/loaders/LocLoader.d.ts.map +0 -0
  69. /package/{lib → lib-dts}/utilities/Constants.d.ts +0 -0
  70. /package/{lib → lib-dts}/utilities/Constants.d.ts.map +0 -0
  71. /package/{lib → lib-dts}/utilities/EntityMarker.d.ts +0 -0
  72. /package/{lib → lib-dts}/utilities/EntityMarker.d.ts.map +0 -0
  73. /package/{lib → lib-dts}/utilities/LoaderTerminalProvider.d.ts +0 -0
  74. /package/{lib → lib-dts}/utilities/LoaderTerminalProvider.d.ts.map +0 -0
  75. /package/{lib → lib-dts}/webpackInterfaces.d.ts +0 -0
  76. /package/{lib → lib-dts}/webpackInterfaces.d.ts.map +0 -0
@@ -0,0 +1,580 @@
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 * as Webpack from 'webpack';
5
+ import { JsonFile, FileSystem, NewlineKind } from '@rushstack/node-core-library';
6
+ import { getPseudolocalizer, parseLocFile, TypingsGenerator } from '@rushstack/localization-utilities';
7
+ import { Constants } from './utilities/Constants';
8
+ import { WebpackConfigurationUpdater } from './WebpackConfigurationUpdater';
9
+ import { EntityMarker } from './utilities/EntityMarker';
10
+ import { AssetProcessor, PLACEHOLDER_REGEX } from './AssetProcessor';
11
+ const PLUGIN_NAME = 'localization';
12
+ /**
13
+ * This plugin facilitates localization in webpack.
14
+ *
15
+ * @public
16
+ */
17
+ export class LocalizationPlugin {
18
+ constructor(options) {
19
+ var _a;
20
+ /**
21
+ * @internal
22
+ */
23
+ this.stringKeys = new Map();
24
+ this._stringPlaceholderCounter = 0;
25
+ this._stringPlaceholderMap = new Map();
26
+ this._locales = new Set();
27
+ this._pseudolocalizers = new Map();
28
+ /**
29
+ * The outermost map's keys are the locale names.
30
+ * The middle map's keys are the resolved, file names.
31
+ * The innermost map's keys are the string identifiers and its values are the string values.
32
+ */
33
+ this._resolvedLocalizedStrings = new Map();
34
+ if (options.filesToIgnore) {
35
+ throw new Error('The filesToIgnore option is no longer supported. Please use globsToIgnore instead.');
36
+ }
37
+ if ((_a = options.typingsOptions) === null || _a === void 0 ? void 0 : _a.ignoreString) {
38
+ throw new Error('The typingsOptions.ignoreString option is no longer supported. Please use the ignoreString ' +
39
+ 'option directly on the constructor options object instead.');
40
+ }
41
+ this._options = options;
42
+ }
43
+ apply(compiler) {
44
+ const isWebpack4 = !!compiler.hooks;
45
+ if (!isWebpack4) {
46
+ throw new Error(`The ${LocalizationPlugin.name} plugin requires Webpack 4`);
47
+ }
48
+ if (this._options.typingsOptions && compiler.context) {
49
+ if (this._options.typingsOptions.generatedTsFolder &&
50
+ !path.isAbsolute(this._options.typingsOptions.generatedTsFolder)) {
51
+ this._options.typingsOptions.generatedTsFolder = path.resolve(compiler.context, this._options.typingsOptions.generatedTsFolder);
52
+ }
53
+ if (this._options.typingsOptions.sourceRoot &&
54
+ !path.isAbsolute(this._options.typingsOptions.sourceRoot)) {
55
+ this._options.typingsOptions.sourceRoot = path.resolve(compiler.context, this._options.typingsOptions.sourceRoot);
56
+ }
57
+ const secondaryGeneratedTsFolders = this._options.typingsOptions.secondaryGeneratedTsFolders;
58
+ if (secondaryGeneratedTsFolders) {
59
+ for (let i = 0; i < secondaryGeneratedTsFolders.length; i++) {
60
+ const secondaryGeneratedTsFolder = secondaryGeneratedTsFolders[i];
61
+ if (!path.isAbsolute(secondaryGeneratedTsFolder)) {
62
+ secondaryGeneratedTsFolders[i] = path.resolve(compiler.context, secondaryGeneratedTsFolder);
63
+ }
64
+ }
65
+ }
66
+ }
67
+ // https://github.com/webpack/webpack-dev-server/pull/1929/files#diff-15fb51940da53816af13330d8ce69b4eR66
68
+ const isWebpackDevServer = process.env.WEBPACK_DEV_SERVER === 'true';
69
+ const { errors, warnings } = this._initializeAndValidateOptions(compiler.options, isWebpackDevServer);
70
+ let typingsPreprocessor;
71
+ if (this._options.typingsOptions) {
72
+ typingsPreprocessor = new TypingsGenerator({
73
+ srcFolder: this._options.typingsOptions.sourceRoot || compiler.context,
74
+ generatedTsFolder: this._options.typingsOptions.generatedTsFolder,
75
+ secondaryGeneratedTsFolders: this._options.typingsOptions.secondaryGeneratedTsFolders,
76
+ exportAsDefault: this._options.typingsOptions.exportAsDefault,
77
+ globsToIgnore: this._options.globsToIgnore,
78
+ ignoreString: this._options.ignoreString,
79
+ processComment: this._options.typingsOptions.processComment
80
+ });
81
+ }
82
+ else {
83
+ typingsPreprocessor = undefined;
84
+ }
85
+ const webpackConfigurationUpdaterOptions = {
86
+ pluginInstance: this,
87
+ configuration: compiler.options,
88
+ globsToIgnore: this._globsToIgnore,
89
+ localeNameOrPlaceholder: Constants.LOCALE_NAME_PLACEHOLDER,
90
+ resxNewlineNormalization: this._resxNewlineNormalization,
91
+ ignoreMissingResxComments: this._ignoreMissingResxComments,
92
+ ignoreString: this._options.ignoreString
93
+ };
94
+ if (errors.length > 0 || warnings.length > 0) {
95
+ compiler.hooks.compilation.tap(PLUGIN_NAME, (compilation) => {
96
+ compilation.errors.push(...errors);
97
+ compilation.warnings.push(...warnings);
98
+ });
99
+ if (errors.length > 0) {
100
+ // If there are any errors, just pass through the resources in source and don't do any
101
+ // additional configuration
102
+ WebpackConfigurationUpdater.amendWebpackConfigurationForInPlaceLocFiles(webpackConfigurationUpdaterOptions);
103
+ return;
104
+ }
105
+ }
106
+ if (isWebpackDevServer) {
107
+ if (typingsPreprocessor) {
108
+ compiler.hooks.afterEnvironment.tap(PLUGIN_NAME, () => typingsPreprocessor.runWatcherAsync());
109
+ if (!compiler.options.plugins) {
110
+ compiler.options.plugins = [];
111
+ }
112
+ compiler.options.plugins.push(new Webpack.WatchIgnorePlugin([this._options.typingsOptions.generatedTsFolder]));
113
+ }
114
+ WebpackConfigurationUpdater.amendWebpackConfigurationForInPlaceLocFiles(webpackConfigurationUpdaterOptions);
115
+ }
116
+ else {
117
+ if (typingsPreprocessor) {
118
+ compiler.hooks.beforeRun.tapPromise(PLUGIN_NAME, async () => await typingsPreprocessor.generateTypingsAsync());
119
+ }
120
+ WebpackConfigurationUpdater.amendWebpackConfigurationForMultiLocale(webpackConfigurationUpdaterOptions);
121
+ if (errors.length === 0) {
122
+ compiler.hooks.thisCompilation.tap(PLUGIN_NAME, (untypedCompilation) => {
123
+ const compilation = untypedCompilation;
124
+ compilation.mainTemplate.hooks.assetPath.tap(PLUGIN_NAME, (assetPath, options) => {
125
+ if (options.contentHashType === 'javascript' &&
126
+ assetPath.match(Constants.LOCALE_FILENAME_TOKEN_REGEX)) {
127
+ // Does this look like an async chunk URL generator?
128
+ if (typeof options.chunk.id === 'string' && options.chunk.id.match(/^\" \+/)) {
129
+ return assetPath.replace(Constants.LOCALE_FILENAME_TOKEN_REGEX, `" + ${Constants.JSONP_PLACEHOLDER} + "`);
130
+ }
131
+ else {
132
+ return assetPath.replace(Constants.LOCALE_FILENAME_TOKEN_REGEX, Constants.LOCALE_NAME_PLACEHOLDER);
133
+ }
134
+ }
135
+ else if (assetPath.match(Constants.NO_LOCALE_SOURCE_MAP_FILENAME_TOKEN_REGEX)) {
136
+ // Replace the placeholder with the [locale] token for sourcemaps
137
+ const deLocalizedFilename = options.filename.replace(PLACEHOLDER_REGEX, Constants.LOCALE_FILENAME_TOKEN);
138
+ return assetPath.replace(Constants.NO_LOCALE_SOURCE_MAP_FILENAME_TOKEN_REGEX, deLocalizedFilename);
139
+ }
140
+ else {
141
+ return assetPath;
142
+ }
143
+ });
144
+ compilation.hooks.optimizeChunks.tap(PLUGIN_NAME, (untypedChunks, untypedChunkGroups) => {
145
+ const chunks = untypedChunks;
146
+ const chunkGroups = untypedChunkGroups;
147
+ let chunksHaveAnyChildren = false;
148
+ for (const chunkGroup of chunkGroups) {
149
+ const children = chunkGroup.getChildren();
150
+ if (children.length > 0) {
151
+ chunksHaveAnyChildren = true;
152
+ break;
153
+ }
154
+ }
155
+ if (chunksHaveAnyChildren &&
156
+ (!compilation.options.output ||
157
+ !compilation.options.output.chunkFilename ||
158
+ compilation.options.output.chunkFilename.indexOf(Constants.LOCALE_FILENAME_TOKEN) === -1)) {
159
+ compilation.errors.push(new Error('The configuration.output.chunkFilename property must be provided and must include ' +
160
+ `the ${Constants.LOCALE_FILENAME_TOKEN} placeholder`));
161
+ return;
162
+ }
163
+ for (const chunk of chunks) {
164
+ // See if the chunk contains any localized modules or loads any localized chunks
165
+ const localizedChunk = this._chunkHasLocalizedModules(chunk);
166
+ // Change the chunk's name to include either the locale name or the locale name for chunks without strings
167
+ const replacementValue = localizedChunk
168
+ ? Constants.LOCALE_NAME_PLACEHOLDER
169
+ : this._noStringsLocaleName;
170
+ if (chunk.hasRuntime()) {
171
+ chunk.filenameTemplate = compilation.options.output.filename.replace(Constants.LOCALE_FILENAME_TOKEN_REGEX, replacementValue);
172
+ }
173
+ else {
174
+ chunk.filenameTemplate = compilation.options.output.chunkFilename.replace(Constants.LOCALE_FILENAME_TOKEN_REGEX, replacementValue);
175
+ }
176
+ }
177
+ });
178
+ });
179
+ compiler.hooks.emit.tap(PLUGIN_NAME, (compilation) => {
180
+ const localizationStats = {
181
+ entrypoints: {},
182
+ namedChunkGroups: {}
183
+ };
184
+ const alreadyProcessedAssets = new Set();
185
+ const hotUpdateRegex = /\.hot-update\.js$/;
186
+ for (const untypedChunk of compilation.chunks) {
187
+ const chunk = untypedChunk;
188
+ const chunkFilesSet = new Set(chunk.files);
189
+ function processChunkJsFile(callback) {
190
+ let alreadyProcessedAFileInThisChunk = false;
191
+ for (const chunkFilename of chunk.files) {
192
+ if (chunkFilename.endsWith('.js') && // Ensure this is a JS file
193
+ !hotUpdateRegex.test(chunkFilename) && // Ensure this is not a webpack hot update
194
+ !alreadyProcessedAssets.has(chunkFilename) // Ensure this isn't a vendor chunk we've already processed
195
+ ) {
196
+ if (alreadyProcessedAFileInThisChunk) {
197
+ throw new Error(`Found more than one JS file in chunk "${chunk.name}". This is not expected.`);
198
+ }
199
+ alreadyProcessedAFileInThisChunk = true;
200
+ alreadyProcessedAssets.add(chunkFilename);
201
+ callback(chunkFilename);
202
+ }
203
+ }
204
+ }
205
+ if (this._chunkHasLocalizedModules(chunk)) {
206
+ processChunkJsFile((chunkFilename) => {
207
+ if (chunkFilename.indexOf(Constants.LOCALE_NAME_PLACEHOLDER) === -1) {
208
+ throw new Error(`Asset ${chunkFilename} is expected to be localized, but is missing a locale placeholder`);
209
+ }
210
+ const asset = compilation.assets[chunkFilename];
211
+ const resultingAssets = AssetProcessor.processLocalizedAsset({
212
+ plugin: this,
213
+ compilation,
214
+ assetName: chunkFilename,
215
+ asset,
216
+ chunk,
217
+ chunkHasLocalizedModules: this._chunkHasLocalizedModules.bind(this),
218
+ locales: this._locales,
219
+ noStringsLocaleName: this._noStringsLocaleName,
220
+ fillMissingTranslationStrings: this._fillMissingTranslationStrings,
221
+ defaultLocale: this._defaultLocale
222
+ });
223
+ // Delete the existing asset because it's been renamed
224
+ delete compilation.assets[chunkFilename];
225
+ chunkFilesSet.delete(chunkFilename);
226
+ const localizedChunkAssets = {};
227
+ for (const [locale, newAsset] of resultingAssets) {
228
+ compilation.assets[newAsset.filename] = newAsset.asset;
229
+ localizedChunkAssets[locale] = newAsset.filename;
230
+ chunkFilesSet.add(newAsset.filename);
231
+ }
232
+ if (chunk.hasRuntime()) {
233
+ // This is an entrypoint
234
+ localizationStats.entrypoints[chunk.name] = {
235
+ localizedAssets: localizedChunkAssets
236
+ };
237
+ }
238
+ else {
239
+ // This is a secondary chunk
240
+ if (chunk.name) {
241
+ localizationStats.namedChunkGroups[chunk.name] = {
242
+ localizedAssets: localizedChunkAssets
243
+ };
244
+ }
245
+ }
246
+ chunk.localizedFiles = localizedChunkAssets;
247
+ });
248
+ }
249
+ else {
250
+ processChunkJsFile((chunkFilename) => {
251
+ const asset = compilation.assets[chunkFilename];
252
+ const resultingAsset = AssetProcessor.processNonLocalizedAsset({
253
+ plugin: this,
254
+ compilation,
255
+ assetName: chunkFilename,
256
+ asset,
257
+ chunk,
258
+ noStringsLocaleName: this._noStringsLocaleName,
259
+ chunkHasLocalizedModules: this._chunkHasLocalizedModules.bind(this)
260
+ });
261
+ // Delete the existing asset because it's been renamed
262
+ delete compilation.assets[chunkFilename];
263
+ chunkFilesSet.delete(chunkFilename);
264
+ compilation.assets[resultingAsset.filename] = resultingAsset.asset;
265
+ chunkFilesSet.add(resultingAsset.filename);
266
+ });
267
+ }
268
+ chunk.files = Array.from(chunkFilesSet);
269
+ }
270
+ if (this._options.localizationStats) {
271
+ if (this._options.localizationStats.dropPath) {
272
+ const resolvedLocalizationStatsDropPath = path.resolve(compiler.outputPath, this._options.localizationStats.dropPath);
273
+ JsonFile.save(localizationStats, resolvedLocalizationStatsDropPath, {
274
+ ensureFolderExists: true
275
+ });
276
+ }
277
+ if (this._options.localizationStats.callback) {
278
+ try {
279
+ this._options.localizationStats.callback(localizationStats);
280
+ }
281
+ catch (e) {
282
+ /* swallow errors from the callback */
283
+ }
284
+ }
285
+ }
286
+ });
287
+ }
288
+ }
289
+ }
290
+ /**
291
+ * @internal
292
+ *
293
+ * @returns
294
+ */
295
+ addDefaultLocFile(terminal, localizedResourcePath, localizedResourceData) {
296
+ const additionalLoadedFilePaths = [];
297
+ const errors = [];
298
+ const locFileData = this._convertLocalizationFileToLocData(localizedResourceData);
299
+ this._addLocFile(this._defaultLocale, localizedResourcePath, locFileData);
300
+ const normalizeLocalizedData = (localizedData) => {
301
+ if (typeof localizedData === 'string') {
302
+ additionalLoadedFilePaths.push(localizedData);
303
+ const localizationFile = parseLocFile({
304
+ filePath: localizedData,
305
+ content: FileSystem.readFile(localizedData),
306
+ terminal: terminal,
307
+ resxNewlineNormalization: this._resxNewlineNormalization,
308
+ ignoreMissingResxComments: this._ignoreMissingResxComments
309
+ });
310
+ return this._convertLocalizationFileToLocData(localizationFile);
311
+ }
312
+ else {
313
+ return localizedData;
314
+ }
315
+ };
316
+ const missingLocales = [];
317
+ for (const [translatedLocaleName, translatedStrings] of Object.entries(this._resolvedTranslatedStringsFromOptions)) {
318
+ const translatedLocFileFromOptions = translatedStrings[localizedResourcePath];
319
+ if (!translatedLocFileFromOptions) {
320
+ missingLocales.push(translatedLocaleName);
321
+ }
322
+ else {
323
+ const translatedLocFileData = normalizeLocalizedData(translatedLocFileFromOptions);
324
+ this._addLocFile(translatedLocaleName, localizedResourcePath, translatedLocFileData);
325
+ }
326
+ }
327
+ if (missingLocales.length > 0 && this._options.localizedData.resolveMissingTranslatedStrings) {
328
+ let resolvedTranslatedData = undefined;
329
+ try {
330
+ resolvedTranslatedData = this._options.localizedData.resolveMissingTranslatedStrings(missingLocales, localizedResourcePath);
331
+ }
332
+ catch (e) {
333
+ errors.push(e);
334
+ }
335
+ if (resolvedTranslatedData) {
336
+ for (const [resolvedLocaleName, resolvedLocaleData] of Object.entries(resolvedTranslatedData)) {
337
+ if (resolvedLocaleData) {
338
+ const translatedLocFileData = normalizeLocalizedData(resolvedLocaleData);
339
+ this._addLocFile(resolvedLocaleName, localizedResourcePath, translatedLocFileData);
340
+ }
341
+ }
342
+ }
343
+ }
344
+ this._pseudolocalizers.forEach((pseudolocalizer, pseudolocaleName) => {
345
+ const pseudolocFileData = {};
346
+ for (const [stringName, stringValue] of Object.entries(locFileData)) {
347
+ pseudolocFileData[stringName] = pseudolocalizer(stringValue);
348
+ }
349
+ this._addLocFile(pseudolocaleName, localizedResourcePath, pseudolocFileData);
350
+ });
351
+ return { additionalLoadedFilePaths, errors };
352
+ }
353
+ /**
354
+ * @internal
355
+ */
356
+ getDataForSerialNumber(serialNumber) {
357
+ return this._stringPlaceholderMap.get(serialNumber);
358
+ }
359
+ _addLocFile(localeName, localizedFilePath, localizedFileData) {
360
+ const filesMap = this._resolvedLocalizedStrings.get(localeName);
361
+ const stringsMap = new Map();
362
+ filesMap.set(localizedFilePath, stringsMap);
363
+ for (const [stringName, stringValue] of Object.entries(localizedFileData)) {
364
+ const stringKey = `${localizedFilePath}?${stringName}`;
365
+ if (!this.stringKeys.has(stringKey)) {
366
+ const placeholder = this._getPlaceholderString();
367
+ this.stringKeys.set(stringKey, placeholder);
368
+ }
369
+ const placeholder = this.stringKeys.get(stringKey);
370
+ if (!this._stringPlaceholderMap.has(placeholder.suffix)) {
371
+ this._stringPlaceholderMap.set(placeholder.suffix, {
372
+ values: {
373
+ [this._passthroughLocaleName]: stringName
374
+ },
375
+ locFilePath: localizedFilePath,
376
+ stringName: stringName
377
+ });
378
+ }
379
+ this._stringPlaceholderMap.get(placeholder.suffix).values[localeName] = stringValue;
380
+ stringsMap.set(stringName, stringValue);
381
+ }
382
+ }
383
+ _initializeAndValidateOptions(configuration, isWebpackDevServer) {
384
+ const errors = [];
385
+ const warnings = [];
386
+ function ensureValidLocaleName(localeName) {
387
+ const LOCALE_NAME_REGEX = /[a-z-]/i;
388
+ if (!localeName.match(LOCALE_NAME_REGEX)) {
389
+ errors.push(new Error(`Invalid locale name: ${localeName}. Locale names may only contain letters and hyphens.`));
390
+ return false;
391
+ }
392
+ else {
393
+ return true;
394
+ }
395
+ }
396
+ // START configuration
397
+ if (!configuration.output ||
398
+ !configuration.output.filename ||
399
+ typeof configuration.output.filename !== 'string' ||
400
+ configuration.output.filename.indexOf(Constants.LOCALE_FILENAME_TOKEN) === -1) {
401
+ errors.push(new Error('The configuration.output.filename property must be provided, must be a string, and must include ' +
402
+ `the ${Constants.LOCALE_FILENAME_TOKEN} placeholder`));
403
+ }
404
+ // END configuration
405
+ // START misc options
406
+ // eslint-disable-next-line no-lone-blocks
407
+ {
408
+ this._globsToIgnore = this._options.globsToIgnore;
409
+ }
410
+ // END misc options
411
+ // START options.localizedData
412
+ if (this._options.localizedData) {
413
+ this._ignoreMissingResxComments = this._options.localizedData.ignoreMissingResxComments;
414
+ // START options.localizedData.passthroughLocale
415
+ if (this._options.localizedData.passthroughLocale) {
416
+ const { usePassthroughLocale, passthroughLocaleName = 'passthrough' } = this._options.localizedData.passthroughLocale;
417
+ if (usePassthroughLocale) {
418
+ this._passthroughLocaleName = passthroughLocaleName;
419
+ this._locales.add(passthroughLocaleName);
420
+ }
421
+ }
422
+ // END options.localizedData.passthroughLocale
423
+ // START options.localizedData.translatedStrings
424
+ const { translatedStrings } = this._options.localizedData;
425
+ this._resolvedTranslatedStringsFromOptions = {};
426
+ if (translatedStrings) {
427
+ for (const [localeName, locale] of Object.entries(translatedStrings)) {
428
+ if (this._locales.has(localeName)) {
429
+ errors.push(Error(`The locale "${localeName}" appears multiple times. ` +
430
+ 'There may be multiple instances with different casing.'));
431
+ return { errors, warnings };
432
+ }
433
+ if (!ensureValidLocaleName(localeName)) {
434
+ return { errors, warnings };
435
+ }
436
+ this._locales.add(localeName);
437
+ this._resolvedLocalizedStrings.set(localeName, new Map());
438
+ this._resolvedTranslatedStringsFromOptions[localeName] = {};
439
+ const locFilePathsInLocale = new Set();
440
+ for (const [locFilePath, locFileDataFromOptions] of Object.entries(locale)) {
441
+ if (locale.hasOwnProperty(locFilePath)) {
442
+ const normalizedLocFilePath = path.resolve(configuration.context, locFilePath);
443
+ if (locFilePathsInLocale.has(normalizedLocFilePath)) {
444
+ errors.push(new Error(`The localization file path "${locFilePath}" appears multiple times in locale ${localeName}. ` +
445
+ 'There may be multiple instances with different casing.'));
446
+ return { errors, warnings };
447
+ }
448
+ locFilePathsInLocale.add(normalizedLocFilePath);
449
+ const normalizedLocFileDataFromOptions = typeof locFileDataFromOptions === 'string'
450
+ ? path.resolve(configuration.context, locFileDataFromOptions)
451
+ : locFileDataFromOptions;
452
+ this._resolvedTranslatedStringsFromOptions[localeName][normalizedLocFilePath] =
453
+ normalizedLocFileDataFromOptions;
454
+ }
455
+ }
456
+ }
457
+ }
458
+ // END options.localizedData.translatedStrings
459
+ // START options.localizedData.defaultLocale
460
+ if (this._options.localizedData.defaultLocale) {
461
+ const { localeName, fillMissingTranslationStrings } = this._options.localizedData.defaultLocale;
462
+ if (this._options.localizedData.defaultLocale.localeName) {
463
+ if (this._locales.has(localeName)) {
464
+ errors.push(new Error('The default locale is also specified in the translated strings.'));
465
+ return { errors, warnings };
466
+ }
467
+ else if (!ensureValidLocaleName(localeName)) {
468
+ return { errors, warnings };
469
+ }
470
+ this._locales.add(localeName);
471
+ this._resolvedLocalizedStrings.set(localeName, new Map());
472
+ this._defaultLocale = localeName;
473
+ this._fillMissingTranslationStrings = !!fillMissingTranslationStrings;
474
+ }
475
+ else {
476
+ errors.push(new Error('Missing default locale name'));
477
+ return { errors, warnings };
478
+ }
479
+ }
480
+ else {
481
+ errors.push(new Error('Missing default locale options.'));
482
+ return { errors, warnings };
483
+ }
484
+ // END options.localizedData.defaultLocale
485
+ // START options.localizedData.pseudoLocales
486
+ if (this._options.localizedData.pseudolocales) {
487
+ for (const [pseudolocaleName, pseudoLocaleOpts] of Object.entries(this._options.localizedData.pseudolocales)) {
488
+ if (this._defaultLocale === pseudolocaleName) {
489
+ errors.push(new Error(`A pseudolocale (${pseudolocaleName}) name is also the default locale name.`));
490
+ return { errors, warnings };
491
+ }
492
+ if (this._locales.has(pseudolocaleName)) {
493
+ errors.push(new Error(`A pseudolocale (${pseudolocaleName}) name is also specified in the translated strings.`));
494
+ return { errors, warnings };
495
+ }
496
+ this._pseudolocalizers.set(pseudolocaleName, getPseudolocalizer(pseudoLocaleOpts));
497
+ this._locales.add(pseudolocaleName);
498
+ this._resolvedLocalizedStrings.set(pseudolocaleName, new Map());
499
+ }
500
+ }
501
+ // END options.localizedData.pseudoLocales
502
+ // START options.localizedData.normalizeResxNewlines
503
+ if (this._options.localizedData.normalizeResxNewlines) {
504
+ switch (this._options.localizedData.normalizeResxNewlines) {
505
+ case 'crlf': {
506
+ this._resxNewlineNormalization = NewlineKind.CrLf;
507
+ break;
508
+ }
509
+ case 'lf': {
510
+ this._resxNewlineNormalization = NewlineKind.Lf;
511
+ break;
512
+ }
513
+ default: {
514
+ errors.push(new Error(`Unexpected value "${this._options.localizedData.normalizeResxNewlines}" for option ` +
515
+ '"localizedData.normalizeResxNewlines"'));
516
+ break;
517
+ }
518
+ }
519
+ }
520
+ // END options.localizedData.normalizeResxNewlines
521
+ }
522
+ else if (!isWebpackDevServer) {
523
+ throw new Error('Localized data must be provided unless webpack dev server is running.');
524
+ }
525
+ // END options.localizedData
526
+ // START options.noStringsLocaleName
527
+ if (this._options.noStringsLocaleName === undefined ||
528
+ this._options.noStringsLocaleName === null ||
529
+ !ensureValidLocaleName(this._options.noStringsLocaleName)) {
530
+ this._noStringsLocaleName = 'none';
531
+ }
532
+ else {
533
+ this._noStringsLocaleName = this._options.noStringsLocaleName;
534
+ }
535
+ // END options.noStringsLocaleName
536
+ return { errors, warnings };
537
+ }
538
+ _getPlaceholderString() {
539
+ const suffix = (this._stringPlaceholderCounter++).toString();
540
+ return {
541
+ value: `${Constants.STRING_PLACEHOLDER_PREFIX}_\\_${Constants.STRING_PLACEHOLDER_LABEL}_${suffix}`,
542
+ suffix: suffix
543
+ };
544
+ }
545
+ _chunkHasLocalizedModules(chunk) {
546
+ let chunkHasAnyLocModules = EntityMarker.getMark(chunk);
547
+ if (chunkHasAnyLocModules === undefined) {
548
+ chunkHasAnyLocModules = false;
549
+ for (const module of chunk.getModules()) {
550
+ if (EntityMarker.getMark(module)) {
551
+ chunkHasAnyLocModules = true;
552
+ break;
553
+ }
554
+ }
555
+ // If this chunk doesn't directly contain any localized resources, it still
556
+ // needs to be localized if it's an entrypoint chunk (i.e. - it has a runtime)
557
+ // and it loads localized async chunks.
558
+ // In that case, the generated chunk URL generation code needs to contain
559
+ // the locale name.
560
+ if (!chunkHasAnyLocModules && chunk.hasRuntime()) {
561
+ for (const asyncChunk of chunk.getAllAsyncChunks()) {
562
+ if (this._chunkHasLocalizedModules(asyncChunk)) {
563
+ chunkHasAnyLocModules = true;
564
+ break;
565
+ }
566
+ }
567
+ }
568
+ EntityMarker.markEntity(chunk, chunkHasAnyLocModules);
569
+ }
570
+ return chunkHasAnyLocModules;
571
+ }
572
+ _convertLocalizationFileToLocData(locFile) {
573
+ const locFileData = {};
574
+ for (const [stringName, locFileEntry] of Object.entries(locFile)) {
575
+ locFileData[stringName] = locFileEntry.value;
576
+ }
577
+ return locFileData;
578
+ }
579
+ }
580
+ //# sourceMappingURL=LocalizationPlugin.js.map