commit 590073c50f2561ee10538fe0cfda5378a501bbd7
parent f6ca179d6da99b61988529cbd91a6177d6f3e692
Author: Sebastian <sebasjm@gmail.com>
Date: Tue, 29 Oct 2024 12:52:28 -0300
add support for context
Diffstat:
2 files changed, 177 insertions(+), 37 deletions(-)
diff --git a/packages/pogen/src/potextract.test.ts b/packages/pogen/src/potextract.test.ts
@@ -35,7 +35,7 @@ function process(src: string): string {
return processFile2(source).trim();
}
-test("p2p: should select the coin", (t) => {
+test("should extract the key from inner body", (t) => {
t.deepEqual(
process(`<i18n.Translate>something</i18n.Translate>`),
`#: test.tsx:3
@@ -44,3 +44,61 @@ msgid "something"
msgstr ""`,
);
});
+
+test("should support context on tags", (t) => {
+ t.deepEqual(
+ process(
+ `return <div>
+ <i18n.Translate context="some_context" anotherkey="not the context" asd={"asd"} zxc={asd()}>something</i18n.Translate>
+ </div>`,
+ ),
+ `#: test.tsx:4
+#, c-format
+msgctxt "some_context"
+msgid "something"
+msgstr ""`,
+ );
+});
+
+test("should support context on string template", (t) => {
+ t.deepEqual(
+ process(`return i18n.context("wire transfer")\`send\`;`),
+ `#: test.tsx:3
+#, c-format
+msgctxt "wire transfer"
+msgid "send"
+msgstr ""`,
+ );
+});
+
+test("should support same message id with different context", (t) => {
+ t.deepEqual(
+ process(
+ `return i18n.context("wire transfer")\`send\` + i18n.context("gift")\`send\`;`,
+ ),
+ `#: test.tsx:3
+#, c-format
+msgctxt "wire transfer"
+msgid "send"
+msgstr ""
+
+#: test.tsx:3
+#, c-format
+msgctxt "gift"
+msgid "send"
+msgstr ""`,
+ );
+});
+
+test("should support on string template", (t) => {
+ t.deepEqual(
+ process(`
+ // comment of the translation
+ return i18n.str\`another key\`;`),
+ `#. comment of the translation
+#: test.tsx:5
+#, c-format
+msgid "another key"
+msgstr ""`,
+ );
+});
diff --git a/packages/pogen/src/potextract.ts b/packages/pogen/src/potextract.ts
@@ -64,12 +64,12 @@ function getTemplate(node: ts.Node): string {
function getComment(
sourceFile: ts.SourceFile,
- lastTokLine: number,
preLastTokLine: number,
+ lastTokLine: number,
node: ts.Node,
): string {
let lc = ts.getLineAndCharacterOfPosition(sourceFile, node.pos);
- let lastComments;
+ let lastComments: ts.CommentRange[];
for (let l = preLastTokLine; l < lastTokLine; l++) {
let pos = ts.getPositionOfLineAndCharacter(sourceFile, l, 0);
let comments = ts.getTrailingCommentRanges(sourceFile.text, pos);
@@ -105,16 +105,47 @@ function getComment(
return text;
}
-function getPath(node: ts.Node): string[] {
+function getPath(node: ts.Node): { path: string[]; ctx: string } {
switch (node.kind) {
- case ts.SyntaxKind.PropertyAccessExpression:
+ case ts.SyntaxKind.PropertyAccessExpression: {
let pae = <ts.PropertyAccessExpression>node;
- return Array.prototype.concat(getPath(pae.expression), [pae.name.text]);
- case ts.SyntaxKind.Identifier:
+ return {
+ path: Array.prototype.concat(getPath(pae.expression).path, [
+ pae.name.text,
+ ]),
+ ctx: "",
+ };
+ }
+ case ts.SyntaxKind.Identifier: {
let id = <ts.Identifier>node;
- return [id.text];
+ return {
+ path: [id.text],
+ ctx: "",
+ };
+ }
+ case ts.SyntaxKind.CallExpression: {
+ const call = <ts.CallExpression>node;
+
+ const firstArg = call.arguments[0];
+ if (
+ call.arguments.length === 1 &&
+ firstArg.kind === ts.SyntaxKind.StringLiteral
+ ) {
+ const str = <ts.StringLiteral>firstArg;
+ return {
+ path: getPath(call.expression).path,
+ ctx: str.text,
+ };
+ }
+ }
+ default: {
+ // console.log("ASDASD", ts.SyntaxKind[node.kind], node);
+ }
}
- return ["(other)"];
+ return {
+ path: ["(other)"],
+ ctx: "",
+ };
}
function arrayEq<T>(a1: T[], a2: T[]) {
@@ -134,6 +165,7 @@ interface TemplateResult {
path: string[];
template: string;
line: number;
+ context: string;
}
function processTaggedTemplateExpression(
@@ -142,17 +174,13 @@ function processTaggedTemplateExpression(
lastTokLine: number,
tte: ts.TaggedTemplateExpression,
): TemplateResult {
- let lc = ts.getLineAndCharacterOfPosition(sourceFile, tte.pos);
- if (lc.line != lastTokLine) {
- preLastTokLine = lastTokLine; // HERE
- lastTokLine = lc.line;
- }
let path = getPath(tte.tag);
let res: TemplateResult = {
- path,
- line: lc.line,
+ path: path.path,
+ line: lastTokLine,
comment: getComment(sourceFile, preLastTokLine, lastTokLine, tte),
template: getTemplate(tte.template),
+ context: path.ctx,
};
return res;
}
@@ -177,8 +205,8 @@ function formatMsgLine(outChunks: string[], head: string, msg: string) {
const m = msg.match(/(.*\n|.+$)/g);
if (!m) return;
// Do escaping, wrap break at newlines
- console.log("head", JSON.stringify(head));
- console.log("msg", JSON.stringify(msg));
+ // console.log("head", JSON.stringify(head));
+ // console.log("msg", JSON.stringify(msg));
let parts = m
.map((x) => x.replace(/\n/g, "\\n").replace(/"/g, '\\"'))
.map((p) => wordwrap(p))
@@ -199,7 +227,7 @@ function getJsxElementPath(node: ts.Node) {
switch (childNode.kind) {
case ts.SyntaxKind.JsxOpeningElement: {
let e = childNode as ts.JsxOpeningElement;
- return (path = getPath(e.tagName));
+ return (path = getPath(e.tagName).path);
}
default:
break;
@@ -224,6 +252,28 @@ function trim(s: string) {
return s.replace(/^[ \n\t]*/, "").replace(/[ \n\t]*$/, "");
}
+function getJsxAttribute(sour: ts.SourceFile, node: ts.Node) {
+ const result = {};
+ ts.forEachChild(node, (childNode: ts.Node) => {
+ switch (childNode.kind) {
+ case ts.SyntaxKind.JsxOpeningElement: {
+ let e = childNode as ts.JsxOpeningElement;
+
+ e.attributes.properties.map((p) => {
+ const id = p.getChildAt(0, sour).getText(sour);
+ const v = p.getChildAt(2, sour);
+ if (v.kind !== ts.SyntaxKind.StringLiteral) {
+ return undefined;
+ }
+ result[id] = JSON.parse(v.getText(sour));
+ });
+ return;
+ }
+ }
+ });
+ return result;
+}
+
function getJsxContent(node: ts.Node) {
let fragments = [];
let holeNum = [1];
@@ -322,12 +372,20 @@ function processNode(
case ts.SyntaxKind.JsxElement:
let path = getJsxElementPath(node);
if (arrayEq(path, ["i18n", "Translate"])) {
- let content = getJsxContent(node);
- let { line } = ts.getLineAndCharacterOfPosition(sourceFile, node.pos);
- let comment = getComment(sourceFile, preLastTokLine, lastTokLine, node);
- if (!knownMessageIds.has(content)) {
- knownMessageIds.add(content);
+ const content = getJsxContent(node);
+ const { line } = ts.getLineAndCharacterOfPosition(sourceFile, node.pos);
+ const comment = getComment(
+ sourceFile,
+ preLastTokLine,
+ lastTokLine,
+ node,
+ );
+ const context = getJsxAttribute(sourceFile, node)["context"] ?? "";
+ const msgid = context + content;
+ if (!knownMessageIds.has(msgid)) {
+ knownMessageIds.add(msgid);
formatMsgComment(sourceFile, outChunks, line, comment);
+ formatMsgLine(outChunks, "msgctxt", context);
formatMsgLine(outChunks, "msgid", content);
outChunks.push(`msgstr ""\n`);
outChunks.push("\n");
@@ -348,8 +406,11 @@ function processNode(
console.error("plural form missing");
process.exit(1);
}
- if (!knownMessageIds.has(singularForm)) {
- knownMessageIds.add(singularForm);
+ const context = getJsxAttribute(sourceFile, node)["context"] ?? "";
+ const msgid = context + singularForm;
+ if (!knownMessageIds.has(msgid)) {
+ knownMessageIds.add(msgid);
+ formatMsgLine(outChunks, "msgctxt", context);
formatMsgLine(outChunks, "msgid", singularForm);
formatMsgLine(outChunks, "msgid_plural", pluralForm);
outChunks.push(`msgstr[0] ""\n`);
@@ -363,7 +424,7 @@ function processNode(
// might be i18n.plural(i18n[.X]`...`, i18n[.X]`...`)
let ce = <ts.CallExpression>node;
let path = getPath(ce.expression);
- if (!arrayEq(path, ["i18n", "plural"])) {
+ if (!arrayEq(path.path, ["i18n", "plural"])) {
break;
}
if (ce.arguments[0].kind != ts.SyntaxKind.TaggedTemplateExpression) {
@@ -373,23 +434,37 @@ function processNode(
break;
}
let { line } = ts.getLineAndCharacterOfPosition(sourceFile, ce.pos);
+ const tte1 = <ts.TaggedTemplateExpression>ce.arguments[0];
+ let lc1 = ts.getLineAndCharacterOfPosition(sourceFile, tte1.pos);
+ if (lc1.line != lastTokLine) {
+ preLastTokLine = lastTokLine; // HERE
+ lastTokLine = lc1.line;
+ }
let t1 = processTaggedTemplateExpression(
sourceFile,
preLastTokLine,
lastTokLine,
- <ts.TaggedTemplateExpression>ce.arguments[0],
+ tte1,
);
+
+ const tte2 = <ts.TaggedTemplateExpression>ce.arguments[1];
+ let lc2 = ts.getLineAndCharacterOfPosition(sourceFile, tte2.pos);
+ if (lc2.line != lastTokLine) {
+ preLastTokLine = lastTokLine; // HERE
+ lastTokLine = lc2.line;
+ }
let t2 = processTaggedTemplateExpression(
sourceFile,
preLastTokLine,
lastTokLine,
- <ts.TaggedTemplateExpression>ce.arguments[1],
+ tte2,
);
let comment = getComment(sourceFile, preLastTokLine, lastTokLine, ce);
- const msgid = t1.template;
+ const msgid = path.ctx + t1.template;
if (!knownMessageIds.has(msgid)) {
knownMessageIds.add(msgid);
formatMsgComment(sourceFile, outChunks, line, comment);
+ formatMsgLine(outChunks, "msgctxt", path.ctx);
formatMsgLine(outChunks, "msgid", t1.template);
formatMsgLine(outChunks, "msgid_plural", t2.template);
outChunks.push(`msgstr[0] ""\n`);
@@ -402,19 +477,26 @@ function processNode(
}
case ts.SyntaxKind.TaggedTemplateExpression: {
let tte = <ts.TaggedTemplateExpression>node;
- let { comment, template, line, path } = processTaggedTemplateExpression(
- sourceFile,
- preLastTokLine,
- lastTokLine,
- tte,
- );
+ let lc2 = ts.getLineAndCharacterOfPosition(sourceFile, tte.pos);
+ if (lc2.line != lastTokLine) {
+ preLastTokLine = lastTokLine;
+ lastTokLine = lc2.line;
+ }
+ let { comment, template, line, path, context } =
+ processTaggedTemplateExpression(
+ sourceFile,
+ preLastTokLine,
+ lastTokLine,
+ tte,
+ );
if (path[0] != "i18n") {
break;
}
- const msgid = template;
+ const msgid = context + template;
if (!knownMessageIds.has(msgid)) {
knownMessageIds.add(msgid);
formatMsgComment(sourceFile, outChunks, line, comment);
+ formatMsgLine(outChunks, "msgctxt", context);
formatMsgLine(outChunks, "msgid", template);
outChunks.push(`msgstr ""\n`);
outChunks.push("\n");