@rushstack/localization-utilities 0.1.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.
package/LICENSE ADDED
@@ -0,0 +1,24 @@
1
+ @rushstack/localization-utilities
2
+
3
+ Copyright (c) Microsoft Corporation. All rights reserved.
4
+
5
+ MIT License
6
+
7
+ Permission is hereby granted, free of charge, to any person obtaining
8
+ a copy of this software and associated documentation files (the
9
+ "Software"), to deal in the Software without restriction, including
10
+ without limitation the rights to use, copy, modify, merge, publish,
11
+ distribute, sublicense, and/or sell copies of the Software, and to
12
+ permit persons to whom the Software is furnished to do so, subject to
13
+ the following conditions:
14
+
15
+ The above copyright notice and this permission notice shall be
16
+ included in all copies or substantial portions of the Software.
17
+
18
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
19
+ EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
20
+ MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
21
+ NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
22
+ LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
23
+ OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
24
+ WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
package/README.md ADDED
@@ -0,0 +1,3 @@
1
+ # @rushstack/localization-utilities
2
+
3
+ This library provides a set of utilities for working with localization files.
@@ -0,0 +1,103 @@
1
+ import { ITerminal } from '@rushstack/node-core-library';
2
+ import { NewlineKind } from '@rushstack/node-core-library';
3
+ import { StringValuesTypingsGenerator } from '@rushstack/typings-generator';
4
+
5
+ /**
6
+ * Get a function that pseudolocalizes a string.
7
+ *
8
+ * @public
9
+ */
10
+ export declare function getPseudolocalizer(options: IPseudolocaleOptions): (str: string) => string;
11
+
12
+ /**
13
+ * @public
14
+ */
15
+ export declare interface ILocalizationFile {
16
+ [stringName: string]: ILocalizedString;
17
+ }
18
+
19
+ /**
20
+ * @public
21
+ */
22
+ export declare interface ILocalizedString {
23
+ value: string;
24
+ comment?: string;
25
+ }
26
+
27
+ /**
28
+ * @public
29
+ */
30
+ export declare interface IParseLocFileOptions {
31
+ terminal: ITerminal;
32
+ filePath: string;
33
+ content: string;
34
+ resxNewlineNormalization: NewlineKind | undefined;
35
+ ignoreMissingResxComments: boolean | undefined;
36
+ }
37
+
38
+ /**
39
+ * Options for the pseudolocale library.
40
+ *
41
+ * @internalRemarks
42
+ * Eventually this should be replaced with DefinitelyTyped types.
43
+ *
44
+ * @public
45
+ */
46
+ export declare interface IPseudolocaleOptions {
47
+ prepend?: string;
48
+ append?: string;
49
+ delimiter?: string;
50
+ startDelimiter?: string;
51
+ endDelimiter?: string;
52
+ extend?: number;
53
+ override?: string;
54
+ }
55
+
56
+ /**
57
+ * @public
58
+ */
59
+ export declare interface IResxReaderOptions {
60
+ resxFilePath: string;
61
+ terminal: ITerminal;
62
+ newlineNormalization: NewlineKind | undefined;
63
+ warnOnMissingComment: boolean;
64
+ }
65
+
66
+ /**
67
+ * @public
68
+ */
69
+ export declare interface ITypingsGeneratorOptions {
70
+ srcFolder: string;
71
+ generatedTsFolder: string;
72
+ terminal?: ITerminal;
73
+ exportAsDefault?: boolean;
74
+ globsToIgnore?: string[];
75
+ resxNewlineNormalization?: NewlineKind | undefined;
76
+ ignoreMissingResxComments?: boolean | undefined;
77
+ }
78
+
79
+ /**
80
+ * @public
81
+ */
82
+ export declare function parseLocFile(options: IParseLocFileOptions): ILocalizationFile;
83
+
84
+ /**
85
+ * @public
86
+ */
87
+ export declare function readResxAsLocFile(resxContents: string, options: IResxReaderOptions): ILocalizationFile;
88
+
89
+ /**
90
+ * @public
91
+ */
92
+ export declare function readResxFileAsLocFile(options: IResxReaderOptions): ILocalizationFile;
93
+
94
+ /**
95
+ * This is a simple tool that generates .d.ts files for .loc.json, .resx.json, .resjson, and .resx files.
96
+ *
97
+ * @public
98
+ */
99
+ export declare class TypingsGenerator extends StringValuesTypingsGenerator {
100
+ constructor(options: ITypingsGeneratorOptions);
101
+ }
102
+
103
+ export { }
@@ -0,0 +1,11 @@
1
+ // This file is read by tools that parse documentation comments conforming to the TSDoc standard.
2
+ // It should be published with your NPM package. It should not be tracked by Git.
3
+ {
4
+ "tsdocVersion": "0.12",
5
+ "toolPackages": [
6
+ {
7
+ "packageName": "@microsoft/api-extractor",
8
+ "packageVersion": "7.24.2"
9
+ }
10
+ ]
11
+ }
@@ -0,0 +1,17 @@
1
+ import { ITerminal, NewlineKind } from '@rushstack/node-core-library';
2
+ import { ILocalizationFile } from './interfaces';
3
+ /**
4
+ * @public
5
+ */
6
+ export interface IParseLocFileOptions {
7
+ terminal: ITerminal;
8
+ filePath: string;
9
+ content: string;
10
+ resxNewlineNormalization: NewlineKind | undefined;
11
+ ignoreMissingResxComments: boolean | undefined;
12
+ }
13
+ /**
14
+ * @public
15
+ */
16
+ export declare function parseLocFile(options: IParseLocFileOptions): ILocalizationFile;
17
+ //# sourceMappingURL=LocFileParser.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"LocFileParser.d.ts","sourceRoot":"","sources":["../src/LocFileParser.ts"],"names":[],"mappings":"AAGA,OAAO,EAAE,SAAS,EAAE,WAAW,EAAwB,MAAM,8BAA8B,CAAC;AAE5F,OAAO,EAAE,iBAAiB,EAAE,MAAM,cAAc,CAAC;AAKjD;;GAEG;AACH,MAAM,WAAW,oBAAoB;IACnC,QAAQ,EAAE,SAAS,CAAC;IACpB,QAAQ,EAAE,MAAM,CAAC;IACjB,OAAO,EAAE,MAAM,CAAC;IAChB,wBAAwB,EAAE,WAAW,GAAG,SAAS,CAAC;IAClD,yBAAyB,EAAE,OAAO,GAAG,SAAS,CAAC;CAChD;AASD;;GAEG;AACH,wBAAgB,YAAY,CAAC,OAAO,EAAE,oBAAoB,GAAG,iBAAiB,CA0D7E"}
@@ -0,0 +1,73 @@
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
+ Object.defineProperty(exports, "__esModule", { value: true });
5
+ exports.parseLocFile = void 0;
6
+ const node_core_library_1 = require("@rushstack/node-core-library");
7
+ const ResxReader_1 = require("./ResxReader");
8
+ const LOC_JSON_SCHEMA = node_core_library_1.JsonSchema.fromFile(`${__dirname}/schemas/locJson.schema.json`);
9
+ const parseCache = new Map();
10
+ /**
11
+ * @public
12
+ */
13
+ function parseLocFile(options) {
14
+ const fileCacheKey = `${options.filePath}?${options.resxNewlineNormalization || 'none'}`;
15
+ if (parseCache.has(fileCacheKey)) {
16
+ const entry = parseCache.get(fileCacheKey);
17
+ if (entry.content === options.content) {
18
+ return entry.parsedFile;
19
+ }
20
+ }
21
+ let parsedFile;
22
+ if (/\.resx$/i.test(options.filePath)) {
23
+ parsedFile = (0, ResxReader_1.readResxAsLocFile)(options.content, {
24
+ terminal: options.terminal,
25
+ resxFilePath: options.filePath,
26
+ newlineNormalization: options.resxNewlineNormalization,
27
+ warnOnMissingComment: !options.ignoreMissingResxComments
28
+ });
29
+ }
30
+ else if (/\.(resx|loc)\.json$/i.test(options.filePath)) {
31
+ parsedFile = node_core_library_1.JsonFile.parseString(options.content);
32
+ try {
33
+ LOC_JSON_SCHEMA.validateObject(parsedFile, options.filePath);
34
+ }
35
+ catch (e) {
36
+ options.terminal.writeError(`The loc file is invalid. Error: ${e}`);
37
+ }
38
+ }
39
+ else if (/\.resjson$/i.test(options.filePath)) {
40
+ const resjsonFile = node_core_library_1.JsonFile.parseString(options.content);
41
+ parsedFile = {};
42
+ const comments = new Map();
43
+ for (const [key, value] of Object.entries(resjsonFile)) {
44
+ if (key.startsWith('_') && key.endsWith('.comment')) {
45
+ const commentKey = key.substring(1, key.length - '.comment'.length);
46
+ comments.set(commentKey, value);
47
+ }
48
+ else {
49
+ parsedFile[key] = { value };
50
+ }
51
+ }
52
+ const orphanComments = [];
53
+ for (const [key, comment] of comments) {
54
+ if (parsedFile[key]) {
55
+ parsedFile[key].comment = comment;
56
+ }
57
+ else {
58
+ orphanComments.push(key);
59
+ }
60
+ }
61
+ if (orphanComments.length > 0) {
62
+ options.terminal.writeErrorLine('The resjson file is invalid. Comments exist for the following string keys ' +
63
+ `that don't have values: ${orphanComments.join(', ')}.`);
64
+ }
65
+ }
66
+ else {
67
+ throw new Error(`Unsupported file extension in file: ${options.filePath}`);
68
+ }
69
+ parseCache.set(fileCacheKey, { content: options.content, parsedFile });
70
+ return parsedFile;
71
+ }
72
+ exports.parseLocFile = parseLocFile;
73
+ //# sourceMappingURL=LocFileParser.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"LocFileParser.js","sourceRoot":"","sources":["../src/LocFileParser.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 { ITerminal, NewlineKind, JsonFile, JsonSchema } from '@rushstack/node-core-library';\n\nimport { ILocalizationFile } from './interfaces';\nimport { readResxAsLocFile } from './ResxReader';\n\nconst LOC_JSON_SCHEMA: JsonSchema = JsonSchema.fromFile(`${__dirname}/schemas/locJson.schema.json`);\n\n/**\n * @public\n */\nexport interface IParseLocFileOptions {\n terminal: ITerminal;\n filePath: string;\n content: string;\n resxNewlineNormalization: NewlineKind | undefined;\n ignoreMissingResxComments: boolean | undefined;\n}\n\ninterface IParseCacheEntry {\n content: string;\n parsedFile: ILocalizationFile;\n}\n\nconst parseCache: Map<string, IParseCacheEntry> = new Map<string, IParseCacheEntry>();\n\n/**\n * @public\n */\nexport function parseLocFile(options: IParseLocFileOptions): ILocalizationFile {\n const fileCacheKey: string = `${options.filePath}?${options.resxNewlineNormalization || 'none'}`;\n if (parseCache.has(fileCacheKey)) {\n const entry: IParseCacheEntry = parseCache.get(fileCacheKey)!;\n if (entry.content === options.content) {\n return entry.parsedFile;\n }\n }\n\n let parsedFile: ILocalizationFile;\n if (/\\.resx$/i.test(options.filePath)) {\n parsedFile = readResxAsLocFile(options.content, {\n terminal: options.terminal,\n resxFilePath: options.filePath,\n newlineNormalization: options.resxNewlineNormalization,\n warnOnMissingComment: !options.ignoreMissingResxComments\n });\n } else if (/\\.(resx|loc)\\.json$/i.test(options.filePath)) {\n parsedFile = JsonFile.parseString(options.content);\n try {\n LOC_JSON_SCHEMA.validateObject(parsedFile, options.filePath);\n } catch (e) {\n options.terminal.writeError(`The loc file is invalid. Error: ${e}`);\n }\n } else if (/\\.resjson$/i.test(options.filePath)) {\n const resjsonFile: Record<string, string> = JsonFile.parseString(options.content);\n parsedFile = {};\n const comments: Map<string, string> = new Map();\n for (const [key, value] of Object.entries(resjsonFile)) {\n if (key.startsWith('_') && key.endsWith('.comment')) {\n const commentKey: string = key.substring(1, key.length - '.comment'.length);\n comments.set(commentKey, value);\n } else {\n parsedFile[key] = { value };\n }\n }\n\n const orphanComments: string[] = [];\n for (const [key, comment] of comments) {\n if (parsedFile[key]) {\n parsedFile[key].comment = comment;\n } else {\n orphanComments.push(key);\n }\n }\n\n if (orphanComments.length > 0) {\n options.terminal.writeErrorLine(\n 'The resjson file is invalid. Comments exist for the following string keys ' +\n `that don't have values: ${orphanComments.join(', ')}.`\n );\n }\n } else {\n throw new Error(`Unsupported file extension in file: ${options.filePath}`);\n }\n\n parseCache.set(fileCacheKey, { content: options.content, parsedFile });\n return parsedFile;\n}\n"]}
@@ -0,0 +1,23 @@
1
+ import { StringValuesTypingsGenerator } from '@rushstack/typings-generator';
2
+ import { ITerminal, NewlineKind } from '@rushstack/node-core-library';
3
+ /**
4
+ * @public
5
+ */
6
+ export interface ITypingsGeneratorOptions {
7
+ srcFolder: string;
8
+ generatedTsFolder: string;
9
+ terminal?: ITerminal;
10
+ exportAsDefault?: boolean;
11
+ globsToIgnore?: string[];
12
+ resxNewlineNormalization?: NewlineKind | undefined;
13
+ ignoreMissingResxComments?: boolean | undefined;
14
+ }
15
+ /**
16
+ * This is a simple tool that generates .d.ts files for .loc.json, .resx.json, .resjson, and .resx files.
17
+ *
18
+ * @public
19
+ */
20
+ export declare class LocFileTypingsGenerator extends StringValuesTypingsGenerator {
21
+ constructor(options: ITypingsGeneratorOptions);
22
+ }
23
+ //# sourceMappingURL=LocFileTypingsGenerator.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"LocFileTypingsGenerator.d.ts","sourceRoot":"","sources":["../src/LocFileTypingsGenerator.ts"],"names":[],"mappings":"AAGA,OAAO,EAAE,4BAA4B,EAAsB,MAAM,8BAA8B,CAAC;AAChG,OAAO,EAAE,SAAS,EAAE,WAAW,EAAE,MAAM,8BAA8B,CAAC;AAKtE;;GAEG;AACH,MAAM,WAAW,wBAAwB;IACvC,SAAS,EAAE,MAAM,CAAC;IAClB,iBAAiB,EAAE,MAAM,CAAC;IAC1B,QAAQ,CAAC,EAAE,SAAS,CAAC;IACrB,eAAe,CAAC,EAAE,OAAO,CAAC;IAC1B,aAAa,CAAC,EAAE,MAAM,EAAE,CAAC;IACzB,wBAAwB,CAAC,EAAE,WAAW,GAAG,SAAS,CAAC;IACnD,yBAAyB,CAAC,EAAE,OAAO,GAAG,SAAS,CAAC;CACjD;AAED;;;;GAIG;AACH,qBAAa,uBAAwB,SAAQ,4BAA4B;gBACpD,OAAO,EAAE,wBAAwB;CA2BrD"}
@@ -0,0 +1,36 @@
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
+ Object.defineProperty(exports, "__esModule", { value: true });
5
+ exports.LocFileTypingsGenerator = void 0;
6
+ const typings_generator_1 = require("@rushstack/typings-generator");
7
+ const LocFileParser_1 = require("./LocFileParser");
8
+ /**
9
+ * This is a simple tool that generates .d.ts files for .loc.json, .resx.json, .resjson, and .resx files.
10
+ *
11
+ * @public
12
+ */
13
+ class LocFileTypingsGenerator extends typings_generator_1.StringValuesTypingsGenerator {
14
+ constructor(options) {
15
+ super(Object.assign(Object.assign({}, options), { fileExtensions: ['.resx', '.resx.json', '.loc.json', '.resjson'], parseAndGenerateTypings: (fileContents, filePath) => {
16
+ const locFileData = (0, LocFileParser_1.parseLocFile)({
17
+ filePath: filePath,
18
+ content: fileContents,
19
+ terminal: this._options.terminal,
20
+ resxNewlineNormalization: options.resxNewlineNormalization,
21
+ ignoreMissingResxComments: options.ignoreMissingResxComments
22
+ });
23
+ const typings = [];
24
+ // eslint-disable-next-line guard-for-in
25
+ for (const stringName in locFileData) {
26
+ typings.push({
27
+ exportName: stringName,
28
+ comment: locFileData[stringName].comment
29
+ });
30
+ }
31
+ return { typings };
32
+ } }));
33
+ }
34
+ }
35
+ exports.LocFileTypingsGenerator = LocFileTypingsGenerator;
36
+ //# sourceMappingURL=LocFileTypingsGenerator.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"LocFileTypingsGenerator.js","sourceRoot":"","sources":["../src/LocFileTypingsGenerator.ts"],"names":[],"mappings":";AAAA,4FAA4F;AAC5F,2DAA2D;;;AAE3D,oEAAgG;AAIhG,mDAA+C;AAe/C;;;;GAIG;AACH,MAAa,uBAAwB,SAAQ,gDAA4B;IACvE,YAAmB,OAAiC;QAClD,KAAK,iCACA,OAAO,KACV,cAAc,EAAE,CAAC,OAAO,EAAE,YAAY,EAAE,WAAW,EAAE,UAAU,CAAC,EAChE,uBAAuB,EAAE,CAAC,YAAoB,EAAE,QAAgB,EAAE,EAAE;gBAClE,MAAM,WAAW,GAAsB,IAAA,4BAAY,EAAC;oBAClD,QAAQ,EAAE,QAAQ;oBAClB,OAAO,EAAE,YAAY;oBACrB,QAAQ,EAAE,IAAI,CAAC,QAAQ,CAAC,QAAS;oBACjC,wBAAwB,EAAE,OAAO,CAAC,wBAAwB;oBAC1D,yBAAyB,EAAE,OAAO,CAAC,yBAAyB;iBAC7D,CAAC,CAAC;gBAEH,MAAM,OAAO,GAAyB,EAAE,CAAC;gBAEzC,wCAAwC;gBACxC,KAAK,MAAM,UAAU,IAAI,WAAW,EAAE;oBACpC,OAAO,CAAC,IAAI,CAAC;wBACX,UAAU,EAAE,UAAU;wBACtB,OAAO,EAAE,WAAW,CAAC,UAAU,CAAC,CAAC,OAAO;qBACzC,CAAC,CAAC;iBACJ;gBAED,OAAO,EAAE,OAAO,EAAE,CAAC;YACrB,CAAC,IACD,CAAC;IACL,CAAC;CACF;AA5BD,0DA4BC","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 { StringValuesTypingsGenerator, IStringValueTyping } from '@rushstack/typings-generator';\nimport { ITerminal, NewlineKind } from '@rushstack/node-core-library';\n\nimport { ILocalizationFile } from './interfaces';\nimport { parseLocFile } from './LocFileParser';\n\n/**\n * @public\n */\nexport interface ITypingsGeneratorOptions {\n srcFolder: string;\n generatedTsFolder: string;\n terminal?: ITerminal;\n exportAsDefault?: boolean;\n globsToIgnore?: string[];\n resxNewlineNormalization?: NewlineKind | undefined;\n ignoreMissingResxComments?: boolean | 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 LocFileTypingsGenerator extends StringValuesTypingsGenerator {\n public constructor(options: ITypingsGeneratorOptions) {\n super({\n ...options,\n fileExtensions: ['.resx', '.resx.json', '.loc.json', '.resjson'],\n parseAndGenerateTypings: (fileContents: string, filePath: string) => {\n const locFileData: ILocalizationFile = parseLocFile({\n filePath: filePath,\n content: fileContents,\n terminal: this._options.terminal!,\n resxNewlineNormalization: options.resxNewlineNormalization,\n ignoreMissingResxComments: options.ignoreMissingResxComments\n });\n\n const typings: IStringValueTyping[] = [];\n\n // eslint-disable-next-line guard-for-in\n for (const stringName in locFileData) {\n typings.push({\n exportName: stringName,\n comment: locFileData[stringName].comment\n });\n }\n\n return { typings };\n }\n });\n }\n}\n"]}
@@ -0,0 +1,8 @@
1
+ import { IPseudolocaleOptions } from './interfaces';
2
+ /**
3
+ * Get a function that pseudolocalizes a string.
4
+ *
5
+ * @public
6
+ */
7
+ export declare function getPseudolocalizer(options: IPseudolocaleOptions): (str: string) => string;
8
+ //# sourceMappingURL=Pseudolocalization.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"Pseudolocalization.d.ts","sourceRoot":"","sources":["../src/Pseudolocalization.ts"],"names":[],"mappings":"AAKA,OAAO,EAAE,oBAAoB,EAAE,MAAM,cAAc,CAAC;AAEpD;;;;GAIG;AACH,wBAAgB,kBAAkB,CAAC,OAAO,EAAE,oBAAoB,GAAG,CAAC,GAAG,EAAE,MAAM,KAAK,MAAM,CAUzF"}
@@ -0,0 +1,23 @@
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 __importDefault = (this && this.__importDefault) || function (mod) {
5
+ return (mod && mod.__esModule) ? mod : { "default": mod };
6
+ };
7
+ Object.defineProperty(exports, "__esModule", { value: true });
8
+ exports.getPseudolocalizer = void 0;
9
+ const decache_1 = __importDefault(require("decache"));
10
+ /**
11
+ * Get a function that pseudolocalizes a string.
12
+ *
13
+ * @public
14
+ */
15
+ function getPseudolocalizer(options) {
16
+ // pseudolocale maintains static state, so we need to load it as isolated modules
17
+ (0, decache_1.default)('pseudolocale');
18
+ const pseudolocale = require('pseudolocale'); // eslint-disable-line
19
+ pseudolocale.option = Object.assign(Object.assign({}, pseudolocale.option), options);
20
+ return pseudolocale.str;
21
+ }
22
+ exports.getPseudolocalizer = getPseudolocalizer;
23
+ //# 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,sDAA8B;AAI9B;;;;GAIG;AACH,SAAgB,kBAAkB,CAAC,OAA6B;IAC9D,iFAAiF;IACjF,IAAA,iBAAO,EAAC,cAAc,CAAC,CAAC;IACxB,MAAM,YAAY,GAAG,OAAO,CAAC,cAAc,CAAC,CAAC,CAAC,sBAAsB;IAEpE,YAAY,CAAC,MAAM,mCACd,YAAY,CAAC,MAAM,GACnB,OAAO,CACX,CAAC;IACF,OAAO,YAAY,CAAC,GAAG,CAAC;AAC1B,CAAC;AAVD,gDAUC","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 decache from 'decache';\n\nimport { IPseudolocaleOptions } from './interfaces';\n\n/**\n * Get a function that pseudolocalizes a string.\n *\n * @public\n */\nexport function getPseudolocalizer(options: IPseudolocaleOptions): (str: string) => string {\n // pseudolocale maintains static state, so we need to load it as isolated modules\n decache('pseudolocale');\n const pseudolocale = require('pseudolocale'); // eslint-disable-line\n\n pseudolocale.option = {\n ...pseudolocale.option,\n ...options\n };\n return pseudolocale.str;\n}\n"]}
@@ -0,0 +1,20 @@
1
+ import { ITerminal, NewlineKind } from '@rushstack/node-core-library';
2
+ import { ILocalizationFile } from './interfaces';
3
+ /**
4
+ * @public
5
+ */
6
+ export interface IResxReaderOptions {
7
+ resxFilePath: string;
8
+ terminal: ITerminal;
9
+ newlineNormalization: NewlineKind | undefined;
10
+ warnOnMissingComment: boolean;
11
+ }
12
+ /**
13
+ * @public
14
+ */
15
+ export declare function readResxFileAsLocFile(options: IResxReaderOptions): ILocalizationFile;
16
+ /**
17
+ * @public
18
+ */
19
+ export declare function readResxAsLocFile(resxContents: string, options: IResxReaderOptions): ILocalizationFile;
20
+ //# sourceMappingURL=ResxReader.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"ResxReader.d.ts","sourceRoot":"","sources":["../src/ResxReader.ts"],"names":[],"mappings":"AAGA,OAAO,EAAc,SAAS,EAAQ,WAAW,EAAE,MAAM,8BAA8B,CAAC;AAGxF,OAAO,EAAoB,iBAAiB,EAAE,MAAM,cAAc,CAAC;AAInE;;GAEG;AACH,MAAM,WAAW,kBAAkB;IACjC,YAAY,EAAE,MAAM,CAAC;IACrB,QAAQ,EAAE,SAAS,CAAC;IACpB,oBAAoB,EAAE,WAAW,GAAG,SAAS,CAAC;IAC9C,oBAAoB,EAAE,OAAO,CAAC;CAC/B;AAcD;;GAEG;AACH,wBAAgB,qBAAqB,CAAC,OAAO,EAAE,kBAAkB,GAAG,iBAAiB,CAGpF;AAED;;GAEG;AACH,wBAAgB,iBAAiB,CAAC,YAAY,EAAE,MAAM,EAAE,OAAO,EAAE,kBAAkB,GAAG,iBAAiB,CAmBtG"}
@@ -0,0 +1,207 @@
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
+ Object.defineProperty(exports, "__esModule", { value: true });
5
+ exports.readResxAsLocFile = exports.readResxFileAsLocFile = void 0;
6
+ const node_core_library_1 = require("@rushstack/node-core-library");
7
+ const xmldoc_1 = require("xmldoc");
8
+ const STRING_NAME_RESX = /^[A-z_$][A-z0-9_$]*$/;
9
+ /**
10
+ * @public
11
+ */
12
+ function readResxFileAsLocFile(options) {
13
+ const resxContents = node_core_library_1.FileSystem.readFile(options.resxFilePath);
14
+ return readResxAsLocFile(resxContents, options);
15
+ }
16
+ exports.readResxFileAsLocFile = readResxFileAsLocFile;
17
+ /**
18
+ * @public
19
+ */
20
+ function readResxAsLocFile(resxContents, options) {
21
+ const writeError = options.terminal.writeErrorLine.bind(options.terminal);
22
+ const writeWarning = options.terminal.writeWarningLine.bind(options.terminal);
23
+ const loggingFunctions = {
24
+ logError: (message) => writeError(message),
25
+ logWarning: (message) => writeWarning(message),
26
+ logFileError: (message, filePath, line, position) => {
27
+ _logWithLocation(writeError, message, filePath, line, position);
28
+ },
29
+ logFileWarning: (message, filePath, line, position) => {
30
+ _logWithLocation(writeWarning, message, filePath, line, position);
31
+ }
32
+ };
33
+ return _readResxAsLocFileInternal(Object.assign(Object.assign({}, options), { resxContents,
34
+ loggingFunctions }));
35
+ }
36
+ exports.readResxAsLocFile = readResxAsLocFile;
37
+ function _readResxAsLocFileInternal(options) {
38
+ const xmlDocument = new xmldoc_1.XmlDocument(options.resxContents);
39
+ if (xmlDocument.name !== 'root') {
40
+ _logErrorWithLocation(options, `Expected RESX to have a "root" element, found "${xmlDocument.name}"`, xmlDocument);
41
+ }
42
+ const locFile = {};
43
+ for (const childNode of xmlDocument.children) {
44
+ switch (childNode.type) {
45
+ case 'element': {
46
+ switch (childNode.name) {
47
+ case 'data': {
48
+ const stringName = childNode.attr.name;
49
+ if (!stringName) {
50
+ _logErrorWithLocation(options, 'Unexpected missing or empty string name', childNode);
51
+ }
52
+ else if (!STRING_NAME_RESX.test(stringName)) {
53
+ _logErrorWithLocation(options, `Invalid string name "${stringName}"`, childNode);
54
+ }
55
+ else {
56
+ if (locFile.hasOwnProperty(stringName)) {
57
+ _logErrorWithLocation(options, `Duplicate string value "${stringName}"`, childNode);
58
+ }
59
+ const locString = _readDataElement(options, childNode);
60
+ if (locString) {
61
+ locFile[stringName] = locString;
62
+ }
63
+ }
64
+ break;
65
+ }
66
+ // Other allowed elements
67
+ case 'xsd:schema':
68
+ case 'resheader':
69
+ break;
70
+ default:
71
+ _logErrorWithLocation(options, `Unexpected RESX element ${childNode.name}`, childNode);
72
+ }
73
+ break;
74
+ }
75
+ case 'text': {
76
+ if (childNode.text.trim() !== '') {
77
+ _logErrorWithLocation(options, 'Found unexpected non-empty text node in RESX');
78
+ }
79
+ break;
80
+ }
81
+ case 'comment':
82
+ break;
83
+ default:
84
+ _logErrorWithLocation(options, `Unexpected ${childNode.type} child in RESX`);
85
+ break;
86
+ }
87
+ }
88
+ return locFile;
89
+ }
90
+ function _readDataElement(options, dataElement) {
91
+ let foundCommentElement = false;
92
+ let foundValueElement = false;
93
+ let comment = undefined;
94
+ let value = undefined;
95
+ for (const childNode of dataElement.children) {
96
+ switch (childNode.type) {
97
+ case 'element': {
98
+ switch (childNode.name) {
99
+ case 'value': {
100
+ if (foundValueElement) {
101
+ _logErrorWithLocation(options, 'Duplicate <value> element found', childNode);
102
+ }
103
+ else {
104
+ foundValueElement = true;
105
+ value = _readTextElement(options, childNode);
106
+ if (value && options.newlineNormalization) {
107
+ value = node_core_library_1.Text.convertTo(value, options.newlineNormalization);
108
+ }
109
+ }
110
+ break;
111
+ }
112
+ case 'comment': {
113
+ if (foundCommentElement) {
114
+ _logErrorWithLocation(options, 'Duplicate <comment> element found', childNode);
115
+ }
116
+ else {
117
+ foundCommentElement = true;
118
+ comment = _readTextElement(options, childNode);
119
+ }
120
+ break;
121
+ }
122
+ default:
123
+ _logErrorWithLocation(options, `Unexpected RESX element ${childNode.name}`, childNode);
124
+ break;
125
+ }
126
+ break;
127
+ }
128
+ case 'text': {
129
+ if (childNode.text.trim() !== '') {
130
+ _logErrorWithLocation(options, 'Found unexpected non-empty text node in RESX <data> element', dataElement);
131
+ }
132
+ break;
133
+ }
134
+ case 'comment':
135
+ break;
136
+ default:
137
+ _logErrorWithLocation(options, `Unexpected ${childNode.type} child in RESX <data> element`, dataElement);
138
+ }
139
+ }
140
+ if (!foundValueElement) {
141
+ _logErrorWithLocation(options, 'Missing string value in <data> element', dataElement);
142
+ }
143
+ else {
144
+ if (comment === undefined && options.warnOnMissingComment) {
145
+ _logWarningWithLocation(options, 'Missing string comment in <data> element', dataElement);
146
+ }
147
+ return {
148
+ value: value || '',
149
+ comment
150
+ };
151
+ }
152
+ }
153
+ function _readTextElement(options, element) {
154
+ let foundText = undefined;
155
+ for (const childNode of element.children) {
156
+ switch (childNode.type) {
157
+ case 'cdata':
158
+ case 'text': {
159
+ if (foundText !== undefined) {
160
+ _logErrorWithLocation(options, 'More than one child node found containing text content', element);
161
+ break;
162
+ }
163
+ foundText = childNode.type === 'text' ? childNode.text : childNode.cdata;
164
+ break;
165
+ }
166
+ case 'comment':
167
+ break;
168
+ case 'element':
169
+ _logErrorWithLocation(options, `Unexpected element`, childNode);
170
+ break;
171
+ default:
172
+ _logErrorWithLocation(options, `Unexpected ${element.type} child`, element);
173
+ break;
174
+ }
175
+ }
176
+ return foundText;
177
+ }
178
+ function _logErrorWithLocation(options, message, element) {
179
+ if (element) {
180
+ options.loggingFunctions.logFileError(message, options.resxFilePath, element.line + 1, element.column + 1);
181
+ }
182
+ else {
183
+ options.loggingFunctions.logFileError(message, options.resxFilePath);
184
+ }
185
+ }
186
+ function _logWarningWithLocation(options, message, element) {
187
+ if (element) {
188
+ options.loggingFunctions.logFileWarning(message, options.resxFilePath, element.line + 1, element.column + 1);
189
+ }
190
+ else {
191
+ options.loggingFunctions.logFileWarning(message, options.resxFilePath);
192
+ }
193
+ }
194
+ function _logWithLocation(loggingFn, message, filePath, line, position) {
195
+ let location;
196
+ if (position !== undefined) {
197
+ location = `${filePath}(${line},${position})`;
198
+ }
199
+ else if (line !== undefined) {
200
+ location = `${filePath}(${line})`;
201
+ }
202
+ else {
203
+ location = filePath;
204
+ }
205
+ loggingFn(`${location}: ${message}`);
206
+ }
207
+ //# sourceMappingURL=ResxReader.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"ResxReader.js","sourceRoot":"","sources":["../src/ResxReader.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 { FileSystem, ITerminal, Text, NewlineKind } from '@rushstack/node-core-library';\nimport { XmlDocument, XmlElement } from 'xmldoc';\n\nimport { ILocalizedString, ILocalizationFile } from './interfaces';\n\nconst STRING_NAME_RESX: RegExp = /^[A-z_$][A-z0-9_$]*$/;\n\n/**\n * @public\n */\nexport interface IResxReaderOptions {\n resxFilePath: string;\n terminal: ITerminal;\n newlineNormalization: NewlineKind | undefined;\n warnOnMissingComment: boolean;\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<IResxReaderOptions, 'terminal'> {\n resxContents: string;\n loggingFunctions: ILoggingFunctions;\n}\n\n/**\n * @public\n */\nexport function readResxFileAsLocFile(options: IResxReaderOptions): ILocalizationFile {\n const resxContents: string = FileSystem.readFile(options.resxFilePath);\n return readResxAsLocFile(resxContents, options);\n}\n\n/**\n * @public\n */\nexport function readResxAsLocFile(resxContents: string, options: IResxReaderOptions): 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 resxContents,\n loggingFunctions\n });\n}\n\nfunction _readResxAsLocFileInternal(options: IResxReaderOptionsInternal): ILocalizationFile {\n const xmlDocument: XmlDocument = new XmlDocument(options.resxContents);\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 if (!STRING_NAME_RESX.test(stringName)) {\n _logErrorWithLocation(options, `Invalid string name \"${stringName}\"`, 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) {\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.newlineNormalization) {\n value = Text.convertTo(value, options.newlineNormalization);\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.warnOnMissingComment) {\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(\n message,\n options.resxFilePath,\n element.line + 1,\n element.column + 1\n );\n } else {\n options.loggingFunctions.logFileError(message, options.resxFilePath);\n }\n}\n\nfunction _logWarningWithLocation(\n options: IResxReaderOptionsInternal,\n message: string,\n element?: XmlElement | XmlDocument\n): void {\n if (element) {\n options.loggingFunctions.logFileWarning(\n message,\n options.resxFilePath,\n element.line + 1,\n element.column + 1\n );\n } else {\n options.loggingFunctions.logFileWarning(message, options.resxFilePath);\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"]}
package/lib/index.d.ts ADDED
@@ -0,0 +1,6 @@
1
+ export { ILocalizationFile, ILocalizedString, IPseudolocaleOptions } from './interfaces';
2
+ export { parseLocFile, IParseLocFileOptions } from './LocFileParser';
3
+ export { ITypingsGeneratorOptions, LocFileTypingsGenerator as TypingsGenerator } from './LocFileTypingsGenerator';
4
+ export { readResxFileAsLocFile, readResxAsLocFile, IResxReaderOptions } from './ResxReader';
5
+ export { getPseudolocalizer } from './Pseudolocalization';
6
+ //# sourceMappingURL=index.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAGA,OAAO,EAAE,iBAAiB,EAAE,gBAAgB,EAAE,oBAAoB,EAAE,MAAM,cAAc,CAAC;AACzF,OAAO,EAAE,YAAY,EAAE,oBAAoB,EAAE,MAAM,iBAAiB,CAAC;AACrE,OAAO,EACL,wBAAwB,EACxB,uBAAuB,IAAI,gBAAgB,EAC5C,MAAM,2BAA2B,CAAC;AACnC,OAAO,EAAE,qBAAqB,EAAE,iBAAiB,EAAE,kBAAkB,EAAE,MAAM,cAAc,CAAC;AAC5F,OAAO,EAAE,kBAAkB,EAAE,MAAM,sBAAsB,CAAC"}
package/lib/index.js ADDED
@@ -0,0 +1,15 @@
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
+ Object.defineProperty(exports, "__esModule", { value: true });
5
+ exports.getPseudolocalizer = exports.readResxAsLocFile = exports.readResxFileAsLocFile = exports.TypingsGenerator = exports.parseLocFile = void 0;
6
+ var LocFileParser_1 = require("./LocFileParser");
7
+ Object.defineProperty(exports, "parseLocFile", { enumerable: true, get: function () { return LocFileParser_1.parseLocFile; } });
8
+ var LocFileTypingsGenerator_1 = require("./LocFileTypingsGenerator");
9
+ Object.defineProperty(exports, "TypingsGenerator", { enumerable: true, get: function () { return LocFileTypingsGenerator_1.LocFileTypingsGenerator; } });
10
+ var ResxReader_1 = require("./ResxReader");
11
+ Object.defineProperty(exports, "readResxFileAsLocFile", { enumerable: true, get: function () { return ResxReader_1.readResxFileAsLocFile; } });
12
+ Object.defineProperty(exports, "readResxAsLocFile", { enumerable: true, get: function () { return ResxReader_1.readResxAsLocFile; } });
13
+ var Pseudolocalization_1 = require("./Pseudolocalization");
14
+ Object.defineProperty(exports, "getPseudolocalizer", { enumerable: true, get: function () { return Pseudolocalization_1.getPseudolocalizer; } });
15
+ //# sourceMappingURL=index.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.js","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":";AAAA,4FAA4F;AAC5F,2DAA2D;;;AAG3D,iDAAqE;AAA5D,6GAAA,YAAY,OAAA;AACrB,qEAGmC;AADjC,2HAAA,uBAAuB,OAAoB;AAE7C,2CAA4F;AAAnF,mHAAA,qBAAqB,OAAA;AAAE,+GAAA,iBAAiB,OAAA;AACjD,2DAA0D;AAAjD,wHAAA,kBAAkB,OAAA","sourcesContent":["// Copyright (c) Microsoft Corporation. All rights reserved. Licensed under the MIT license.\n// See LICENSE in the project root for license information.\n\nexport { ILocalizationFile, ILocalizedString, IPseudolocaleOptions } from './interfaces';\nexport { parseLocFile, IParseLocFileOptions } from './LocFileParser';\nexport {\n ITypingsGeneratorOptions,\n LocFileTypingsGenerator as TypingsGenerator\n} from './LocFileTypingsGenerator';\nexport { readResxFileAsLocFile, readResxAsLocFile, IResxReaderOptions } from './ResxReader';\nexport { getPseudolocalizer } from './Pseudolocalization';\n"]}
@@ -0,0 +1,31 @@
1
+ /**
2
+ * Options for the pseudolocale library.
3
+ *
4
+ * @internalRemarks
5
+ * Eventually this should be replaced with DefinitelyTyped types.
6
+ *
7
+ * @public
8
+ */
9
+ export interface IPseudolocaleOptions {
10
+ prepend?: string;
11
+ append?: string;
12
+ delimiter?: string;
13
+ startDelimiter?: string;
14
+ endDelimiter?: string;
15
+ extend?: number;
16
+ override?: string;
17
+ }
18
+ /**
19
+ * @public
20
+ */
21
+ export interface ILocalizationFile {
22
+ [stringName: string]: ILocalizedString;
23
+ }
24
+ /**
25
+ * @public
26
+ */
27
+ export interface ILocalizedString {
28
+ value: string;
29
+ comment?: string;
30
+ }
31
+ //# sourceMappingURL=interfaces.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"interfaces.d.ts","sourceRoot":"","sources":["../src/interfaces.ts"],"names":[],"mappings":"AAGA;;;;;;;GAOG;AACH,MAAM,WAAW,oBAAoB;IACnC,OAAO,CAAC,EAAE,MAAM,CAAC;IACjB,MAAM,CAAC,EAAE,MAAM,CAAC;IAChB,SAAS,CAAC,EAAE,MAAM,CAAC;IACnB,cAAc,CAAC,EAAE,MAAM,CAAC;IACxB,YAAY,CAAC,EAAE,MAAM,CAAC;IACtB,MAAM,CAAC,EAAE,MAAM,CAAC;IAChB,QAAQ,CAAC,EAAE,MAAM,CAAC;CACnB;AAED;;GAEG;AACH,MAAM,WAAW,iBAAiB;IAChC,CAAC,UAAU,EAAE,MAAM,GAAG,gBAAgB,CAAC;CACxC;AAED;;GAEG;AACH,MAAM,WAAW,gBAAgB;IAC/B,KAAK,EAAE,MAAM,CAAC;IACd,OAAO,CAAC,EAAE,MAAM,CAAC;CAClB"}
@@ -0,0 +1,5 @@
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
+ Object.defineProperty(exports, "__esModule", { value: true });
5
+ //# 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"]}
@@ -0,0 +1,28 @@
1
+ {
2
+ "$schema": "http://json-schema.org/draft-04/schema#",
3
+ "title": "Localizable JSON file",
4
+
5
+ "properties": {
6
+ "$schema": {
7
+ "description": "Part of the JSON Schema standard, this optional keyword declares the URL of the schema that the file conforms to. Editors may download the schema and use it to perform syntax highlighting.",
8
+ "type": "string"
9
+ }
10
+ },
11
+ "patternProperties": {
12
+ "^[A-Za-z_][0-9A-Za-z_]*$": {
13
+ "type": "object",
14
+ "properties": {
15
+ "value": {
16
+ "type": "string"
17
+ },
18
+ "comment": {
19
+ "type": "string"
20
+ }
21
+ },
22
+ "additionalProperties": false,
23
+ "required": ["value"]
24
+ }
25
+ },
26
+ "additionalProperties": false,
27
+ "type": "object"
28
+ }
package/package.json ADDED
@@ -0,0 +1,32 @@
1
+ {
2
+ "name": "@rushstack/localization-utilities",
3
+ "version": "0.1.0",
4
+ "description": "This plugin contains some useful functions for localization.",
5
+ "main": "lib/index.js",
6
+ "typings": "dist/localization-utilities.d.ts",
7
+ "license": "MIT",
8
+ "repository": {
9
+ "type": "git",
10
+ "url": "https://github.com/microsoft/rushstack.git",
11
+ "directory": "libraries/localization-utilities"
12
+ },
13
+ "dependencies": {
14
+ "@rushstack/node-core-library": "3.45.5",
15
+ "@rushstack/typings-generator": "0.6.25",
16
+ "decache": "~4.5.1",
17
+ "pseudolocale": "~1.1.0",
18
+ "xmldoc": "~1.1.2"
19
+ },
20
+ "devDependencies": {
21
+ "@rushstack/eslint-config": "2.6.0",
22
+ "@rushstack/heft": "0.45.5",
23
+ "@rushstack/heft-node-rig": "1.9.6",
24
+ "@types/node": "12.20.24",
25
+ "@types/xmldoc": "1.1.4"
26
+ },
27
+ "scripts": {
28
+ "build": "heft build --clean",
29
+ "_phase:build": "heft build --clean"
30
+ },
31
+ "readme": "# @rushstack/localization-utilities\n\nThis library provides a set of utilities for working with localization files.\n"
32
+ }