Skip to content

Commit

Permalink
Merge pull request #851 from lslezak/master
Browse files Browse the repository at this point in the history
Generate and upload the product POT file to allow translating the product descriptions
  • Loading branch information
lslezak authored Nov 7, 2023
2 parents f1a8318 + b459244 commit 9b67fbe
Show file tree
Hide file tree
Showing 6 changed files with 443 additions and 7 deletions.
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

0 comments on commit 9b67fbe

Please sign in to comment.