Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions .github/workflows/product_translations/.gitignore
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
node_modules/
218 changes: 218 additions & 0 deletions .github/workflows/product_translations/package-lock.json

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

6 changes: 6 additions & 0 deletions .github/workflows/product_translations/package.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
{
"devDependencies": {
"gettext-parser": "^7.0.1",
"yaml": "^2.3.4"
}
}
143 changes: 143 additions & 0 deletions .github/workflows/product_translations/products_pot
Original file line number Diff line number Diff line change
@@ -0,0 +1,143 @@
#! /usr/bin/node

/*
This script generates the POT file with product translations from product
YAML files.

Requirements: NodeJS, run "npm ci" to install the needed NPM packages

Usage:

products_pot *.yaml
*/

const fs = require("fs");
const process = require("process");

const { Parser, LineCounter, parseDocument } = require("yaml");
const gettextParser = require("gettext-parser");

/**
* Translatable text with source location
*/
class POEntry {
text;
file;
line;
product;

/**
* Constructor
* @param {string} text - text of the description
* @param {string} file - file name
* @param {number} line - line location
* @param {string} product - name of the product
*/
constructor(text, file, line, product) {
this.text = text;
this.file = file;
this.line = line;
this.product = product;
}
}

// collects translatable texts (POEntries) and generates the final POT file
class POFile {
entries = [];

/**
* generate a time stamp string for the POT file header
* @returns {string} timestamp
*/
timestamp() {
const date = new Date();
return date.getUTCFullYear() + "-" + date.getUTCMonth() + "-" +
date.getUTCDate() + " " + date.getUTCHours() + ":" + date.getUTCMinutes() +
"+0000";
}

/**
* generate the POT file content
* @returns {string} generated POT file or empty string if there are no entries
*/
dump() {
if (this.entries.length === 0) return "";

// template file with the default POT file header
const template = require("./template.json");
template.headers["POT-Creation-Date"] = this.timestamp();

const translations = template.translations[""];

this.entries.forEach(e => {
translations[e.text] = {
msgid: e.text,
comments: {
translator: `TRANSLATORS: description of product "${e.product}"`,
reference: e.file + ":" + e.line
},
msgstr: [""]
};
});

// sort the output by the msgid to have consistent results
return String(gettextParser.po.compile(template, { sort: true }));
}
}

/**
* Reads and parses the YAML product file
*/
class YamlReader {
file;

/**
* Constructor
* @param {string} file - name of the YAML file to read
*/
constructor(file) {
this.file = file;
}

/**
* Read and parse the YAML file
* @returns {undefined,POEntry} the found description entry or `undefined` if not found
*/
description() {
const data = fs.readFileSync(this.file, "utf-8");

// get the parsed text value
const parsed = parseDocument(data);
const description = parsed.get("description");
if (!description) return;

const product = parsed.get("name");

const lineCounter = new LineCounter();
const tokens = new Parser(lineCounter.addNewLine).parse(data);

for (const token of tokens) {
if (token.type === "document") {
// get the line position of the value
const description_token = token.value.items.find(i => i.key.source === "description");
const line = lineCounter.linePos(description_token.value.offset).line;
return new POEntry(description, this.file, line, product);
}
}
}
}

const output = new POFile();
// script arguments (the first arg is executor path ("/usr/bin/node"),
// the second is name of this script)
const [,, ...params] = process.argv;

params.forEach(f => {
const reader = new YamlReader(f);
const descriptionEntry = reader.description();
if (descriptionEntry) {
output.entries.push(descriptionEntry);
}
});

console.log(output.dump());
26 changes: 26 additions & 0 deletions .github/workflows/product_translations/template.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,26 @@
{
"charset": "utf-8",
"headers": {
"Project-Id-Version": "PACKAGE VERSION",
"Report-Msgid-Bugs-To": "",
"PO-Revision-Date": "YEAR-MO-DA HO:MI+ZONE",
"Last-Translator": "FULL NAME <EMAIL@ADDRESS>",
"Language-Team": "LANGUAGE <LL@li.org>",
"Language": "",
"MIME-Version": "1.0",
"Content-Type": "text/plain; charset=utf-8",
"Content-Transfer-Encoding": "8bit",
"Plural-Forms": "nplurals=INTEGER; plural=EXPRESSION;"
},
"translations": {
"": {
"": {
"msgid": "",
"comments": {
"translator": "SOME DESCRIPTIVE TITLE.\nCopyright (C) YEAR SuSE Linux Products GmbH, Nuernberg\nThis file is distributed under the same license as the PACKAGE package.\nFIRST AUTHOR <EMAIL@ADDRESS>, YEAR.\n",
"flag": "fuzzy"
}
}
}
}
}
Loading