• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1import * as fs from "fs";
2import * as path from "path";
3import * as xml2js from "xml2js";
4
5function main(): void {
6    const args = process.argv.slice(2);
7    if (args.length !== 3) {
8        console.log("Usage:");
9        console.log("\tnode generateLocalizedDiagnosticMessages.js <lcl source directory> <output directory> <generated diagnostics map file>");
10        return;
11    }
12
13    const inputPath = args[0];
14    const outputPath = args[1];
15    const diagnosticsMapFilePath = args[2];
16
17    // generate the lcg file for enu
18    generateLCGFile();
19
20    // generate other langs
21    fs.readdir(inputPath, (err, files) => {
22        handleError(err);
23        files.forEach(visitDirectory);
24    });
25
26    return;
27
28    function visitDirectory(name: string) {
29        const inputFilePath = path.join(inputPath, name, "diagnosticMessages", "diagnosticMessages.generated.json.lcl");
30
31        fs.readFile(inputFilePath, (err, data) => {
32            handleError(err);
33            xml2js.parseString(data.toString(), (err, result) => {
34                handleError(err);
35                if (!result || !result.LCX || !result.LCX.$ || !result.LCX.$.TgtCul) {
36                    console.error("Unexpected XML file structure. Expected to find result.LCX.$.TgtCul.");
37                    process.exit(1);
38                }
39                const outputDirectoryName = getPreferredLocaleName(result.LCX.$.TgtCul).toLowerCase();
40                if (!outputDirectoryName) {
41                    console.error(`Invalid output locale name for '${result.LCX.$.TgtCul}'.`);
42                    process.exit(1);
43                }
44                writeFile(path.join(outputPath, outputDirectoryName, "diagnosticMessages.generated.json"), xmlObjectToString(result));
45            });
46        });
47    }
48
49    /**
50     * A locale name is based on the language tagging conventions of RFC 4646 (Windows Vista
51     * and later), and is represented by LOCALE_SNAME.
52     * Generally, the pattern <language>-<REGION> is used. Here, language is a lowercase ISO 639
53     * language code. The codes from ISO 639-1 are used when available. Otherwise, codes from
54     * ISO 639-2/T are used. REGION specifies an uppercase ISO 3166-1 country/region identifier.
55     * For example, the locale name for English (United States) is "en-US" and the locale name
56     * for Divehi (Maldives) is "dv-MV".
57     *
58     * If the locale is a neutral locale (no region), the LOCALE_SNAME value follows the
59     * pattern <language>. If it is a neutral locale for which the script is significant, the
60     * pattern is <language>-<Script>.
61     *
62     * More at https://msdn.microsoft.com/en-us/library/windows/desktop/dd373814(v=vs.85).aspx
63     *
64     * Most of the languages we support are neutral locales, so we want to use the language name.
65     * There are three exceptions, zh-CN, zh-TW and pt-BR.
66     */
67    function getPreferredLocaleName(localeName: string) {
68        switch (localeName) {
69            case "zh-CN":
70            case "zh-TW":
71            case "pt-BR":
72                return localeName;
73            default:
74                return localeName.split("-")[0];
75        }
76    }
77
78    function handleError(err: null | object) {
79        if (err) {
80            console.error(err);
81            process.exit(1);
82        }
83    }
84
85    function xmlObjectToString(o: any) {
86        const out: any = {};
87        for (const item of o.LCX.Item[0].Item[0].Item) {
88            let ItemId = item.$.ItemId;
89            let val = item.Str[0].Tgt ? item.Str[0].Tgt[0].Val[0] : item.Str[0].Val[0];
90
91            if (typeof ItemId !== "string" || typeof val !== "string") {
92                console.error("Unexpected XML file structure");
93                process.exit(1);
94            }
95
96            if (ItemId.charAt(0) === ";") {
97                ItemId = ItemId.slice(1); // remove leading semicolon
98            }
99
100            val = val.replace(/]5D;/, "]"); // unescape `]`
101            out[ItemId] = val;
102        }
103        return JSON.stringify(out, undefined, 2);
104    }
105
106
107    function ensureDirectoryExists(directoryPath: string, action: () => void) {
108        fs.exists(directoryPath, exists => {
109            if (!exists) {
110                const basePath = path.dirname(directoryPath);
111                if (basePath !== directoryPath) {
112                    return ensureDirectoryExists(basePath, () => fs.mkdir(directoryPath, action));
113                }
114            }
115            action();
116        });
117    }
118
119    function writeFile(fileName: string, contents: string) {
120        ensureDirectoryExists(path.dirname(fileName), () => {
121            fs.writeFile(fileName, contents, handleError);
122        });
123    }
124
125    function objectToList(o: Record<string, string>) {
126        const list: { key: string, value: string }[] = [];
127        for (const key in o) {
128            list.push({ key, value: o[key] });
129        }
130        return list;
131    }
132
133    function generateLCGFile() {
134        return fs.readFile(diagnosticsMapFilePath, (err, data) => {
135            handleError(err);
136            writeFile(
137                path.join(outputPath, "enu", "diagnosticMessages.generated.json.lcg"),
138                getLCGFileXML(
139                    objectToList(JSON.parse(data.toString()))
140                        .sort((a, b) => a.key > b.key ? 1 : -1)  // lcg sorted by property keys
141                        .reduce((s, { key, value }) => s + getItemXML(key, value), "")
142                ));
143        });
144
145        function getItemXML(key: string, value: string) {
146            // escape entrt value
147            value = value.replace(/]/, "]5D;");
148
149            return `
150            <Item ItemId=";${key}" ItemType="0" PsrId="306" Leaf="true">
151              <Str Cat="Text">
152                <Val><![CDATA[${value}]]></Val>
153              </Str>
154              <Disp Icon="Str" />
155            </Item>`;
156        }
157
158        function getLCGFileXML(items: string) {
159            return `<?xml version="1.0" encoding="utf-8"?>
160<LCX SchemaVersion="6.0" Name="diagnosticMessages.generated.json" PsrId="306" FileType="1" SrcCul="en-US" xmlns="http://schemas.microsoft.com/locstudio/2006/6/lcx">
161  <OwnedComments>
162    <Cmt Name="Dev" />
163    <Cmt Name="LcxAdmin" />
164    <Cmt Name="Rccx" />
165  </OwnedComments>
166  <Item ItemId=";String Table" ItemType="0" PsrId="306" Leaf="false">
167    <Disp Icon="Expand" Expand="true" Disp="true" LocTbl="false" />
168    <Item ItemId=";Strings" ItemType="0" PsrId="306" Leaf="false">
169      <Disp Icon="Str" Disp="true" LocTbl="false" />${items}
170    </Item>
171  </Item>
172</LCX>`;
173        }
174    }
175}
176
177main();
178