Skip to content

Commit 3e50998

Browse files
committed
Add RESX support for the loc file preprocessor.
1 parent 8ec919b commit 3e50998

2 files changed

Lines changed: 129 additions & 47 deletions

File tree

common/reviews/api/localization-plugin.api.md

Lines changed: 14 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,7 @@
44
55
```ts
66

7+
import { Terminal } from '@microsoft/node-core-library';
78
import * as Webpack from 'webpack';
89

910
// @public (undocumented)
@@ -72,19 +73,21 @@ export interface ILocalizationStatsEntrypoint {
7273
}
7374

7475
// @public (undocumented)
75-
export interface ILocJsonFileData {
76-
// (undocumented)
77-
[stringName: string]: string;
78-
}
79-
80-
// @public (undocumented)
81-
export interface ILocJsonPreprocessorOptions {
76+
export interface ILocFilePreprocessorOptions {
8277
// (undocumented)
8378
filesToIgnore?: string[];
8479
// (undocumented)
8580
generatedTsFolder: string;
8681
// (undocumented)
8782
srcFolder: string;
83+
// (undocumented)
84+
terminal: Terminal;
85+
}
86+
87+
// @public (undocumented)
88+
export interface ILocJsonFileData {
89+
// (undocumented)
90+
[stringName: string]: string;
8891
}
8992

9093
// @internal (undocumented)
@@ -105,10 +108,11 @@ export class LocalizationPlugin implements Webpack.Plugin {
105108
}
106109

107110
// @public
108-
export class LocJsonPreprocessor {
111+
export class LocFilePreprocessor {
112+
constructor(options: ILocFilePreprocessorOptions);
109113
// (undocumented)
110-
static preprocessLocJsonFiles(options: ILocJsonPreprocessorOptions): void;
111-
}
114+
generateTypings(): void;
115+
}
112116

113117

114118
// (No @packageDocumentation comment for this package)

webpack/localization-plugin/src/LocJsonPreprocessor.ts

Lines changed: 115 additions & 37 deletions
Original file line numberDiff line numberDiff line change
@@ -4,89 +4,167 @@
44
import {
55
FileSystem,
66
JsonFile,
7-
JsonSchema
7+
JsonSchema,
8+
Terminal,
9+
ConsoleTerminalProvider
810
} from '@microsoft/node-core-library';
911
import * as glob from 'glob';
1012
import * as path from 'path';
1113
import { EOL } from 'os';
1214

13-
import { ILocJsonFile } from './interfaces';
15+
import { ILocFile } from './interfaces';
16+
import { ResxReader, ILogger } from './utilities/ResxReader';
1417

1518
/**
1619
* @public
1720
*/
18-
export interface ILocJsonPreprocessorOptions {
21+
export interface ILocFilePreprocessorOptions {
1922
srcFolder: string;
2023
generatedTsFolder: string;
24+
terminal: Terminal;
2125
filesToIgnore?: string[];
2226
}
2327

2428
/**
25-
* This is a simple tool that generates .d.ts files for .loc.json files.
29+
* This is a simple tool that generates .d.ts files for .loc.json and .resx files.
2630
*
2731
* @public
2832
*/
29-
export class LocJsonPreprocessor {
30-
public static preprocessLocJsonFiles(options: ILocJsonPreprocessorOptions): void {
31-
const {
32-
srcFolder,
33-
generatedTsFolder
34-
}: ILocJsonPreprocessorOptions = options;
35-
36-
const filesToIgnore: Set<string> = new Set<string>((options.filesToIgnore || []).map((fileToIgnore) => {
33+
export class LocFilePreprocessor {
34+
private _options: ILocFilePreprocessorOptions;
35+
private _loggingOptions: ILogger;
36+
37+
public constructor(options: ILocFilePreprocessorOptions) {
38+
this._options = {
39+
filesToIgnore: [],
40+
...options
41+
};
42+
43+
if (this._options.terminal) {
44+
this._options.terminal = new Terminal(new ConsoleTerminalProvider({ verboseEnabled: true }));
45+
}
46+
47+
function logWithLocation(
48+
loggingFn: typeof Terminal.prototype.writeErrorLine,
49+
message: string,
50+
filePath: string,
51+
line?: number,
52+
position?: number
53+
): void {
54+
let location: string;
55+
if (position !== undefined) {
56+
location = `${filePath}(${line},${position})`;
57+
} else if (line !== undefined) {
58+
location = `${filePath}(${line})`;
59+
} else {
60+
location = filePath;
61+
}
62+
63+
loggingFn(`${location}: ${message}`);
64+
}
65+
66+
this._loggingOptions = {
67+
logError: (message: string) => this._options.terminal.writeErrorLine(message),
68+
logWarning: (message: string) => this._options.terminal.writeWarningLine(message),
69+
logFileError: (message: string, filePath: string, line?: number, position?: number) => {
70+
logWithLocation(
71+
this._options.terminal.writeErrorLine.bind(this._options.terminal),
72+
message,
73+
filePath,
74+
line,
75+
position
76+
);
77+
},
78+
logFileWarning: (message: string, filePath: string, line?: number, position?: number) => {
79+
logWithLocation(
80+
this._options.terminal.writeWarningLine.bind(this._options.terminal),
81+
message,
82+
filePath,
83+
line,
84+
position
85+
);
86+
}
87+
}
88+
}
89+
90+
public generateTypings(): void {
91+
FileSystem.ensureEmptyFolder(this._options.generatedTsFolder);
92+
93+
const filesToIgnore: Set<string> = new Set<string>((this._options.filesToIgnore!).map((fileToIgnore) => {
3794
if (path.isAbsolute(fileToIgnore)) {
3895
return fileToIgnore;
3996
} else {
40-
return path.resolve(srcFolder, fileToIgnore);
97+
return path.resolve(this._options.srcFolder, fileToIgnore);
4198
}
4299
}));
43100

44-
FileSystem.ensureEmptyFolder(generatedTsFolder);
45101
const locJsonFiles: string[] = glob.sync(
46102
path.join('**', '*.loc.json'),
47103
{
48-
root: srcFolder,
104+
root: this._options.srcFolder,
49105
absolute: true
50106
}
51107
);
52108

53109
const locJsonSchema: JsonSchema = JsonSchema.fromFile(path.resolve(__dirname, 'schemas', 'locJson.schema.json'));
54-
55110
for (let locJsonFilePath of locJsonFiles) {
56111
locJsonFilePath = path.resolve(locJsonFilePath);
57112

58113
if (filesToIgnore.has(locJsonFilePath)) {
59114
continue;
60115
}
61116

62-
const outputLines: string[] = [
63-
'// This file was generated by a tool. Modifying it will produce unexpected behavior',
64-
''
65-
];
117+
const locFileData: ILocFile = JsonFile.loadAndValidate(locJsonFilePath, locJsonSchema);
118+
this._generateTypingsForLocFile(locJsonFilePath, locFileData);
119+
}
120+
121+
const resxFiles: string[] = glob.sync(
122+
path.join('**', '*.resx'),
123+
{
124+
root: this._options.srcFolder,
125+
absolute: true
126+
}
127+
);
128+
129+
for (let resxFilePath of resxFiles) {
130+
resxFilePath = path.resolve(resxFilePath);
131+
132+
if (filesToIgnore.has(resxFilePath)) {
133+
continue;
134+
}
135+
136+
const locFileData: ILocFile = ResxReader.readResxFileAsLocFile({ ...this._loggingOptions, resxFilePath });
137+
this._generateTypingsForLocFile(resxFilePath, locFileData);
138+
}
139+
}
66140

67-
const locJsonFile: ILocJsonFile = JsonFile.loadAndValidate(locJsonFilePath, locJsonSchema);
68-
for (const stringName in locJsonFile) { // eslint-disable-line guard-for-in
69-
const { comment } = locJsonFile[stringName];
141+
private _generateTypingsForLocFile(locFilePath: string, locFileData: ILocFile): void {
142+
const outputLines: string[] = [
143+
'// This file was generated by a tool. Modifying it will produce unexpected behavior',
144+
''
145+
];
70146

71-
if (comment.trim()) {
72-
outputLines.push(...[
73-
'/**',
74-
` * ${comment.replace(/\*\//g, '*\\/')}`,
75-
' */'
76-
]);
77-
}
147+
for (const stringName in locFileData) { // eslint-disable-line guard-for-in
148+
const { comment } = locFileData[stringName];
78149

150+
if (comment.trim()) {
79151
outputLines.push(...[
80-
`export declare const ${stringName}: string;`,
81-
''
152+
'/**',
153+
` * ${comment.replace(/\*\//g, '*\\/')}`,
154+
' */'
82155
]);
83156
}
84157

85-
const generatedTsFilePath: string = (
86-
`${path.resolve(generatedTsFolder, path.relative(srcFolder, locJsonFilePath))}.d.ts`
87-
);
88-
FileSystem.ensureFolder(path.dirname(generatedTsFilePath));
89-
FileSystem.writeFile(generatedTsFilePath, outputLines.join(EOL));
158+
outputLines.push(...[
159+
`export declare const ${stringName}: string;`,
160+
''
161+
]);
90162
}
163+
164+
const generatedTsFilePath: string = path.resolve(
165+
this._options.generatedTsFolder,
166+
path.relative(this._options.srcFolder, `${locFilePath}.d.ts`)
167+
);
168+
FileSystem.writeFile(generatedTsFilePath, outputLines.join(EOL), { ensureFolderExists: true });
91169
}
92170
}

0 commit comments

Comments
 (0)