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