195 lines
5 KiB
JavaScript
Executable file
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();
|
|
});
|