agama/.github/workflows/product_translations/merge_po

195 lines
5 KiB
JavaScript
Executable file

#! /usr/bin/node
/*
This script merges the product translations back to the product YAML files.
Requirements: NodeJS, run "npm ci" to install the needed NPM packages
Usage:
merge_po <translations_dir> <products_dir>
*/
const fs = require("fs");
const process = require("process");
const path = require("path");
const { parseDocument, isCollection } = require("yaml");
const { globSync } = require("glob");
const gettextParser = require("gettext-parser");
/**
* Translation object with original text, its translations and the locale name.
*/
class Translation {
msgid;
msgstr;
locale;
/**
* Constructor
* @param {string} msgid - the original text
* @param {string} msgstr - the translation
* @param {string} locale - the locale
*/
constructor(msgid, msgstr, locale) {
this.msgid = msgid;
this.msgstr = msgstr;
this.locale = locale;
}
/**
* Read all translations (PO files) in the specified directory
* @param {string} dir - input directory for reading the the PO files
* @returns {Translation[]} list of found translations
*/
static read(dir) {
const ret = [];
// sort the files so the translations in the YAML files are also sorted
const files = globSync(dir + "/*.po").sort();
files.forEach((f) => {
console.log(`Reading ${f}`);
const locale = path.basename(f, ".po");
const input = fs.readFileSync(f, "utf-8");
const po = gettextParser.po.parse(input);
// translations for the default (empty) context
const translations = po.translations[""];
Object.values(translations).forEach((t) => {
if (t.msgid !== "") {
ret.push(new Translation(t.msgid, t.msgstr[0], locale));
}
});
});
return ret;
}
}
/**
* YAML product file
*/
class YamlProduct {
file;
document;
/**
* Constructor
* @param {string} file - the path to the YAML file
*/
constructor(file) {
this.file = file;
// parse the file
const data = fs.readFileSync(this.file, "utf-8");
this.document = parseDocument(data);
}
/**
* Read all product definitions (YAML files) in the specified directory
* @param {string} dir - input directory for reading the the PO files
* @returns {YamlProduct[]} all found products
*/
static read(dir) {
const ret = [];
const files = globSync(dir + "/*.y{a,}ml");
return files.map((f) => {
console.log(`Reading ${f}`);
return new YamlProduct(f);
});
}
/**
* Find the matching translations in the list and add them to the YAML document
* @param {Translation[]} translations
*/
addTranslations(translations) {
const description = this.document.get("description");
const newTranslations = {};
translations.forEach((t) => {
if (t.msgid === description && t.msgstr?.length > 0) {
newTranslations[t.locale] = t.msgstr;
}
});
// add or update the translations depending on whether they already exist
if (Object.keys(newTranslations).length > 0) {
this.document.setIn(["translations", "description"], newTranslations);
}
const modes = this.document.get("modes");
if (modes && isCollection(modes)) {
modes.items.forEach((mode) => {
const modeId = mode.get("id");
const modeName = mode.get("name");
const modeDescription = mode.get("description");
const nameTranslations = {};
const descTranslations = {};
translations.forEach((t) => {
if (t.msgstr?.length > 0) {
if (t.msgid === modeName) {
nameTranslations[t.locale] = t.msgstr;
}
if (t.msgid === modeDescription) {
descTranslations[t.locale] = t.msgstr;
}
}
});
if (Object.keys(nameTranslations).length > 0) {
this.document.setIn(
["translations", "mode", modeId, "name"],
nameTranslations,
);
}
if (Object.keys(descTranslations).length > 0) {
this.document.setIn(
["translations", "mode", modeId, "description"],
descTranslations,
);
}
});
}
}
/**
* Convert back the parsed YAML to String
* @returns {string} new update YAML data
*/
dump() {
return this.document.toString();
}
/**
* Save the current YAML data back to the file, the original content is
* overwritten.
*/
save() {
console.log(`Writing ${this.file}`);
fs.writeFileSync(this.file, this.dump(), "utf-8");
}
}
// script arguments (the first arg is executor path ("/usr/bin/node"),
// the second is name of this script)
const [, , translations_dir, products_dir] = process.argv;
if (!translations_dir || !products_dir) {
console.log("ERROR: missing argument");
process.exit(1);
}
// read all YAML products and all translations
const translations = Translation.read(translations_dir);
const products = YamlProduct.read(products_dir);
// inject the translations to the products and save the changes
products.forEach((p) => {
p.addTranslations(translations);
p.save();
});