@rushstack/localization-utilities 0.14.14 → 0.15.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 (54) 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/LocFileParser.js +59 -0
  5. package/lib-esm/LocFileParser.js.map +1 -0
  6. package/lib-esm/Pseudolocalization.js +26 -0
  7. package/lib-esm/Pseudolocalization.js.map +1 -0
  8. package/lib-esm/TypingsGenerator.js +95 -0
  9. package/lib-esm/TypingsGenerator.js.map +1 -0
  10. package/lib-esm/index.js +9 -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/parsers/parseLocJson.js +28 -0
  15. package/lib-esm/parsers/parseLocJson.js.map +1 -0
  16. package/lib-esm/parsers/parseResJson.js +38 -0
  17. package/lib-esm/parsers/parseResJson.js.map +1 -0
  18. package/lib-esm/parsers/parseResx.js +194 -0
  19. package/lib-esm/parsers/parseResx.js.map +1 -0
  20. package/lib-esm/schemas/locJson.schema.json +29 -0
  21. package/package.json +32 -7
  22. /package/{lib → lib-commonjs}/LocFileParser.js +0 -0
  23. /package/{lib → lib-commonjs}/LocFileParser.js.map +0 -0
  24. /package/{lib → lib-commonjs}/Pseudolocalization.js +0 -0
  25. /package/{lib → lib-commonjs}/Pseudolocalization.js.map +0 -0
  26. /package/{lib → lib-commonjs}/TypingsGenerator.js +0 -0
  27. /package/{lib → lib-commonjs}/TypingsGenerator.js.map +0 -0
  28. /package/{lib → lib-commonjs}/index.js +0 -0
  29. /package/{lib → lib-commonjs}/index.js.map +0 -0
  30. /package/{lib → lib-commonjs}/interfaces.js +0 -0
  31. /package/{lib → lib-commonjs}/interfaces.js.map +0 -0
  32. /package/{lib → lib-commonjs}/parsers/parseLocJson.js +0 -0
  33. /package/{lib → lib-commonjs}/parsers/parseLocJson.js.map +0 -0
  34. /package/{lib → lib-commonjs}/parsers/parseResJson.js +0 -0
  35. /package/{lib → lib-commonjs}/parsers/parseResJson.js.map +0 -0
  36. /package/{lib → lib-commonjs}/parsers/parseResx.js +0 -0
  37. /package/{lib → lib-commonjs}/parsers/parseResx.js.map +0 -0
  38. /package/{lib → lib-commonjs}/schemas/locJson.schema.json +0 -0
  39. /package/{lib → lib-dts}/LocFileParser.d.ts +0 -0
  40. /package/{lib → lib-dts}/LocFileParser.d.ts.map +0 -0
  41. /package/{lib → lib-dts}/Pseudolocalization.d.ts +0 -0
  42. /package/{lib → lib-dts}/Pseudolocalization.d.ts.map +0 -0
  43. /package/{lib → lib-dts}/TypingsGenerator.d.ts +0 -0
  44. /package/{lib → lib-dts}/TypingsGenerator.d.ts.map +0 -0
  45. /package/{lib → lib-dts}/index.d.ts +0 -0
  46. /package/{lib → lib-dts}/index.d.ts.map +0 -0
  47. /package/{lib → lib-dts}/interfaces.d.ts +0 -0
  48. /package/{lib → lib-dts}/interfaces.d.ts.map +0 -0
  49. /package/{lib → lib-dts}/parsers/parseLocJson.d.ts +0 -0
  50. /package/{lib → lib-dts}/parsers/parseLocJson.d.ts.map +0 -0
  51. /package/{lib → lib-dts}/parsers/parseResJson.d.ts +0 -0
  52. /package/{lib → lib-dts}/parsers/parseResJson.d.ts.map +0 -0
  53. /package/{lib → lib-dts}/parsers/parseResx.d.ts +0 -0
  54. /package/{lib → lib-dts}/parsers/parseResx.d.ts.map +0 -0
