pogen: read files from tsconfig, import po2ts

This commit is contained in:
Florian Dold 2022-02-14 16:51:13 +01:00
parent aa7d48bfb1
commit e6c1294c91
No known key found for this signature in database
GPG Key ID: D2E4F00F29D02A4B
7 changed files with 133 additions and 105 deletions

View File

@ -11,6 +11,7 @@
"compile": "tsc"
},
"devDependencies": {
"po2json": "^0.4.5",
"typescript": "^4.5.5"
},
"dependencies": {

View File

@ -1,32 +0,0 @@
const ts = require("typescript");
const configPath = ts.findConfigFile(
/*searchPath*/ "./",
ts.sys.fileExists,
"tsconfig.json"
);
if (!configPath) {
throw new Error("Could not find a valid 'tsconfig.json'.");
}
console.log(configPath);
const cmdline = ts.getParsedCommandLineOfConfigFile(configPath, {}, {
fileExists: ts.sys.fileExists,
getCurrentDirectory: ts.sys.getCurrentDirectory,
onUnRecoverableConfigFileDiagnostic: (e) => console.log(e),
readDirectory: ts.sys.readDirectory,
readFile: ts.sys.readFile,
useCaseSensitiveFileNames: true,
})
console.log(cmdline);
const prog = ts.createProgram({
options: cmdline.options,
rootNames: cmdline.fileNames,
});
const allFiles = prog.getSourceFiles();
console.log(allFiles.map(x => x.path));

View File

@ -0,0 +1,60 @@
/*
This file is part of GNU Taler
(C) 2020 Taler Systems S.A.
GNU Taler is free software; you can redistribute it and/or modify it under the
terms of the GNU General Public License as published by the Free Software
Foundation; either version 3, or (at your option) any later version.
GNU Taler is distributed in the hope that it will be useful, but WITHOUT ANY
WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR
A PARTICULAR PURPOSE. See the GNU General Public License for more details.
You should have received a copy of the GNU General Public License along with
GNU Taler; see the file COPYING. If not, see <http://www.gnu.org/licenses/>
*/
/**
* Convert a <lang>.po file into a JavaScript / TypeScript expression.
*/
// @ts-ignore
import * as po2json from "po2json";
import * as fs from "fs";
import * as path from "path";
const files = fs
.readdirSync("./src/i18n")
.filter((x) => x.endsWith(".po"))
.map((x) => path.join("./src/i18n/", x));
if (files.length === 0) {
console.error("no .po files found in src/i18n/");
process.exit(1);
}
console.log(files);
const chunks: string[] = [];
for (const filename of files) {
const m = filename.match(/([a-zA-Z0-9-_]+).po/);
if (!m) {
console.error("error: unexpected filename (expected <lang>.po)");
process.exit(1);
}
const lang = m[1];
const pojson = po2json.parseFileSync(filename, {
format: "jed1.x",
fuzzy: true,
});
const s =
"strings['" + lang + "'] = " + JSON.stringify(pojson, null, " ") + ";\n\n";
chunks.push(s);
}
const tsContents = chunks.join("");
fs.writeFileSync("src/i18n/strings.ts", tsContents);

View File

@ -1,32 +1,23 @@
/*
This file is part of TALER
(C) 2016 GNUnet e.V.
This file is part of GNU Taler
(C) 2019-2022 Taler Systems S.A.
TALER is free software; you can redistribute it and/or modify it under the
GNU Taler is free software; you can redistribute it and/or modify it under the
terms of the GNU General Public License as published by the Free Software
Foundation; either version 3, or (at your option) any later version.
TALER is distributed in the hope that it will be useful, but WITHOUT ANY
GNU Taler is distributed in the hope that it will be useful, but WITHOUT ANY
WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR
A PARTICULAR PURPOSE. See the GNU General Public License for more details.
You should have received a copy of the GNU General Public License along with
TALER; see the file COPYING. If not, see <http://www.gnu.org/licenses/>
GNU Taler; see the file COPYING. If not, see <http://www.gnu.org/licenses/>
*/
/**
* Generate .po file from list of source files.
*
* Note that duplicate message IDs are NOT merged, to get the same output as
* you would from xgettext, just run msguniq.
*
* @author Florian Dold
*/
/**
* Imports.
*/
import { readFileSync } from "fs";
import * as ts from "typescript";
function wordwrap(str: string, width: number = 80): string[] {
@ -34,7 +25,7 @@ function wordwrap(str: string, width: number = 80): string[] {
return str.match(RegExp(regex, "g"));
}
export function processFile(sourceFile: ts.SourceFile) {
export function processFile(sourceFile: ts.SourceFile, outChunks: string[]) {
processNode(sourceFile);
let lastTokLine = 0;
let preLastTokLine = 0;
@ -146,11 +137,11 @@ export function processFile(sourceFile: ts.SourceFile) {
function formatMsgComment(line: number, comment?: string) {
if (comment) {
for (let cl of comment.split("\n")) {
console.log(`#. ${cl}`);
outChunks.push(`#. ${cl}\n`);
}
}
console.log(`#: ${sourceFile.fileName}:${line + 1}`);
console.log(`#, c-format`);
outChunks.push(`#: ${sourceFile.fileName}:${line + 1}\n`);
outChunks.push(`#, c-format\n`);
}
function formatMsgLine(head: string, msg: string) {
@ -161,11 +152,11 @@ export function processFile(sourceFile: ts.SourceFile) {
.map((p) => wordwrap(p))
.reduce((a, b) => a.concat(b));
if (parts.length == 1) {
console.log(`${head} "${parts[0]}"`);
outChunks.push(`${head} "${parts[0]}"\n`);
} else {
console.log(`${head} ""`);
outChunks.push(`${head} ""\n`);
for (let p of parts) {
console.log(`"${p}"`);
outChunks.push(`"${p}"\n`);
}
}
}
@ -208,7 +199,7 @@ export function processFile(sourceFile: ts.SourceFile) {
switch (childNode.kind) {
case ts.SyntaxKind.JsxText: {
let e = childNode as ts.JsxText;
let s = e.getFullText();
let s = e.text;
let t = s.split("\n").map(trim).join(" ");
if (s[0] === " ") {
t = " " + t;
@ -295,8 +286,8 @@ export function processFile(sourceFile: ts.SourceFile) {
let comment = getComment(node);
formatMsgComment(line, comment);
formatMsgLine("msgid", content);
console.log(`msgstr ""`);
console.log();
outChunks.push(`msgstr ""\n`);
outChunks.push("\n");
return;
}
if (arrayEq(path, ["i18n", "TranslateSwitch"])) {
@ -315,9 +306,9 @@ export function processFile(sourceFile: ts.SourceFile) {
}
formatMsgLine("msgid", singularForm);
formatMsgLine("msgid_plural", pluralForm);
console.log(`msgstr[0] ""`);
console.log(`msgstr[1] ""`);
console.log();
outChunks.push(`msgstr[0] ""\n`);
outChunks.push(`msgstr[1] ""\n`);
outChunks.push(`\n`);
return;
}
break;
@ -346,25 +337,24 @@ export function processFile(sourceFile: ts.SourceFile) {
formatMsgComment(line, comment);
formatMsgLine("msgid", t1.template);
formatMsgLine("msgid_plural", t2.template);
console.log(`msgstr[0] ""`);
console.log(`msgstr[1] ""`);
console.log();
outChunks.push(`msgstr[0] ""\n`);
outChunks.push(`msgstr[1] ""\n`);
outChunks.push("\n");
// Important: no processing for child i18n expressions here
return;
}
case ts.SyntaxKind.TaggedTemplateExpression: {
let tte = <ts.TaggedTemplateExpression>node;
let { comment, template, line, path } = processTaggedTemplateExpression(
tte,
);
let { comment, template, line, path } =
processTaggedTemplateExpression(tte);
if (path[0] != "i18n") {
break;
}
formatMsgComment(line, comment);
formatMsgLine("msgid", template);
console.log(`msgstr ""`);
console.log();
outChunks.push(`msgstr ""\n`);
outChunks.push("\n");
break;
}
}
@ -373,36 +363,51 @@ export function processFile(sourceFile: ts.SourceFile) {
}
}
export function main() {
const configPath = ts.findConfigFile(
/*searchPath*/ "./",
ts.sys.fileExists,
"tsconfig.json",
);
if (!configPath) {
throw new Error("Could not find a valid 'tsconfig.json'.");
}
const configPath = ts.findConfigFile(
/*searchPath*/ "./",
ts.sys.fileExists,
"tsconfig.json",
);
if (!configPath) {
throw new Error("Could not find a valid 'tsconfig.json'.");
}
const cmdline = ts.getParsedCommandLineOfConfigFile(
configPath,
{},
{
fileExists: ts.sys.fileExists,
getCurrentDirectory: ts.sys.getCurrentDirectory,
onUnRecoverableConfigFileDiagnostic: (e) => console.log(e),
readDirectory: ts.sys.readDirectory,
readFile: ts.sys.readFile,
useCaseSensitiveFileNames: true,
},
);
console.log(configPath);
const fileNames = cmdline.fileNames;
const cmdline = ts.getParsedCommandLineOfConfigFile(
configPath,
{},
{
fileExists: ts.sys.fileExists,
getCurrentDirectory: ts.sys.getCurrentDirectory,
onUnRecoverableConfigFileDiagnostic: (e) => console.log(e),
readDirectory: ts.sys.readDirectory,
readFile: ts.sys.readFile,
useCaseSensitiveFileNames: true,
},
);
fileNames.sort();
console.log(cmdline);
const outChunks: string[] = [];
const prog = ts.createProgram({
options: cmdline.options,
rootNames: cmdline.fileNames,
});
outChunks.push(`# SOME DESCRIPTIVE TITLE.
const allFiles = prog.getSourceFiles();
const ownFiles = allFiles.filter(
(x) =>
!x.isDeclarationFile &&
!prog.isSourceFileFromExternalLibrary(x) &&
!prog.isSourceFileDefaultLibrary(x),
);
console.log(ownFiles.map((x) => x.fileName));
const chunks = [];
chunks.push(`# SOME DESCRIPTIVE TITLE.
# Copyright (C) YEAR THE PACKAGE'S COPYRIGHT HOLDER
# This file is distributed under the same license as the PACKAGE package.
# FIRST AUTHOR <EMAIL@ADDRESS>, YEAR.
@ -421,16 +426,8 @@ msgstr ""
"Content-Type: text/plain; charset=UTF-8\\n"
"Content-Transfer-Encoding: 8bit\\n"`);
fileNames.forEach((fileName) => {
let sourceFile = ts.createSourceFile(
fileName,
readFileSync(fileName).toString(),
ts.ScriptTarget.ES2016,
/*setParentNodes */ true,
);
processFile(sourceFile);
});
const out = outChunks.join("");
console.log(out);
for (const f of ownFiles) {
processFile(f, chunks);
}
console.log(chunks.join(""));

View File

@ -10,5 +10,5 @@
"lib": ["es6"],
"types": ["node"]
},
"files": ["pogen.ts"]
"include": ["src/**/*.ts"]
}

View File

@ -150,10 +150,12 @@ importers:
packages/pogen:
specifiers:
'@types/node': ^17.0.17
po2json: ^0.4.5
typescript: ^4.5.5
dependencies:
'@types/node': 17.0.17
devDependencies:
po2json: 0.4.5
typescript: 4.5.5
packages/taler-util: