Modernize localize script, use new XML library

This file is pretty much the same as it was when it was committed in
2017; these days, we can write clearer code with async/await and new FS
APIs.

Additionally, we can improve the performance of this script by using a
newer/faster/maintained XML library. This will enable us to run the
script unconditionally in a later commit.
This commit is contained in:
Jake Bailey
2022-10-09 20:12:45 -07:00
parent 394c4ae68b
commit aec2761d31
3 changed files with 100 additions and 157 deletions

View File

@@ -1,8 +1,28 @@
import fs from "fs";
import path from "path";
import xml2js from "xml2js";
import { XMLParser } from "fast-xml-parser";
function main() {
/** @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:");
@@ -15,38 +35,33 @@ function main() {
const diagnosticsMapFilePath = args[2];
// generate the lcg file for enu
generateLCGFile();
await generateLCGFile();
// generate other langs
fs.readdir(inputPath, (err, files) => {
handleError(err);
files.forEach(visitDirectory);
});
const files = await fs.promises.readdir(inputPath);
await Promise.all(files.map(visitDirectory));
return;
/**
* @param {string} name
*/
function visitDirectory(name) {
async function visitDirectory(name) {
const inputFilePath = path.join(inputPath, name, "diagnosticMessages", "diagnosticMessages.generated.json.lcl");
fs.readFile(inputFilePath, (err, data) => {
handleError(err);
xml2js.parseString(data.toString(), (err, result) => {
handleError(err);
if (!result || !result.LCX || !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);
}
writeFile(path.join(outputPath, outputDirectoryName, "diagnosticMessages.generated.json"), xmlObjectToString(result));
});
});
const contents = await fs.promises.readFile(inputFilePath);
/** @type {ParsedLCL} */
// eslint-disable-next-line local/object-literal-surrounding-space
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));
}
/**
@@ -81,24 +96,14 @@ function main() {
}
/**
* @param {null | object} err
*/
function handleError(err) {
if (err) {
console.error(err);
process.exit(1);
}
}
/**
* @param {any} o
* @param {ParsedLCL} o
*/
function xmlObjectToString(o) {
/** @type {any} */
const out = {};
for (const item of o.LCX.Item[0].Item[0].Item) {
let ItemId = item.$.ItemId;
let val = item.Str[0].Tgt ? item.Str[0].Tgt[0].Val[0] : item.Str[0].Val[0];
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");
@@ -115,55 +120,26 @@ function main() {
return JSON.stringify(out, undefined, 2);
}
/**
* @param {string} directoryPath
* @param {() => void} action
*/
function ensureDirectoryExists(directoryPath, action) {
fs.exists(directoryPath, exists => {
if (!exists) {
const basePath = path.dirname(directoryPath);
if (basePath !== directoryPath) {
return ensureDirectoryExists(basePath, () => fs.mkdir(directoryPath, action));
}
}
action();
});
}
/**
* @param {string} fileName
* @param {string} contents
*/
function writeFile(fileName, contents) {
ensureDirectoryExists(path.dirname(fileName), () => {
fs.writeFile(fileName, contents, handleError);
});
async function writeFile(fileName, contents) {
await fs.promises.mkdir(path.dirname(fileName), { recursive: true });
await fs.promises.writeFile(fileName, contents);
}
/**
* @param {Record<string, string>} o
*/
function objectToList(o) {
const list = [];
for (const key in o) {
list.push({ key, value: o[key] });
}
return list;
}
function generateLCGFile() {
return fs.readFile(diagnosticsMapFilePath, (err, data) => {
handleError(err);
writeFile(
path.join(outputPath, "enu", "diagnosticMessages.generated.json.lcg"),
getLCGFileXML(
objectToList(JSON.parse(data.toString()))
.sort((a, b) => a.key > b.key ? 1 : -1) // lcg sorted by property keys
.reduce((s, { key, value }) => s + getItemXML(key, value), "")
));
});
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
@@ -204,4 +180,4 @@ function main() {
}
}
main();
await main();