package/CHANGELOG.json CHANGED
@@ -1,6 +1,58 @@
1
1
  {
2
2
  "name": "@rushstack/localization-utilities",
3
3
  "entries": [
4
+ {
5
+ "version": "0.15.1",
6
+ "tag": "@rushstack/localization-utilities_v0.15.1",
7
+ "date": "Fri, 20 Feb 2026 00:15:04 GMT",
8
+ "comments": {
9
+ "patch": [
10
+ {
11
+ "comment": "Add `\"node\"` condition before `\"import\"` in the `\"exports\"` map so that Node.js uses the CJS output (which handles extensionless imports), while bundlers still use ESM via `\"import\"`. Fixes https://github.com/microsoft/rushstack/issues/5644."
12
+ }
13
+ ],
14
+ "dependency": [
15
+ {
16
+ "comment": "Updating dependency \"@rushstack/node-core-library\" to `5.20.1`"
17
+ },
18
+ {
19
+ "comment": "Updating dependency \"@rushstack/terminal\" to `0.22.1`"
20
+ },
21
+ {
22
+ "comment": "Updating dependency \"@rushstack/typings-generator\" to `0.16.1`"
23
+ },
24
+ {
25
+ "comment": "Updating dependency \"@rushstack/heft\" to `1.2.1`"
26
+ }
27
+ ]
28
+ }
29
+ },
30
+ {
31
+ "version": "0.15.0",
32
+ "tag": "@rushstack/localization-utilities_v0.15.0",
33
+ "date": "Thu, 19 Feb 2026 00:04:53 GMT",
34
+ "comments": {
35
+ "minor": [
36
+ {
37
+ "comment": "Normalize package layout. CommonJS is now under `lib-commonjs`, DTS is now under `lib-dts`, and ESM is now under `lib-esm`. Imports to `lib` still work as before, handled by the `\"exports\"` field in `package.json`."
38
+ }
39
+ ],
40
+ "dependency": [
41
+ {
42
+ "comment": "Updating dependency \"@rushstack/node-core-library\" to `5.20.0`"
43
+ },
44
+ {
45
+ "comment": "Updating dependency \"@rushstack/terminal\" to `0.22.0`"
46
+ },
47
+ {
48
+ "comment": "Updating dependency \"@rushstack/typings-generator\" to `0.16.0`"
49
+ },
50
+ {
51
+ "comment": "Updating dependency \"@rushstack/heft\" to `1.2.0`"
52
+ }
53
+ ]
54
+ }
55
+ },
4
56
  {
5
57
  "version": "0.14.14",
6
58
  "tag": "@rushstack/localization-utilities_v0.14.14",
package/CHANGELOG.md CHANGED
@@ -1,6 +1,20 @@
1
1
  # Change Log - @rushstack/localization-utilities
2
2
 
3
- This log was last generated on Sat, 07 Feb 2026 01:13:26 GMT and should not be manually modified.
3
+ This log was last generated on Fri, 20 Feb 2026 00:15:04 GMT and should not be manually modified.
4
+
5
+ ## 0.15.1
6
+ Fri, 20 Feb 2026 00:15:04 GMT
7
+
8
+ ### Patches
9
+
10
+ - Add `"node"` condition before `"import"` in the `"exports"` map so that Node.js uses the CJS output (which handles extensionless imports), while bundlers still use ESM via `"import"`. Fixes https://github.com/microsoft/rushstack/issues/5644.
11
+
12
+ ## 0.15.0
13
+ Thu, 19 Feb 2026 00:04:53 GMT
14
+
15
+ ### Minor changes
16
+
17
+ - Normalize package layout. CommonJS is now under `lib-commonjs`, DTS is now under `lib-dts`, and ESM is now under `lib-esm`. Imports to `lib` still work as before, handled by the `"exports"` field in `package.json`.
4
18
 
5
19
  ## 0.14.14
6
20
  Sat, 07 Feb 2026 01:13:26 GMT
@@ -5,7 +5,7 @@
5
5
  "toolPackages": [
6
6
  {
7
7
  "packageName": "@microsoft/api-extractor",
8
- "packageVersion": "7.56.2"
8
+ "packageVersion": "7.57.0"
9
9
  }
10
10
  ]
11
11
  }
@@ -0,0 +1,59 @@
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 { parseLocJson } from './parsers/parseLocJson';
4
+ import { parseResJson } from './parsers/parseResJson';
5
+ import { parseResx } from './parsers/parseResx';
6
+ const parseCache = new Map();
7
+ export function selectParserByFilePath(filePath) {
8
+ if (/\.resx$/i.test(filePath)) {
9
+ return 'resx';
10
+ }
11
+ else if (/\.(resx|loc)\.json$/i.test(filePath)) {
12
+ return 'loc.json';
13
+ }
14
+ else if (/\.resjson$/i.test(filePath)) {
15
+ return 'resjson';
16
+ }
17
+ else {
18
+ throw new Error(`Unsupported file extension in file: ${filePath}`);
19
+ }
20
+ }
21
+ /**
22
+ * @public
23
+ */
24
+ export function parseLocFile(options) {
25
+ const { parser = selectParserByFilePath(options.filePath) } = options;
26
+ const fileCacheKey = `${options.filePath}?${parser}&${options.resxNewlineNormalization || 'none'}`;
27
+ const parseCacheEntry = parseCache.get(fileCacheKey);
28
+ if (parseCacheEntry) {
29
+ if (parseCacheEntry.content === options.content &&
30
+ parseCacheEntry.ignoreString === options.ignoreString) {
31
+ return parseCacheEntry.parsedFile;
32
+ }
33
+ }
34
+ let parsedFile;
35
+ switch (parser) {
36
+ case 'resx': {
37
+ parsedFile = parseResx(options);
38
+ break;
39
+ }
40
+ case 'loc.json': {
41
+ parsedFile = parseLocJson(options);
42
+ break;
43
+ }
44
+ case 'resjson': {
45
+ parsedFile = parseResJson(options);
46
+ break;
47
+ }
48
+ default: {
49
+ throw new Error(`Unsupported parser: ${parser}`);
50
+ }
51
+ }
52
+ parseCache.set(fileCacheKey, {
53
+ content: options.content,
54
+ parsedFile,
55
+ ignoreString: options.ignoreString
56
+ });
57
+ return parsedFile;
58
+ }
59
+ //# sourceMappingURL=LocFileParser.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"LocFileParser.js","sourceRoot":"","sources":["../src/LocFileParser.ts"],"names":[],"mappings":"AAAA,4FAA4F;AAC5F,2DAA2D;AAG3D,OAAO,EAAE,YAAY,EAAE,MAAM,wBAAwB,CAAC;AACtD,OAAO,EAAE,YAAY,EAAE,MAAM,wBAAwB,CAAC;AACtD,OAAO,EAA8B,SAAS,EAAE,MAAM,qBAAqB,CAAC;AAoB5E,MAAM,UAAU,GAAkC,IAAI,GAAG,EAA4B,CAAC;AAEtF,MAAM,UAAU,sBAAsB,CAAC,QAAgB;IACrD,IAAI,UAAU,CAAC,IAAI,CAAC,QAAQ,CAAC,EAAE,CAAC;QAC9B,OAAO,MAAM,CAAC;IAChB,CAAC;SAAM,IAAI,sBAAsB,CAAC,IAAI,CAAC,QAAQ,CAAC,EAAE,CAAC;QACjD,OAAO,UAAU,CAAC;IACpB,CAAC;SAAM,IAAI,aAAa,CAAC,IAAI,CAAC,QAAQ,CAAC,EAAE,CAAC;QACxC,OAAO,SAAS,CAAC;IACnB,CAAC;SAAM,CAAC;QACN,MAAM,IAAI,KAAK,CAAC,uCAAuC,QAAQ,EAAE,CAAC,CAAC;IACrE,CAAC;AACH,CAAC;AAED;;GAEG;AACH,MAAM,UAAU,YAAY,CAAC,OAA6B;IACxD,MAAM,EAAE,MAAM,GAAG,sBAAsB,CAAC,OAAO,CAAC,QAAQ,CAAC,EAAE,GAAG,OAAO,CAAC;IAEtE,MAAM,YAAY,GAAW,GAAG,OAAO,CAAC,QAAQ,IAAI,MAAM,IAAI,OAAO,CAAC,wBAAwB,IAAI,MAAM,EAAE,CAAC;IAC3G,MAAM,eAAe,GAAiC,UAAU,CAAC,GAAG,CAAC,YAAY,CAAC,CAAC;IACnF,IAAI,eAAe,EAAE,CAAC;QACpB,IACE,eAAe,CAAC,OAAO,KAAK,OAAO,CAAC,OAAO;YAC3C,eAAe,CAAC,YAAY,KAAK,OAAO,CAAC,YAAY,EACrD,CAAC;YACD,OAAO,eAAe,CAAC,UAAU,CAAC;QACpC,CAAC;IACH,CAAC;IAED,IAAI,UAA6B,CAAC;IAClC,QAAQ,MAAM,EAAE,CAAC;QACf,KAAK,MAAM,CAAC,CAAC,CAAC;YACZ,UAAU,GAAG,SAAS,CAAC,OAAO,CAAC,CAAC;YAChC,MAAM;QACR,CAAC;QAED,KAAK,UAAU,CAAC,CAAC,CAAC;YAChB,UAAU,GAAG,YAAY,CAAC,OAAO,CAAC,CAAC;YACnC,MAAM;QACR,CAAC;QAED,KAAK,SAAS,CAAC,CAAC,CAAC;YACf,UAAU,GAAG,YAAY,CAAC,OAAO,CAAC,CAAC;YACnC,MAAM;QACR,CAAC;QAED,OAAO,CAAC,CAAC,CAAC;YACR,MAAM,IAAI,KAAK,CAAC,uBAAuB,MAAM,EAAE,CAAC,CAAC;QACnD,CAAC;IACH,CAAC;IAED,UAAU,CAAC,GAAG,CAAC,YAAY,EAAE;QAC3B,OAAO,EAAE,OAAO,CAAC,OAAO;QACxB,UAAU;QACV,YAAY,EAAE,OAAO,CAAC,YAAY;KACnC,CAAC,CAAC;IAEH,OAAO,UAAU,CAAC;AACpB,CAAC","sourcesContent":["// Copyright (c) Microsoft Corporation. All rights reserved. Licensed under the MIT license.\n// See LICENSE in the project root for license information.\n\nimport type { IgnoreStringFunction, ILocalizationFile, IParseFileOptions } from './interfaces';\nimport { parseLocJson } from './parsers/parseLocJson';\nimport { parseResJson } from './parsers/parseResJson';\nimport { type IParseResxOptionsBase, parseResx } from './parsers/parseResx';\n\n/**\n * @public\n */\nexport type ParserKind = 'resx' | 'loc.json' | 'resjson';\n\n/**\n * @public\n */\nexport interface IParseLocFileOptions extends IParseFileOptions, IParseResxOptionsBase {\n parser?: ParserKind;\n}\n\ninterface IParseCacheEntry {\n content: string;\n parsedFile: ILocalizationFile;\n ignoreString: IgnoreStringFunction | undefined;\n}\n\nconst parseCache: Map<string, IParseCacheEntry> = new Map<string, IParseCacheEntry>();\n\nexport function selectParserByFilePath(filePath: string): ParserKind {\n if (/\\.resx$/i.test(filePath)) {\n return 'resx';\n } else if (/\\.(resx|loc)\\.json$/i.test(filePath)) {\n return 'loc.json';\n } else if (/\\.resjson$/i.test(filePath)) {\n return 'resjson';\n } else {\n throw new Error(`Unsupported file extension in file: ${filePath}`);\n }\n}\n\n/**\n * @public\n */\nexport function parseLocFile(options: IParseLocFileOptions): ILocalizationFile {\n const { parser = selectParserByFilePath(options.filePath) } = options;\n\n const fileCacheKey: string = `${options.filePath}?${parser}&${options.resxNewlineNormalization || 'none'}`;\n const parseCacheEntry: IParseCacheEntry | undefined = parseCache.get(fileCacheKey);\n if (parseCacheEntry) {\n if (\n parseCacheEntry.content === options.content &&\n parseCacheEntry.ignoreString === options.ignoreString\n ) {\n return parseCacheEntry.parsedFile;\n }\n }\n\n let parsedFile: ILocalizationFile;\n switch (parser) {\n case 'resx': {\n parsedFile = parseResx(options);\n break;\n }\n\n case 'loc.json': {\n parsedFile = parseLocJson(options);\n break;\n }\n\n case 'resjson': {\n parsedFile = parseResJson(options);\n break;\n }\n\n default: {\n throw new Error(`Unsupported parser: ${parser}`);\n }\n }\n\n parseCache.set(fileCacheKey, {\n content: options.content,\n parsedFile,\n ignoreString: options.ignoreString\n });\n\n return parsedFile;\n}\n"]}
@@ -0,0 +1,26 @@
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 vm from 'node:vm';
4
+ import { FileSystem } from '@rushstack/node-core-library';
5
+ const pseudolocalePath = require.resolve('pseudolocale/pseudolocale.min.js');
6
+ /**
7
+ * Get a function that pseudolocalizes a string.
8
+ *
9
+ * @public
10
+ */
11
+ export function getPseudolocalizer(options) {
12
+ const pseudolocaleCode = FileSystem.readFile(pseudolocalePath);
13
+ const context = {
14
+ pseudolocale: undefined
15
+ };
16
+ // Load pseudolocale in an isolated context because the configuration for is stored on a singleton
17
+ vm.runInNewContext(pseudolocaleCode, context);
18
+ const { pseudolocale } = context;
19
+ if (!pseudolocale) {
20
+ throw new Error(`Failed to load pseudolocale module`);
21
+ }
22
+ Object.assign(pseudolocale.option, options);
23
+ // `pseudolocale.str` captures `pseudolocale` in its closure and refers to `pseudolocale.option`.
24
+ return pseudolocale.str;
25
+ }
26
+ //# sourceMappingURL=Pseudolocalization.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"Pseudolocalization.js","sourceRoot":"","sources":["../src/Pseudolocalization.ts"],"names":[],"mappings":"AAAA,4FAA4F;AAC5F,2DAA2D;AAE3D,OAAO,EAAE,MAAM,SAAS,CAAC;AAEzB,OAAO,EAAE,UAAU,EAAE,MAAM,8BAA8B,CAAC;AAI1D,MAAM,gBAAgB,GAAW,OAAO,CAAC,OAAO,CAAC,kCAAkC,CAAC,CAAC;AAOrF;;;;GAIG;AACH,MAAM,UAAU,kBAAkB,CAAC,OAA6B;IAC9D,MAAM,gBAAgB,GAAW,UAAU,CAAC,QAAQ,CAAC,gBAAgB,CAAC,CAAC;IACvE,MAAM,OAAO,GAET;QACF,YAAY,EAAE,SAAS;KACxB,CAAC;IAEF,kGAAkG;IAClG,EAAE,CAAC,eAAe,CAAC,gBAAgB,EAAE,OAAO,CAAC,CAAC;IAC9C,MAAM,EAAE,YAAY,EAAE,GAAG,OAAO,CAAC;IACjC,IAAI,CAAC,YAAY,EAAE,CAAC;QAClB,MAAM,IAAI,KAAK,CAAC,oCAAoC,CAAC,CAAC;IACxD,CAAC;IAED,MAAM,CAAC,MAAM,CAAC,YAAY,CAAC,MAAM,EAAE,OAAO,CAAC,CAAC;IAC5C,iGAAiG;IACjG,OAAO,YAAY,CAAC,GAAG,CAAC;AAC1B,CAAC","sourcesContent":["// Copyright (c) Microsoft Corporation. All rights reserved. Licensed under the MIT license.\n// See LICENSE in the project root for license information.\n\nimport vm from 'node:vm';\n\nimport { FileSystem } from '@rushstack/node-core-library';\n\nimport type { IPseudolocaleOptions } from './interfaces';\n\nconst pseudolocalePath: string = require.resolve('pseudolocale/pseudolocale.min.js');\n\ninterface IPseudolocale {\n option: IPseudolocaleOptions;\n str(str: string): string;\n}\n\n/**\n * Get a function that pseudolocalizes a string.\n *\n * @public\n */\nexport function getPseudolocalizer(options: IPseudolocaleOptions): (str: string) => string {\n const pseudolocaleCode: string = FileSystem.readFile(pseudolocalePath);\n const context: {\n pseudolocale: IPseudolocale | undefined;\n } = {\n pseudolocale: undefined\n };\n\n // Load pseudolocale in an isolated context because the configuration for is stored on a singleton\n vm.runInNewContext(pseudolocaleCode, context);\n const { pseudolocale } = context;\n if (!pseudolocale) {\n throw new Error(`Failed to load pseudolocale module`);\n }\n\n Object.assign(pseudolocale.option, options);\n // `pseudolocale.str` captures `pseudolocale` in its closure and refers to `pseudolocale.option`.\n return pseudolocale.str;\n}\n"]}
@@ -0,0 +1,95 @@
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 { StringValuesTypingsGenerator } from '@rushstack/typings-generator';
4
+ import { FileSystem } from '@rushstack/node-core-library';
5
+ import { parseLocFile } from './LocFileParser';
6
+ /**
7
+ * This is a simple tool that generates .d.ts files for .loc.json, .resx.json, .resjson, and .resx files.
8
+ *
9
+ * @public
10
+ */
11
+ export class TypingsGenerator extends StringValuesTypingsGenerator {
12
+ constructor(options) {
13
+ const { ignoreString, processComment, resxNewlineNormalization, ignoreMissingResxComments, trimmedJsonOutputFolders, exportAsDefault } = options;
14
+ const inferDefaultExportInterfaceNameFromFilename = typeof exportAsDefault === 'object'
15
+ ? exportAsDefault.inferInterfaceNameFromFilename
16
+ : undefined;
17
+ const getJsonPaths = trimmedJsonOutputFolders && trimmedJsonOutputFolders.length > 0
18
+ ? (relativePath) => {
19
+ const jsonRelativePath = relativePath.endsWith('.json') || relativePath.endsWith('.resjson')
20
+ ? relativePath
21
+ : `${relativePath}.json`;
22
+ const jsonPaths = [];
23
+ for (const outputFolder of trimmedJsonOutputFolders) {
24
+ jsonPaths.push(`${outputFolder}/${jsonRelativePath}`);
25
+ }
26
+ return jsonPaths;
27
+ }
28
+ : undefined;
29
+ super({
30
+ ...options,
31
+ fileExtensions: ['.resx', '.resx.json', '.loc.json', '.resjson'],
32
+ getAdditionalOutputFiles: getJsonPaths,
33
+ // eslint-disable-next-line @typescript-eslint/naming-convention
34
+ parseAndGenerateTypings: async (content, filePath, relativeFilePath) => {
35
+ const locFileData = parseLocFile({
36
+ filePath,
37
+ content,
38
+ terminal: this.terminal,
39
+ resxNewlineNormalization,
40
+ ignoreMissingResxComments,
41
+ ignoreString
42
+ });
43
+ const typings = [];
44
+ const json = trimmedJsonOutputFolders ? {} : undefined;
45
+ for (const [stringName, value] of Object.entries(locFileData)) {
46
+ let comment = value.comment;
47
+ if (processComment) {
48
+ comment = processComment(comment, relativeFilePath, stringName);
49
+ }
50
+ if (json) {
51
+ json[stringName] = value.value;
52
+ }
53
+ typings.push({
54
+ exportName: stringName,
55
+ comment
56
+ });
57
+ }
58
+ if (getJsonPaths) {
59
+ const jsonBuffer = Buffer.from(JSON.stringify(json), 'utf8');
60
+ for (const jsonFile of getJsonPaths(relativeFilePath)) {
61
+ await FileSystem.writeFileAsync(jsonFile, jsonBuffer, {
62
+ ensureFolderExists: true
63
+ });
64
+ }
65
+ }
66
+ if (inferDefaultExportInterfaceNameFromFilename) {
67
+ const lastSlashIndex = Math.max(filePath.lastIndexOf('/'), filePath.lastIndexOf('\\'));
68
+ let extensionIndex = filePath.lastIndexOf('.');
69
+ if (filePath.slice(extensionIndex).toLowerCase() === '.json') {
70
+ extensionIndex = filePath.lastIndexOf('.', extensionIndex - 1);
71
+ }
72
+ const fileNameWithoutExtension = filePath.substring(lastSlashIndex + 1, extensionIndex);
73
+ const normalizedFileName = fileNameWithoutExtension.replace(/[^a-zA-Z0-9$_]/g, '');
74
+ const firstCharUpperCased = normalizedFileName.charAt(0).toUpperCase();
75
+ let interfaceName = `I${firstCharUpperCased}${normalizedFileName.slice(1)}`;
76
+ if (!interfaceName.endsWith('strings') && !interfaceName.endsWith('Strings')) {
77
+ interfaceName += 'Strings';
78
+ }
79
+ return {
80
+ typings,
81
+ exportAsDefault: {
82
+ interfaceName
83
+ }
84
+ };
85
+ }
86
+ else {
87
+ return {
88
+ typings
89
+ };
90
+ }
91
+ }
92
+ });
93
+ }
94
+ }
95
+ //# sourceMappingURL=TypingsGenerator.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"TypingsGenerator.js","sourceRoot":"","sources":["../src/TypingsGenerator.ts"],"names":[],"mappings":"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","sourcesContent":["// Copyright (c) Microsoft Corporation. All rights reserved. Licensed under the MIT license.\n// See LICENSE in the project root for license information.\n\nimport {\n StringValuesTypingsGenerator,\n type IStringValueTypings,\n type IExportAsDefaultOptions,\n type IStringValueTyping,\n type ITypingsGeneratorBaseOptions\n} from '@rushstack/typings-generator';\nimport { FileSystem, type NewlineKind } from '@rushstack/node-core-library';\n\nimport type { IgnoreStringFunction, ILocalizationFile } from './interfaces';\nimport { parseLocFile } from './LocFileParser';\n\n/**\n * @public\n */\nexport interface IInferInterfaceNameExportAsDefaultOptions\n extends Omit<IExportAsDefaultOptions, 'interfaceName'> {\n /**\n * When `exportAsDefault` is true and this option is true, the default export interface name will be inferred\n * from the filename.\n */\n inferInterfaceNameFromFilename?: boolean;\n}\n\n/**\n * @public\n */\nexport interface ITypingsGeneratorOptions extends ITypingsGeneratorBaseOptions {\n /**\n * Options for configuring the default export.\n */\n exportAsDefault?: boolean | IExportAsDefaultOptions | IInferInterfaceNameExportAsDefaultOptions;\n\n /**\n * Normalizes the line endings in .resx files to the specified kind.\n */\n resxNewlineNormalization?: NewlineKind | undefined;\n\n /**\n * If specified, the generator will write trimmed .json files to the specified folders.\n * The .json files will be written to the same relative path as the source file.\n * For example, if the source file is \"&lt;root&gt;/foo/bar.resx\", and the output folder is \"dist\",\n * the trimmed .json file will be written to \"dist/foo/bar.resx.json\".\n */\n trimmedJsonOutputFolders?: string[] | undefined;\n\n /**\n * If true, .resx files will not throw errors if comments are missing.\n */\n ignoreMissingResxComments?: boolean | undefined;\n\n /**\n * Optionally, provide a function that will be called for each string. If the function returns `true`\n * the string will not be included.\n */\n ignoreString?: IgnoreStringFunction;\n\n /**\n * Processes the raw text of a comment.\n * @param comment - The original text of the comment to process\n * @param relativeFilePath - The relative file path\n * @param stringName - The name of the string that the comment is for\n * @returns The processed comment\n */\n processComment?: (\n comment: string | undefined,\n relativeFilePath: string,\n stringName: string\n ) => string | undefined;\n}\n\n/**\n * This is a simple tool that generates .d.ts files for .loc.json, .resx.json, .resjson, and .resx files.\n *\n * @public\n */\nexport class TypingsGenerator extends StringValuesTypingsGenerator {\n public constructor(options: ITypingsGeneratorOptions) {\n const {\n ignoreString,\n processComment,\n resxNewlineNormalization,\n ignoreMissingResxComments,\n trimmedJsonOutputFolders,\n exportAsDefault\n } = options;\n const inferDefaultExportInterfaceNameFromFilename: boolean | undefined =\n typeof exportAsDefault === 'object'\n ? (exportAsDefault as IInferInterfaceNameExportAsDefaultOptions).inferInterfaceNameFromFilename\n : undefined;\n\n const getJsonPaths: ((relativePath: string) => string[]) | undefined =\n trimmedJsonOutputFolders && trimmedJsonOutputFolders.length > 0\n ? (relativePath: string): string[] => {\n const jsonRelativePath: string =\n relativePath.endsWith('.json') || relativePath.endsWith('.resjson')\n ? relativePath\n : `${relativePath}.json`;\n\n const jsonPaths: string[] = [];\n for (const outputFolder of trimmedJsonOutputFolders) {\n jsonPaths.push(`${outputFolder}/${jsonRelativePath}`);\n }\n return jsonPaths;\n }\n : undefined;\n\n super({\n ...options,\n fileExtensions: ['.resx', '.resx.json', '.loc.json', '.resjson'],\n getAdditionalOutputFiles: getJsonPaths,\n // eslint-disable-next-line @typescript-eslint/naming-convention\n parseAndGenerateTypings: async (\n content: string,\n filePath: string,\n relativeFilePath: string\n ): Promise<IStringValueTypings> => {\n const locFileData: ILocalizationFile = parseLocFile({\n filePath,\n content,\n terminal: this.terminal,\n resxNewlineNormalization,\n ignoreMissingResxComments,\n ignoreString\n });\n\n const typings: IStringValueTyping[] = [];\n\n const json: Record<string, string> | undefined = trimmedJsonOutputFolders ? {} : undefined;\n\n for (const [stringName, value] of Object.entries(locFileData)) {\n let comment: string | undefined = value.comment;\n if (processComment) {\n comment = processComment(comment, relativeFilePath, stringName);\n }\n\n if (json) {\n json[stringName] = value.value;\n }\n\n typings.push({\n exportName: stringName,\n comment\n });\n }\n\n if (getJsonPaths) {\n const jsonBuffer: Buffer = Buffer.from(JSON.stringify(json), 'utf8');\n for (const jsonFile of getJsonPaths(relativeFilePath)) {\n await FileSystem.writeFileAsync(jsonFile, jsonBuffer, {\n ensureFolderExists: true\n });\n }\n }\n\n if (inferDefaultExportInterfaceNameFromFilename) {\n const lastSlashIndex: number = Math.max(filePath.lastIndexOf('/'), filePath.lastIndexOf('\\\\'));\n let extensionIndex: number = filePath.lastIndexOf('.');\n if (filePath.slice(extensionIndex).toLowerCase() === '.json') {\n extensionIndex = filePath.lastIndexOf('.', extensionIndex - 1);\n }\n\n const fileNameWithoutExtension: string = filePath.substring(lastSlashIndex + 1, extensionIndex);\n const normalizedFileName: string = fileNameWithoutExtension.replace(/[^a-zA-Z0-9$_]/g, '');\n const firstCharUpperCased: string = normalizedFileName.charAt(0).toUpperCase();\n let interfaceName: string | undefined = `I${firstCharUpperCased}${normalizedFileName.slice(1)}`;\n\n if (!interfaceName.endsWith('strings') && !interfaceName.endsWith('Strings')) {\n interfaceName += 'Strings';\n }\n\n return {\n typings,\n exportAsDefault: {\n interfaceName\n }\n };\n } else {\n return {\n typings\n };\n }\n }\n });\n }\n}\n"]}
@@ -0,0 +1,9 @@
1
+ // Copyright (c) Microsoft Corporation. All rights reserved. Licensed under the MIT license.
2
+ // See LICENSE in the project root for license information.
3
+ export { parseLocJson } from './parsers/parseLocJson';
4
+ export { parseResJson } from './parsers/parseResJson';
5
+ export { parseResx } from './parsers/parseResx';
6
+ export { parseLocFile } from './LocFileParser';
7
+ export { TypingsGenerator } from './TypingsGenerator';
8
+ export { getPseudolocalizer } from './Pseudolocalization';
9
+ //# sourceMappingURL=index.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.js","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA,4FAA4F;AAC5F,2DAA2D;AAe3D,OAAO,EAAE,YAAY,EAAE,MAAM,wBAAwB,CAAC;AACtD,OAAO,EAAE,YAAY,EAAE,MAAM,wBAAwB,CAAC;AACtD,OAAO,EAAE,SAAS,EAAsD,MAAM,qBAAqB,CAAC;AACpG,OAAO,EAAE,YAAY,EAA8C,MAAM,iBAAiB,CAAC;AAC3F,OAAO,EAGL,gBAAgB,EACjB,MAAM,oBAAoB,CAAC;AAC5B,OAAO,EAAE,kBAAkB,EAAE,MAAM,sBAAsB,CAAC","sourcesContent":["// Copyright (c) Microsoft Corporation. All rights reserved. Licensed under the MIT license.\n// See LICENSE in the project root for license information.\n\n/**\n * Some utilities for working with Rush Stack localization files.\n *\n * @packageDocumentation\n */\n\nexport type {\n ILocalizationFile,\n ILocalizedString,\n IPseudolocaleOptions,\n IParseFileOptions,\n IgnoreStringFunction\n} from './interfaces';\nexport { parseLocJson } from './parsers/parseLocJson';\nexport { parseResJson } from './parsers/parseResJson';\nexport { parseResx, type IParseResxOptions, type IParseResxOptionsBase } from './parsers/parseResx';\nexport { parseLocFile, type IParseLocFileOptions, type ParserKind } from './LocFileParser';\nexport {\n type ITypingsGeneratorOptions,\n type IInferInterfaceNameExportAsDefaultOptions,\n TypingsGenerator\n} from './TypingsGenerator';\nexport { getPseudolocalizer } from './Pseudolocalization';\n"]}
@@ -0,0 +1,4 @@
1
+ // Copyright (c) Microsoft Corporation. All rights reserved. Licensed under the MIT license.
2
+ // See LICENSE in the project root for license information.
3
+ export {};
4
+ //# sourceMappingURL=interfaces.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"interfaces.js","sourceRoot":"","sources":["../src/interfaces.ts"],"names":[],"mappings":"AAAA,4FAA4F;AAC5F,2DAA2D","sourcesContent":["// Copyright (c) Microsoft Corporation. All rights reserved. Licensed under the MIT license.\n// See LICENSE in the project root for license information.\n\n/**\n * Options for the pseudolocale library.\n *\n * @internalRemarks\n * Eventually this should be replaced with DefinitelyTyped types.\n *\n * @public\n */\nexport interface IPseudolocaleOptions {\n prepend?: string;\n append?: string;\n delimiter?: string;\n startDelimiter?: string;\n endDelimiter?: string;\n extend?: number;\n override?: string;\n}\n\n/**\n * @public\n */\nexport interface ILocalizationFile {\n [stringName: string]: ILocalizedString;\n}\n\n/**\n * @public\n */\nexport interface ILocalizedString {\n value: string;\n comment?: string;\n}\n\n/**\n * @public\n */\nexport interface IParseFileOptions {\n content: string;\n filePath: string;\n /**\n * Optionally, provide a function that will be called for each string. If the function returns `true`\n * the string will not be included.\n */\n ignoreString?: IgnoreStringFunction;\n}\n\n/**\n * @public\n */\nexport type IgnoreStringFunction = (filePath: string, stringName: string) => boolean;\n"]}
@@ -0,0 +1,28 @@
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 { JsonFile, JsonSchema } from '@rushstack/node-core-library';
4
+ import locJsonSchema from '../schemas/locJson.schema.json';
5
+ const LOC_JSON_SCHEMA = JsonSchema.fromLoadedObject(locJsonSchema);
6
+ /**
7
+ * @public
8
+ */
9
+ export function parseLocJson({ content, filePath, ignoreString }) {
10
+ const parsedFile = JsonFile.parseString(content);
11
+ try {
12
+ LOC_JSON_SCHEMA.validateObject(parsedFile, filePath, { ignoreSchemaField: true });
13
+ }
14
+ catch (e) {
15
+ throw new Error(`The loc file is invalid. Error: ${e}`);
16
+ }
17
+ // Normalize file shape and possibly filter
18
+ const newParsedFile = {};
19
+ for (const [key, stringData] of Object.entries(parsedFile)) {
20
+ if (!(ignoreString === null || ignoreString === void 0 ? void 0 : ignoreString(filePath, key))) {
21
+ // Normalize entry shape. We allow the values to be plain strings as a format that can be handed
22
+ // off to webpack builds that don't understand the comment syntax.
23
+ newParsedFile[key] = typeof stringData === 'string' ? { value: stringData } : stringData;
24
+ }
25
+ }
26
+ return newParsedFile;
27
+ }
28
+ //# sourceMappingURL=parseLocJson.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"parseLocJson.js","sourceRoot":"","sources":["../../src/parsers/parseLocJson.ts"],"names":[],"mappings":"AAAA,4FAA4F;AAC5F,2DAA2D;AAE3D,OAAO,EAAE,QAAQ,EAAE,UAAU,EAAE,MAAM,8BAA8B,CAAC;AAGpE,OAAO,aAAa,MAAM,gCAAgC,CAAC;AAE3D,MAAM,eAAe,GAAe,UAAU,CAAC,gBAAgB,CAAC,aAAa,CAAC,CAAC;AAE/E;;GAEG;AACH,MAAM,UAAU,YAAY,CAAC,EAAE,OAAO,EAAE,QAAQ,EAAE,YAAY,EAAqB;IACjF,MAAM,UAAU,GAAsB,QAAQ,CAAC,WAAW,CAAC,OAAO,CAAC,CAAC;IACpE,IAAI,CAAC;QACH,eAAe,CAAC,cAAc,CAAC,UAAU,EAAE,QAAQ,EAAE,EAAE,iBAAiB,EAAE,IAAI,EAAE,CAAC,CAAC;IACpF,CAAC;IAAC,OAAO,CAAC,EAAE,CAAC;QACX,MAAM,IAAI,KAAK,CAAC,mCAAmC,CAAC,EAAE,CAAC,CAAC;IAC1D,CAAC;IAED,2CAA2C;IAC3C,MAAM,aAAa,GAAsB,EAAE,CAAC;IAC5C,KAAK,MAAM,CAAC,GAAG,EAAE,UAAU,CAAC,IAAI,MAAM,CAAC,OAAO,CAAC,UAAU,CAAC,EAAE,CAAC;QAC3D,IAAI,CAAC,CAAA,YAAY,aAAZ,YAAY,uBAAZ,YAAY,CAAG,QAAQ,EAAE,GAAG,CAAC,CAAA,EAAE,CAAC;YACnC,gGAAgG;YAChG,kEAAkE;YAClE,aAAa,CAAC,GAAG,CAAC,GAAG,OAAO,UAAU,KAAK,QAAQ,CAAC,CAAC,CAAC,EAAE,KAAK,EAAE,UAAU,EAAE,CAAC,CAAC,CAAC,UAAU,CAAC;QAC3F,CAAC;IACH,CAAC;IAED,OAAO,aAAa,CAAC;AACvB,CAAC","sourcesContent":["// Copyright (c) Microsoft Corporation. All rights reserved. Licensed under the MIT license.\n// See LICENSE in the project root for license information.\n\nimport { JsonFile, JsonSchema } from '@rushstack/node-core-library';\n\nimport type { ILocalizationFile, IParseFileOptions } from '../interfaces';\nimport locJsonSchema from '../schemas/locJson.schema.json';\n\nconst LOC_JSON_SCHEMA: JsonSchema = JsonSchema.fromLoadedObject(locJsonSchema);\n\n/**\n * @public\n */\nexport function parseLocJson({ content, filePath, ignoreString }: IParseFileOptions): ILocalizationFile {\n const parsedFile: ILocalizationFile = JsonFile.parseString(content);\n try {\n LOC_JSON_SCHEMA.validateObject(parsedFile, filePath, { ignoreSchemaField: true });\n } catch (e) {\n throw new Error(`The loc file is invalid. Error: ${e}`);\n }\n\n // Normalize file shape and possibly filter\n const newParsedFile: ILocalizationFile = {};\n for (const [key, stringData] of Object.entries(parsedFile)) {\n if (!ignoreString?.(filePath, key)) {\n // Normalize entry shape. We allow the values to be plain strings as a format that can be handed\n // off to webpack builds that don't understand the comment syntax.\n newParsedFile[key] = typeof stringData === 'string' ? { value: stringData } : stringData;\n }\n }\n\n return newParsedFile;\n}\n"]}
@@ -0,0 +1,38 @@
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 { JsonFile } from '@rushstack/node-core-library';
4
+ /**
5
+ * @public
6
+ */
7
+ export function parseResJson({ content, ignoreString, filePath }) {
8
+ const resjsonFile = JsonFile.parseString(content);
9
+ const parsedFile = {};
10
+ const usedComments = new Map();
11
+ for (const [key, value] of Object.entries(resjsonFile)) {
12
+ if (key.startsWith('_') && key.endsWith('.comment')) {
13
+ if (!usedComments.has(key)) {
14
+ usedComments.set(key, false);
15
+ }
16
+ }
17
+ else {
18
+ const commentKey = `_${key}.comment`;
19
+ const comment = resjsonFile[commentKey];
20
+ usedComments.set(commentKey, true);
21
+ if (!(ignoreString === null || ignoreString === void 0 ? void 0 : ignoreString(filePath, key))) {
22
+ parsedFile[key] = { value, comment };
23
+ }
24
+ }
25
+ }
26
+ const orphanComments = [];
27
+ for (const [key, used] of usedComments) {
28
+ if (!used) {
29
+ orphanComments.push(key.slice(1, -'.comment'.length));
30
+ }
31
+ }
32
+ if (orphanComments.length > 0) {
33
+ throw new Error('The resjson file is invalid. Comments exist for the following string keys ' +
34
+ `that don't have values: ${orphanComments.join(', ')}.`);
35
+ }
36
+ return parsedFile;
37
+ }
38
+ //# sourceMappingURL=parseResJson.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"parseResJson.js","sourceRoot":"","sources":["../../src/parsers/parseResJson.ts"],"names":[],"mappings":"AAAA,4FAA4F;AAC5F,2DAA2D;AAE3D,OAAO,EAAE,QAAQ,EAAE,MAAM,8BAA8B,CAAC;AAIxD;;GAEG;AACH,MAAM,UAAU,YAAY,CAAC,EAAE,OAAO,EAAE,YAAY,EAAE,QAAQ,EAAqB;IACjF,MAAM,WAAW,GAA2B,QAAQ,CAAC,WAAW,CAAC,OAAO,CAAC,CAAC;IAC1E,MAAM,UAAU,GAAsB,EAAE,CAAC;IAEzC,MAAM,YAAY,GAAyB,IAAI,GAAG,EAAE,CAAC;IACrD,KAAK,MAAM,CAAC,GAAG,EAAE,KAAK,CAAC,IAAI,MAAM,CAAC,OAAO,CAAC,WAAW,CAAC,EAAE,CAAC;QACvD,IAAI,GAAG,CAAC,UAAU,CAAC,GAAG,CAAC,IAAI,GAAG,CAAC,QAAQ,CAAC,UAAU,CAAC,EAAE,CAAC;YACpD,IAAI,CAAC,YAAY,CAAC,GAAG,CAAC,GAAG,CAAC,EAAE,CAAC;gBAC3B,YAAY,CAAC,GAAG,CAAC,GAAG,EAAE,KAAK,CAAC,CAAC;YAC/B,CAAC;QACH,CAAC;aAAM,CAAC;YACN,MAAM,UAAU,GAAW,IAAI,GAAG,UAAU,CAAC;YAC7C,MAAM,OAAO,GAAuB,WAAW,CAAC,UAAU,CAAC,CAAC;YAC5D,YAAY,CAAC,GAAG,CAAC,UAAU,EAAE,IAAI,CAAC,CAAC;YAEnC,IAAI,CAAC,CAAA,YAAY,aAAZ,YAAY,uBAAZ,YAAY,CAAG,QAAQ,EAAE,GAAG,CAAC,CAAA,EAAE,CAAC;gBACnC,UAAU,CAAC,GAAG,CAAC,GAAG,EAAE,KAAK,EAAE,OAAO,EAAE,CAAC;YACvC,CAAC;QACH,CAAC;IACH,CAAC;IAED,MAAM,cAAc,GAAa,EAAE,CAAC;IACpC,KAAK,MAAM,CAAC,GAAG,EAAE,IAAI,CAAC,IAAI,YAAY,EAAE,CAAC;QACvC,IAAI,CAAC,IAAI,EAAE,CAAC;YACV,cAAc,CAAC,IAAI,CAAC,GAAG,CAAC,KAAK,CAAC,CAAC,EAAE,CAAC,UAAU,CAAC,MAAM,CAAC,CAAC,CAAC;QACxD,CAAC;IACH,CAAC;IAED,IAAI,cAAc,CAAC,MAAM,GAAG,CAAC,EAAE,CAAC;QAC9B,MAAM,IAAI,KAAK,CACb,4EAA4E;YAC1E,2BAA2B,cAAc,CAAC,IAAI,CAAC,IAAI,CAAC,GAAG,CAC1D,CAAC;IACJ,CAAC;IAED,OAAO,UAAU,CAAC;AACpB,CAAC","sourcesContent":["// Copyright (c) Microsoft Corporation. All rights reserved. Licensed under the MIT license.\n// See LICENSE in the project root for license information.\n\nimport { JsonFile } from '@rushstack/node-core-library';\n\nimport type { ILocalizationFile, IParseFileOptions } from '../interfaces';\n\n/**\n * @public\n */\nexport function parseResJson({ content, ignoreString, filePath }: IParseFileOptions): ILocalizationFile {\n const resjsonFile: Record<string, string> = JsonFile.parseString(content);\n const parsedFile: ILocalizationFile = {};\n\n const usedComments: Map<string, boolean> = new Map();\n for (const [key, value] of Object.entries(resjsonFile)) {\n if (key.startsWith('_') && key.endsWith('.comment')) {\n if (!usedComments.has(key)) {\n usedComments.set(key, false);\n }\n } else {\n const commentKey: string = `_${key}.comment`;\n const comment: string | undefined = resjsonFile[commentKey];\n usedComments.set(commentKey, true);\n\n if (!ignoreString?.(filePath, key)) {\n parsedFile[key] = { value, comment };\n }\n }\n }\n\n const orphanComments: string[] = [];\n for (const [key, used] of usedComments) {\n if (!used) {\n orphanComments.push(key.slice(1, -'.comment'.length));\n }\n }\n\n if (orphanComments.length > 0) {\n throw new Error(\n 'The resjson file is invalid. Comments exist for the following string keys ' +\n `that don't have values: ${orphanComments.join(', ')}.`\n );\n }\n\n return parsedFile;\n}\n"]}
@@ -0,0 +1,194 @@
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 { XmlDocument } from 'xmldoc';
4
+ import { Text } from '@rushstack/node-core-library';
5
+ /**
6
+ * @public
7
+ */
8
+ export function parseResx(options) {
9
+ const writeError = options.terminal.writeErrorLine.bind(options.terminal);
10
+ const writeWarning = options.terminal.writeWarningLine.bind(options.terminal);
11
+ const loggingFunctions = {
12
+ logError: (message) => writeError(message),
13
+ logWarning: (message) => writeWarning(message),
14
+ logFileError: (message, filePath, line, position) => {
15
+ _logWithLocation(writeError, message, filePath, line, position);
16
+ },
17
+ logFileWarning: (message, filePath, line, position) => {
18
+ _logWithLocation(writeWarning, message, filePath, line, position);
19
+ }
20
+ };
21
+ return _readResxAsLocFileInternal({
22
+ ...options,
23
+ loggingFunctions
24
+ });
25
+ }
26
+ function _readResxAsLocFileInternal(options) {
27
+ const { ignoreString } = options;
28
+ const xmlDocument = new XmlDocument(options.content);
29
+ if (xmlDocument.name !== 'root') {
30
+ _logErrorWithLocation(options, `Expected RESX to have a "root" element, found "${xmlDocument.name}"`, xmlDocument);
31
+ }
32
+ const locFile = {};
33
+ for (const childNode of xmlDocument.children) {
34
+ switch (childNode.type) {
35
+ case 'element': {
36
+ switch (childNode.name) {
37
+ case 'data': {
38
+ const stringName = childNode.attr.name;
39
+ if (!stringName) {
40
+ _logErrorWithLocation(options, 'Unexpected missing or empty string name', childNode);
41
+ }
42
+ else {
43
+ if (locFile.hasOwnProperty(stringName)) {
44
+ _logErrorWithLocation(options, `Duplicate string value "${stringName}"`, childNode);
45
+ }
46
+ const locString = _readDataElement(options, childNode);
47
+ if (locString && !(ignoreString === null || ignoreString === void 0 ? void 0 : ignoreString(options.filePath, stringName))) {
48
+ locFile[stringName] = locString;
49
+ }
50
+ }
51
+ break;
52
+ }
53
+ // Other allowed elements
54
+ case 'xsd:schema':
55
+ case 'resheader':
56
+ break;
57
+ default:
58
+ _logErrorWithLocation(options, `Unexpected RESX element ${childNode.name}`, childNode);
59
+ }
60
+ break;
61
+ }
62
+ case 'text': {
63
+ if (childNode.text.trim() !== '') {
64
+ _logErrorWithLocation(options, 'Found unexpected non-empty text node in RESX');
65
+ }
66
+ break;
67
+ }
68
+ case 'comment':
69
+ break;
70
+ default:
71
+ _logErrorWithLocation(options, `Unexpected ${childNode.type} child in RESX`);
72
+ break;
73
+ }
74
+ }
75
+ return locFile;
76
+ }
77
+ function _readDataElement(options, dataElement) {
78
+ let foundCommentElement = false;
79
+ let foundValueElement = false;
80
+ let comment = undefined;
81
+ let value = undefined;
82
+ for (const childNode of dataElement.children) {
83
+ switch (childNode.type) {
84
+ case 'element': {
85
+ switch (childNode.name) {
86
+ case 'value': {
87
+ if (foundValueElement) {
88
+ _logErrorWithLocation(options, 'Duplicate <value> element found', childNode);
89
+ }
90
+ else {
91
+ foundValueElement = true;
92
+ value = _readTextElement(options, childNode);
93
+ if (value && options.resxNewlineNormalization) {
94
+ value = Text.convertTo(value, options.resxNewlineNormalization);
95
+ }
96
+ }
97
+ break;
98
+ }
99
+ case 'comment': {
100
+ if (foundCommentElement) {
101
+ _logErrorWithLocation(options, 'Duplicate <comment> element found', childNode);
102
+ }
103
+ else {
104
+ foundCommentElement = true;
105
+ comment = _readTextElement(options, childNode);
106
+ }
107
+ break;
108
+ }
109
+ default:
110
+ _logErrorWithLocation(options, `Unexpected RESX element ${childNode.name}`, childNode);
111
+ break;
112
+ }
113
+ break;
114
+ }
115
+ case 'text': {
116
+ if (childNode.text.trim() !== '') {
117
+ _logErrorWithLocation(options, 'Found unexpected non-empty text node in RESX <data> element', dataElement);
118
+ }
119
+ break;
120
+ }
121
+ case 'comment':
122
+ break;
123
+ default:
124
+ _logErrorWithLocation(options, `Unexpected ${childNode.type} child in RESX <data> element`, dataElement);
125
+ }
126
+ }
127
+ if (!foundValueElement) {
128
+ _logErrorWithLocation(options, 'Missing string value in <data> element', dataElement);
129
+ }
130
+ else {
131
+ if (comment === undefined && options.ignoreMissingResxComments === false) {
132
+ _logWarningWithLocation(options, 'Missing string comment in <data> element', dataElement);
133
+ }
134
+ return {
135
+ value: value || '',
136
+ comment
137
+ };
138
+ }
139
+ }
140
+ function _readTextElement(options, element) {
141
+ let foundText = undefined;
142
+ for (const childNode of element.children) {
143
+ switch (childNode.type) {
144
+ case 'cdata':
145
+ case 'text': {
146
+ if (foundText !== undefined) {
147
+ _logErrorWithLocation(options, 'More than one child node found containing text content', element);
148
+ break;
149
+ }
150
+ foundText = childNode.type === 'text' ? childNode.text : childNode.cdata;
151
+ break;
152
+ }
153
+ case 'comment':
154
+ break;
155
+ case 'element':
156
+ _logErrorWithLocation(options, `Unexpected element`, childNode);
157
+ break;
158
+ default:
159
+ _logErrorWithLocation(options, `Unexpected ${element.type} child`, element);
160
+ break;
161
+ }
162
+ }
163
+ return foundText;
164
+ }
165
+ function _logErrorWithLocation(options, message, element) {
166
+ if (element) {
167
+ options.loggingFunctions.logFileError(message, options.filePath, element.line + 1, element.column + 1);
168
+ }
169
+ else {
170
+ options.loggingFunctions.logFileError(message, options.filePath);
171
+ }
172
+ }
173
+ function _logWarningWithLocation(options, message, element) {
174
+ if (element) {
175
+ options.loggingFunctions.logFileWarning(message, options.filePath, element.line + 1, element.column + 1);
176
+ }
177
+ else {
178
+ options.loggingFunctions.logFileWarning(message, options.filePath);
179
+ }
180
+ }
181
+ function _logWithLocation(loggingFn, message, filePath, line, position) {
182
+ let location;
183
+ if (position !== undefined) {
184
+ location = `${filePath}(${line},${position})`;
185
+ }
186
+ else if (line !== undefined) {
187
+ location = `${filePath}(${line})`;
188
+ }
189
+ else {
190
+ location = filePath;
191
+ }
192
+ loggingFn(`${location}: ${message}`);
193
+ }
194
+ //# sourceMappingURL=parseResx.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"parseResx.js","sourceRoot":"","sources":["../../src/parsers/parseResx.ts"],"names":[],"mappings":"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","sourcesContent":["// Copyright (c) Microsoft Corporation. All rights reserved. Licensed under the MIT license.\n// See LICENSE in the project root for license information.\n\nimport { XmlDocument, type XmlElement } from 'xmldoc';\n\nimport { Text, type NewlineKind } from '@rushstack/node-core-library';\nimport type { ITerminal } from '@rushstack/terminal';\n\nimport type { ILocalizedString, ILocalizationFile, IParseFileOptions } from '../interfaces';\n\n/**\n * @public\n */\nexport interface IParseResxOptions extends IParseFileOptions, IParseResxOptionsBase {}\n\n/**\n * @public\n */\nexport interface IParseResxOptionsBase {\n terminal: ITerminal;\n resxNewlineNormalization: NewlineKind | undefined;\n ignoreMissingResxComments: boolean | undefined;\n}\n\ninterface ILoggingFunctions {\n logError: (message: string) => void;\n logWarning: (message: string) => void;\n logFileError: (message: string, filePath: string, line?: number, position?: number) => void;\n logFileWarning: (message: string, filePath: string, line?: number, position?: number) => void;\n}\n\ninterface IResxReaderOptionsInternal extends Omit<IParseResxOptions, 'terminal'> {\n loggingFunctions: ILoggingFunctions;\n}\n\n/**\n * @public\n */\nexport function parseResx(options: IParseResxOptions): ILocalizationFile {\n const writeError: (message: string) => void = options.terminal.writeErrorLine.bind(options.terminal);\n const writeWarning: (message: string) => void = options.terminal.writeWarningLine.bind(options.terminal);\n const loggingFunctions: ILoggingFunctions = {\n logError: (message: string) => writeError(message),\n logWarning: (message: string) => writeWarning(message),\n logFileError: (message: string, filePath: string, line?: number, position?: number) => {\n _logWithLocation(writeError, message, filePath, line, position);\n },\n logFileWarning: (message: string, filePath: string, line?: number, position?: number) => {\n _logWithLocation(writeWarning, message, filePath, line, position);\n }\n };\n\n return _readResxAsLocFileInternal({\n ...options,\n loggingFunctions\n });\n}\n\nfunction _readResxAsLocFileInternal(options: IResxReaderOptionsInternal): ILocalizationFile {\n const { ignoreString } = options;\n const xmlDocument: XmlDocument = new XmlDocument(options.content);\n\n if (xmlDocument.name !== 'root') {\n _logErrorWithLocation(\n options,\n `Expected RESX to have a \"root\" element, found \"${xmlDocument.name}\"`,\n xmlDocument\n );\n }\n\n const locFile: ILocalizationFile = {};\n\n for (const childNode of xmlDocument.children) {\n switch (childNode.type) {\n case 'element': {\n switch (childNode.name) {\n case 'data': {\n const stringName: string = childNode.attr.name;\n if (!stringName) {\n _logErrorWithLocation(options, 'Unexpected missing or empty string name', childNode);\n } else {\n if (locFile.hasOwnProperty(stringName)) {\n _logErrorWithLocation(options, `Duplicate string value \"${stringName}\"`, childNode);\n }\n\n const locString: ILocalizedString | undefined = _readDataElement(options, childNode);\n\n if (locString && !ignoreString?.(options.filePath, stringName)) {\n locFile[stringName] = locString;\n }\n }\n\n break;\n }\n\n // Other allowed elements\n case 'xsd:schema':\n case 'resheader':\n break;\n\n default:\n _logErrorWithLocation(options, `Unexpected RESX element ${childNode.name}`, childNode);\n }\n\n break;\n }\n\n case 'text': {\n if (childNode.text.trim() !== '') {\n _logErrorWithLocation(options, 'Found unexpected non-empty text node in RESX');\n }\n\n break;\n }\n\n case 'comment':\n break;\n\n default:\n _logErrorWithLocation(options, `Unexpected ${childNode.type} child in RESX`);\n break;\n }\n }\n\n return locFile;\n}\n\nfunction _readDataElement(\n options: IResxReaderOptionsInternal,\n dataElement: XmlElement\n): ILocalizedString | undefined {\n let foundCommentElement: boolean = false;\n let foundValueElement: boolean = false;\n let comment: string | undefined = undefined;\n let value: string | undefined = undefined;\n\n for (const childNode of dataElement.children) {\n switch (childNode.type) {\n case 'element': {\n switch (childNode.name) {\n case 'value': {\n if (foundValueElement) {\n _logErrorWithLocation(options, 'Duplicate <value> element found', childNode);\n } else {\n foundValueElement = true;\n value = _readTextElement(options, childNode);\n if (value && options.resxNewlineNormalization) {\n value = Text.convertTo(value, options.resxNewlineNormalization);\n }\n }\n\n break;\n }\n\n case 'comment': {\n if (foundCommentElement) {\n _logErrorWithLocation(options, 'Duplicate <comment> element found', childNode);\n } else {\n foundCommentElement = true;\n comment = _readTextElement(options, childNode);\n }\n\n break;\n }\n\n default:\n _logErrorWithLocation(options, `Unexpected RESX element ${childNode.name}`, childNode);\n break;\n }\n\n break;\n }\n\n case 'text': {\n if (childNode.text.trim() !== '') {\n _logErrorWithLocation(\n options,\n 'Found unexpected non-empty text node in RESX <data> element',\n dataElement\n );\n }\n\n break;\n }\n\n case 'comment':\n break;\n\n default:\n _logErrorWithLocation(\n options,\n `Unexpected ${childNode.type} child in RESX <data> element`,\n dataElement\n );\n }\n }\n\n if (!foundValueElement) {\n _logErrorWithLocation(options, 'Missing string value in <data> element', dataElement);\n } else {\n if (comment === undefined && options.ignoreMissingResxComments === false) {\n _logWarningWithLocation(options, 'Missing string comment in <data> element', dataElement);\n }\n\n return {\n value: value || '',\n comment\n };\n }\n}\n\nfunction _readTextElement(options: IResxReaderOptionsInternal, element: XmlElement): string | undefined {\n let foundText: string | undefined = undefined;\n\n for (const childNode of element.children) {\n switch (childNode.type) {\n case 'cdata':\n case 'text': {\n if (foundText !== undefined) {\n _logErrorWithLocation(options, 'More than one child node found containing text content', element);\n break;\n }\n\n foundText = childNode.type === 'text' ? childNode.text : childNode.cdata;\n break;\n }\n\n case 'comment':\n break;\n\n case 'element':\n _logErrorWithLocation(options, `Unexpected element`, childNode);\n break;\n\n default:\n _logErrorWithLocation(options, `Unexpected ${element.type} child`, element);\n break;\n }\n }\n\n return foundText;\n}\n\nfunction _logErrorWithLocation(\n options: IResxReaderOptionsInternal,\n message: string,\n element?: XmlElement | XmlDocument\n): void {\n if (element) {\n options.loggingFunctions.logFileError(message, options.filePath, element.line + 1, element.column + 1);\n } else {\n options.loggingFunctions.logFileError(message, options.filePath);\n }\n}\n\nfunction _logWarningWithLocation(\n options: IResxReaderOptionsInternal,\n message: string,\n element?: XmlElement | XmlDocument\n): void {\n if (element) {\n options.loggingFunctions.logFileWarning(message, options.filePath, element.line + 1, element.column + 1);\n } else {\n options.loggingFunctions.logFileWarning(message, options.filePath);\n }\n}\n\nfunction _logWithLocation(\n loggingFn: (message: string) => void,\n message: string,\n filePath: string,\n line?: number,\n position?: number\n): void {\n let location: string;\n if (position !== undefined) {\n location = `${filePath}(${line},${position})`;\n } else if (line !== undefined) {\n location = `${filePath}(${line})`;\n } else {\n location = filePath;\n }\n\n loggingFn(`${location}: ${message}`);\n}\n"]}
@@ -0,0 +1,29 @@
1
+ {
2
+ "$schema": "http://json-schema.org/draft-04/schema#",
3
+ "title": "Localizable JSON file",
4
+
5
+ "patternProperties": {
6
+ "^[A-Za-z_$][0-9A-Za-z_$]*$": {
7
+ "oneOf": [
8
+ {
9
+ "type": "object",
10
+ "properties": {
11
+ "value": {
12
+ "type": "string"
13
+ },
14
+ "comment": {
15
+ "type": "string"
16
+ }
17
+ },
18
+ "additionalProperties": false,
19
+ "required": ["value"]
20
+ },
21
+ {
22
+ "type": "string"
23
+ }
24
+ ]
25
+ }
26
+ },
27
+ "additionalProperties": false,
28
+ "type": "object"
29
+ }
package/package.json CHANGED
@@ -1,9 +1,33 @@
1
1
  {
2
2
  "name": "@rushstack/localization-utilities",
3
- "version": "0.14.14",
3
+ "version": "0.15.1",
4
4
  "description": "This plugin contains some useful functions for localization.",
5
- "main": "lib/index.js",
6
- "typings": "dist/localization-utilities.d.ts",
5
+ "main": "./lib-commonjs/index.js",
6
+ "module": "./lib-esm/index.js",
7
+ "types": "./dist/localization-utilities.d.ts",
8
+ "exports": {
9
+ ".": {
10
+ "types": "./dist/localization-utilities.d.ts",
11
+ "node": "./lib-commonjs/index.js",
12
+ "import": "./lib-esm/index.js",
13
+ "require": "./lib-commonjs/index.js"
14
+ },
15
+ "./lib/*.schema.json": "./lib-commonjs/*.schema.json",
16
+ "./lib/*": {
17
+ "types": "./lib-dts/*.d.ts",
18
+ "node": "./lib-commonjs/*.js",
19
+ "import": "./lib-esm/*.js",
20
+ "require": "./lib-commonjs/*.js"
21
+ },
22
+ "./package.json": "./package.json"
23
+ },
24
+ "typesVersions": {
25
+ "*": {
26
+ "lib/*": [
27
+ "lib-dts/*"
28
+ ]
29
+ }
30
+ },
7
31
  "license": "MIT",
8
32
  "repository": {
9
33
  "type": "git",
@@ -13,16 +37,17 @@
13
37
  "dependencies": {
14
38
  "pseudolocale": "~1.1.0",
15
39
  "xmldoc": "~1.1.2",
16
- "@rushstack/node-core-library": "5.19.1",
17
- "@rushstack/terminal": "0.21.0",
18
- "@rushstack/typings-generator": "0.15.14"
40
+ "@rushstack/node-core-library": "5.20.1",
41
+ "@rushstack/terminal": "0.22.1",
42
+ "@rushstack/typings-generator": "0.16.1"
19
43
  },
20
44
  "devDependencies": {
21
45
  "@types/xmldoc": "1.1.4",
22
46
  "eslint": "~9.37.0",
23
- "@rushstack/heft": "1.1.14",
47
+ "@rushstack/heft": "1.2.1",
24
48
  "local-node-rig": "1.0.0"
25
49
  },
50
+ "sideEffects": false,
26
51
  "scripts": {
27
52
  "build": "heft build --clean",
28
53
  "_phase:build": "heft run --only build -- --clean",
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes