From e6c1294c910e6b54d24d62981632cf5e5f79d33f Mon Sep 17 00:00:00 2001 From: Florian Dold Date: Mon, 14 Feb 2022 16:51:13 +0100 Subject: [PATCH] pogen: read files from tsconfig, import po2ts --- packages/pogen/package.json | 1 + packages/pogen/po2.js | 32 ---- packages/pogen/{ => src}/dumpTree.ts | 0 packages/pogen/src/po2ts.ts | 60 ++++++++ .../pogen/{pogen.ts => src/potextract.ts} | 141 +++++++++--------- packages/pogen/tsconfig.json | 2 +- pnpm-lock.yaml | 2 + 7 files changed, 133 insertions(+), 105 deletions(-) delete mode 100644 packages/pogen/po2.js rename packages/pogen/{ => src}/dumpTree.ts (100%) create mode 100644 packages/pogen/src/po2ts.ts rename packages/pogen/{pogen.ts => src/potextract.ts} (81%) diff --git a/packages/pogen/package.json b/packages/pogen/package.json index 763edca78..04c7ba1c0 100644 --- a/packages/pogen/package.json +++ b/packages/pogen/package.json @@ -11,6 +11,7 @@ "compile": "tsc" }, "devDependencies": { + "po2json": "^0.4.5", "typescript": "^4.5.5" }, "dependencies": { diff --git a/packages/pogen/po2.js b/packages/pogen/po2.js deleted file mode 100644 index 532a1522f..000000000 --- a/packages/pogen/po2.js +++ /dev/null @@ -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)); \ No newline at end of file diff --git a/packages/pogen/dumpTree.ts b/packages/pogen/src/dumpTree.ts similarity index 100% rename from packages/pogen/dumpTree.ts rename to packages/pogen/src/dumpTree.ts diff --git a/packages/pogen/src/po2ts.ts b/packages/pogen/src/po2ts.ts new file mode 100644 index 000000000..d0f4ed34d --- /dev/null +++ b/packages/pogen/src/po2ts.ts @@ -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 + */ + +/** + * Convert a .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 .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); diff --git a/packages/pogen/pogen.ts b/packages/pogen/src/potextract.ts similarity index 81% rename from packages/pogen/pogen.ts rename to packages/pogen/src/potextract.ts index 23ac389f4..5999d9e1c 100644 --- a/packages/pogen/pogen.ts +++ b/packages/pogen/src/potextract.ts @@ -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 + GNU Taler; see the file COPYING. If not, see */ -/** - * 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 = 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 , 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("")); diff --git a/packages/pogen/tsconfig.json b/packages/pogen/tsconfig.json index d51c5326e..68225832d 100644 --- a/packages/pogen/tsconfig.json +++ b/packages/pogen/tsconfig.json @@ -10,5 +10,5 @@ "lib": ["es6"], "types": ["node"] }, - "files": ["pogen.ts"] + "include": ["src/**/*.ts"] } diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index 6353ef5fb..d1832bf90 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -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: