185 строки
6.4 KiB
JavaScript
185 строки
6.4 KiB
JavaScript
import {
|
|
XMLParser,
|
|
} from "fast-xml-parser";
|
|
import fs from "fs";
|
|
import path from "path";
|
|
|
|
/** @typedef {{
|
|
LCX: {
|
|
$_TgtCul: string;
|
|
Item: {
|
|
Item: {
|
|
Item: {
|
|
$_ItemId: string;
|
|
Str: {
|
|
Val: string;
|
|
Tgt: {
|
|
Val: string;
|
|
};
|
|
};
|
|
}[];
|
|
};
|
|
};
|
|
}
|
|
}} ParsedLCL */
|
|
void 0;
|
|
|
|
async function main() {
|
|
const args = process.argv.slice(2);
|
|
if (args.length !== 3) {
|
|
console.log("Usage:");
|
|
console.log("\tnode generateLocalizedDiagnosticMessages.js <lcl source directory> <output directory> <generated diagnostics map file>");
|
|
return;
|
|
}
|
|
|
|
const inputPath = args[0];
|
|
const outputPath = args[1];
|
|
const diagnosticsMapFilePath = args[2];
|
|
|
|
// generate the lcg file for enu
|
|
await generateLCGFile();
|
|
|
|
// generate other langs
|
|
const files = await fs.promises.readdir(inputPath);
|
|
await Promise.all(files.map(visitDirectory));
|
|
|
|
return;
|
|
|
|
/**
|
|
* @param {string} name
|
|
*/
|
|
async function visitDirectory(name) {
|
|
const inputFilePath = path.join(inputPath, name, "diagnosticMessages", "diagnosticMessages.generated.json.lcl");
|
|
const contents = await fs.promises.readFile(inputFilePath);
|
|
/** @type {ParsedLCL} */
|
|
const result = new XMLParser({ ignoreAttributes: false, attributeNamePrefix: "$_" }).parse(contents);
|
|
if (!result || !result.LCX || !result.LCX.$_TgtCul) {
|
|
console.error("Unexpected XML file structure. Expected to find result.LCX.$_TgtCul.");
|
|
process.exit(1);
|
|
}
|
|
const outputDirectoryName = getPreferredLocaleName(result.LCX.$_TgtCul).toLowerCase();
|
|
if (!outputDirectoryName) {
|
|
console.error(`Invalid output locale name for '${result.LCX.$_TgtCul}'.`);
|
|
process.exit(1);
|
|
}
|
|
await writeFile(path.join(outputPath, outputDirectoryName, "diagnosticMessages.generated.json"), xmlObjectToString(result));
|
|
}
|
|
|
|
/**
|
|
* A locale name is based on the language tagging conventions of RFC 4646 (Windows Vista
|
|
* and later), and is represented by LOCALE_SNAME.
|
|
* Generally, the pattern <language>-<REGION> is used. Here, language is a lowercase ISO 639
|
|
* language code. The codes from ISO 639-1 are used when available. Otherwise, codes from
|
|
* ISO 639-2/T are used. REGION specifies an uppercase ISO 3166-1 country/region identifier.
|
|
* For example, the locale name for English (United States) is "en-US" and the locale name
|
|
* for Divehi (Maldives) is "dv-MV".
|
|
*
|
|
* If the locale is a neutral locale (no region), the LOCALE_SNAME value follows the
|
|
* pattern <language>. If it is a neutral locale for which the script is significant, the
|
|
* pattern is <language>-<Script>.
|
|
*
|
|
* More at https://msdn.microsoft.com/en-us/library/windows/desktop/dd373814(v=vs.85).aspx
|
|
*
|
|
* Most of the languages we support are neutral locales, so we want to use the language name.
|
|
* There are three exceptions, zh-CN, zh-TW and pt-BR.
|
|
*
|
|
* @param {string} localeName
|
|
*/
|
|
function getPreferredLocaleName(localeName) {
|
|
switch (localeName) {
|
|
case "zh-CN":
|
|
case "zh-TW":
|
|
case "pt-BR":
|
|
return localeName;
|
|
default:
|
|
return localeName.split("-")[0];
|
|
}
|
|
}
|
|
|
|
/**
|
|
* @param {ParsedLCL} o
|
|
*/
|
|
function xmlObjectToString(o) {
|
|
/** @type {any} */
|
|
const out = {};
|
|
for (const item of o.LCX.Item.Item.Item) {
|
|
let ItemId = item.$_ItemId;
|
|
let val = item.Str.Tgt ? item.Str.Tgt.Val : item.Str.Val;
|
|
|
|
if (typeof ItemId !== "string" || typeof val !== "string") {
|
|
console.error("Unexpected XML file structure");
|
|
process.exit(1);
|
|
}
|
|
|
|
if (ItemId.charAt(0) === ";") {
|
|
ItemId = ItemId.slice(1); // remove leading semicolon
|
|
}
|
|
|
|
val = val.replace(/]5D;/, "]"); // unescape `]`
|
|
out[ItemId] = val;
|
|
}
|
|
return JSON.stringify(out, undefined, 2);
|
|
}
|
|
|
|
/**
|
|
* @param {string} fileName
|
|
* @param {string} contents
|
|
*/
|
|
async function writeFile(fileName, contents) {
|
|
await fs.promises.mkdir(path.dirname(fileName), { recursive: true });
|
|
await fs.promises.writeFile(fileName, contents);
|
|
}
|
|
|
|
async function generateLCGFile() {
|
|
const contents = await fs.promises.readFile(diagnosticsMapFilePath, "utf-8");
|
|
await writeFile(
|
|
path.join(outputPath, "enu", "diagnosticMessages.generated.json.lcg"),
|
|
getLCGFileXML(
|
|
Object.entries(JSON.parse(contents))
|
|
.sort((a, b) => a[0] > b[0] ? 1 : -1) // lcg sorted by property keys
|
|
.reduce((s, [key, value]) => s + getItemXML(key, value), ""),
|
|
),
|
|
);
|
|
return;
|
|
|
|
/**
|
|
* @param {string} key
|
|
* @param {string} value
|
|
*/
|
|
function getItemXML(key, value) {
|
|
// escape entrt value
|
|
value = value.replace(/]/g, "]5D;");
|
|
|
|
return `
|
|
<Item ItemId=";${key}" ItemType="0" PsrId="306" Leaf="true">
|
|
<Str Cat="Text">
|
|
<Val><![CDATA[${value}]]></Val>
|
|
</Str>
|
|
<Disp Icon="Str" />
|
|
</Item>`;
|
|
}
|
|
|
|
/**
|
|
* @param {string} items
|
|
*/
|
|
function getLCGFileXML(items) {
|
|
return `<?xml version="1.0" encoding="utf-8"?>
|
|
<LCX SchemaVersion="6.0" Name="diagnosticMessages.generated.json" PsrId="306" FileType="1" SrcCul="en-US" xmlns="http://schemas.microsoft.com/locstudio/2006/6/lcx">
|
|
<OwnedComments>
|
|
<Cmt Name="Dev" />
|
|
<Cmt Name="LcxAdmin" />
|
|
<Cmt Name="Rccx" />
|
|
</OwnedComments>
|
|
<Item ItemId=";String Table" ItemType="0" PsrId="306" Leaf="false">
|
|
<Disp Icon="Expand" Expand="true" Disp="true" LocTbl="false" />
|
|
<Item ItemId=";Strings" ItemType="0" PsrId="306" Leaf="false">
|
|
<Disp Icon="Str" Disp="true" LocTbl="false" />${items}
|
|
</Item>
|
|
</Item>
|
|
</LCX>`;
|
|
}
|
|
}
|
|
}
|
|
|
|
await main();
|