From 9e9e04c930f9219ecbfb5da16f40ffb8bf3af2ae Mon Sep 17 00:00:00 2001 From: Kiruthiga A Date: Sun, 29 Mar 2026 10:20:41 +0530 Subject: [PATCH 01/13] Add script to auto-generate test-translation.json from .po files Currently, client/test-translation.json requires manual updates whenever translations change, which can lead to inconsistencies in snapshot tests. This PR adds a new script, generateTranslations.js, that automatically reads the i18n/en.po file and generates the corresponding JSON in client/test-translation.json. The generated JSON includes all msgid entries along with their msgstr values and necessary headers for proper test handling. With this script, developers no longer need to manually update test-translation.json. It ensures that snapshot tests always have up-to-date translations, reducing maintenance overhead and minimizing inconsistencies. Usage: node generateTranslations.js Next steps could include integrating this script with the test runner to automatically populate test-translation.json before tests are executed. --- scripts/generateTranslations.js | 46 +++++++++++++++++++++++++++++++++ 1 file changed, 46 insertions(+) create mode 100644 scripts/generateTranslations.js diff --git a/scripts/generateTranslations.js b/scripts/generateTranslations.js new file mode 100644 index 000000000..f6fa7eb3c --- /dev/null +++ b/scripts/generateTranslations.js @@ -0,0 +1,46 @@ +const fs = require("fs"); + +const poData = fs.readFileSync("i18n/en.po", "utf-8"); + +let lines = poData.split("\n"); + +let translations = {}; +let currentId = ""; + +lines.forEach(line => { + if (line.startsWith("msgid")) { + currentId = line.replace('msgid "', '').replace('"', '').trim(); + } + + if (line.startsWith("msgstr")) { + const value = line.replace('msgstr "', '').replace('"', '').trim(); + + if (currentId) { + translations[currentId] = { + msgid: currentId, + msgstr: [value] + }; + } + } +}); + +const finalJSON = { + charset: "utf-8", + headers: { + "content-type": "text/plain; charset=utf-8", + "plural-forms": "nplurals = 2; plural = (n != 1);", + language: "en", + "mime-version": "1.0", + "content-transfer-encoding": "8bit" + }, + translations: { + "": translations + } +}; + +fs.writeFileSync( + "client/test-translation.json", + JSON.stringify(finalJSON, null, 2) +); + +console.log("✅ JSON generated!"); \ No newline at end of file From 89994ec781f5a0b0242934701e75a641f89eab6c Mon Sep 17 00:00:00 2001 From: Kiruthiga A Date: Sun, 29 Mar 2026 10:36:03 +0530 Subject: [PATCH 02/13] Fix code style using Prettier --- scripts/generateTranslations.js | 18 +++++++++--------- 1 file changed, 9 insertions(+), 9 deletions(-) diff --git a/scripts/generateTranslations.js b/scripts/generateTranslations.js index f6fa7eb3c..992846a23 100644 --- a/scripts/generateTranslations.js +++ b/scripts/generateTranslations.js @@ -7,18 +7,18 @@ let lines = poData.split("\n"); let translations = {}; let currentId = ""; -lines.forEach(line => { +lines.forEach((line) => { if (line.startsWith("msgid")) { - currentId = line.replace('msgid "', '').replace('"', '').trim(); + currentId = line.replace('msgid "', "").replace('"', "").trim(); } if (line.startsWith("msgstr")) { - const value = line.replace('msgstr "', '').replace('"', '').trim(); + const value = line.replace('msgstr "', "").replace('"', "").trim(); if (currentId) { translations[currentId] = { msgid: currentId, - msgstr: [value] + msgstr: [value], }; } } @@ -31,16 +31,16 @@ const finalJSON = { "plural-forms": "nplurals = 2; plural = (n != 1);", language: "en", "mime-version": "1.0", - "content-transfer-encoding": "8bit" + "content-transfer-encoding": "8bit", }, translations: { - "": translations - } + "": translations, + }, }; fs.writeFileSync( "client/test-translation.json", - JSON.stringify(finalJSON, null, 2) + JSON.stringify(finalJSON, null, 2), ); -console.log("✅ JSON generated!"); \ No newline at end of file +console.log("✅ JSON generated!"); From e9a55d03c16b78d29fd8f78362076ab8537726ac Mon Sep 17 00:00:00 2001 From: Kiruthiga A Date: Sun, 29 Mar 2026 10:52:17 +0530 Subject: [PATCH 03/13] Fix code style using Prettier --- scripts/generateTranslations.js | 46 --------------------------------- 1 file changed, 46 deletions(-) delete mode 100644 scripts/generateTranslations.js diff --git a/scripts/generateTranslations.js b/scripts/generateTranslations.js deleted file mode 100644 index 992846a23..000000000 --- a/scripts/generateTranslations.js +++ /dev/null @@ -1,46 +0,0 @@ -const fs = require("fs"); - -const poData = fs.readFileSync("i18n/en.po", "utf-8"); - -let lines = poData.split("\n"); - -let translations = {}; -let currentId = ""; - -lines.forEach((line) => { - if (line.startsWith("msgid")) { - currentId = line.replace('msgid "', "").replace('"', "").trim(); - } - - if (line.startsWith("msgstr")) { - const value = line.replace('msgstr "', "").replace('"', "").trim(); - - if (currentId) { - translations[currentId] = { - msgid: currentId, - msgstr: [value], - }; - } - } -}); - -const finalJSON = { - charset: "utf-8", - headers: { - "content-type": "text/plain; charset=utf-8", - "plural-forms": "nplurals = 2; plural = (n != 1);", - language: "en", - "mime-version": "1.0", - "content-transfer-encoding": "8bit", - }, - translations: { - "": translations, - }, -}; - -fs.writeFileSync( - "client/test-translation.json", - JSON.stringify(finalJSON, null, 2), -); - -console.log("✅ JSON generated!"); From 9e0768f1fe037817edf812ea6cbead6970dade64 Mon Sep 17 00:00:00 2001 From: Kiruthiga A Date: Sun, 29 Mar 2026 10:53:26 +0530 Subject: [PATCH 04/13] Fix code style using Prettier --- scripts/generateTranslations.js | 46 +++++++++++++++++++++++++++++++++ 1 file changed, 46 insertions(+) create mode 100644 scripts/generateTranslations.js diff --git a/scripts/generateTranslations.js b/scripts/generateTranslations.js new file mode 100644 index 000000000..992846a23 --- /dev/null +++ b/scripts/generateTranslations.js @@ -0,0 +1,46 @@ +const fs = require("fs"); + +const poData = fs.readFileSync("i18n/en.po", "utf-8"); + +let lines = poData.split("\n"); + +let translations = {}; +let currentId = ""; + +lines.forEach((line) => { + if (line.startsWith("msgid")) { + currentId = line.replace('msgid "', "").replace('"', "").trim(); + } + + if (line.startsWith("msgstr")) { + const value = line.replace('msgstr "', "").replace('"', "").trim(); + + if (currentId) { + translations[currentId] = { + msgid: currentId, + msgstr: [value], + }; + } + } +}); + +const finalJSON = { + charset: "utf-8", + headers: { + "content-type": "text/plain; charset=utf-8", + "plural-forms": "nplurals = 2; plural = (n != 1);", + language: "en", + "mime-version": "1.0", + "content-transfer-encoding": "8bit", + }, + translations: { + "": translations, + }, +}; + +fs.writeFileSync( + "client/test-translation.json", + JSON.stringify(finalJSON, null, 2), +); + +console.log("✅ JSON generated!"); From cb0eee5b7487430572dd7b26225f333e441d386a Mon Sep 17 00:00:00 2001 From: Kiruthiga A Date: Sun, 29 Mar 2026 11:02:10 +0530 Subject: [PATCH 05/13] [feature] Add script to auto-generate test-translation.json from .po files From 607febba2960ed248cdfbd928c18a9496755d65d Mon Sep 17 00:00:00 2001 From: Kiruthiga A Date: Sun, 29 Mar 2026 11:05:25 +0530 Subject: [PATCH 06/13] [docs] Add PR template info for CodeRabbit --- PR_DESCRIPTION.md | Bin 0 -> 224 bytes 1 file changed, 0 insertions(+), 0 deletions(-) create mode 100644 PR_DESCRIPTION.md diff --git a/PR_DESCRIPTION.md b/PR_DESCRIPTION.md new file mode 100644 index 0000000000000000000000000000000000000000..efd19d06e586636ce03ba0e3529c900fd57f83cb GIT binary patch literal 224 zcmY+8y$Zrm3`Wmf@EtBV`-h9TNjC=_>?RIUi>($bt%6Ulo?9mgN$yQfzU1>xDJjUg zQd80KU`fJ=z@8Te^NN#J=)rzYD|eR*oUPvUmZSfItbg64>b_FZ*zXu~G5)J&@^RZw vcg>D18#NoAR_}dGoJrMcBqJRdGGoeuxt+wBcuk?jEu~z%KBzPC>Vd=;j#wt3 literal 0 HcmV?d00001 From c2c692b81c1009002d729be2a8d20e0073ce633e Mon Sep 17 00:00:00 2001 From: Kiruthiga A Date: Sun, 29 Mar 2026 11:09:26 +0530 Subject: [PATCH 07/13] [style] Fix ESLint & Prettier issues --- scripts/generateTranslations.js | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/scripts/generateTranslations.js b/scripts/generateTranslations.js index 992846a23..39c74efb8 100644 --- a/scripts/generateTranslations.js +++ b/scripts/generateTranslations.js @@ -2,9 +2,9 @@ const fs = require("fs"); const poData = fs.readFileSync("i18n/en.po", "utf-8"); -let lines = poData.split("\n"); +const lines = poData.split("\n"); -let translations = {}; +const translations = {}; let currentId = ""; lines.forEach((line) => { From 003d2e09b6287bc53cac7b5a2089352e57e72beb Mon Sep 17 00:00:00 2001 From: Kiruthiga A Date: Sun, 29 Mar 2026 11:17:11 +0530 Subject: [PATCH 08/13] [fix] Use path.join for cross-platform PO/JSON paths --- scripts/generateTranslations.js | 21 ++++++++++++++++----- 1 file changed, 16 insertions(+), 5 deletions(-) diff --git a/scripts/generateTranslations.js b/scripts/generateTranslations.js index 39c74efb8..b8ebb821c 100644 --- a/scripts/generateTranslations.js +++ b/scripts/generateTranslations.js @@ -1,9 +1,21 @@ const fs = require("fs"); +const path = require("path"); + +// Use project-root-relative paths for cross-platform safety +const poFilePath = path.join(process.cwd(), "i18n", "en.po"); +const outputFilePath = path.join( + process.cwd(), + "client", + "test-translation.json", +); -const poData = fs.readFileSync("i18n/en.po", "utf-8"); +// Read the PO file +const poData = fs.readFileSync(poFilePath, "utf-8"); +// Split lines const lines = poData.split("\n"); +// Parse translations const translations = {}; let currentId = ""; @@ -24,6 +36,7 @@ lines.forEach((line) => { } }); +// Prepare final JSON const finalJSON = { charset: "utf-8", headers: { @@ -38,9 +51,7 @@ const finalJSON = { }, }; -fs.writeFileSync( - "client/test-translation.json", - JSON.stringify(finalJSON, null, 2), -); +// Write output file with trailing newline +fs.writeFileSync(outputFilePath, `${JSON.stringify(finalJSON, null, 2) }\n`); console.log("✅ JSON generated!"); From f7e8bcbefde4a97a70d08a3c5f44584ad2be9e76 Mon Sep 17 00:00:00 2001 From: Kiruthiga A Date: Sun, 29 Mar 2026 11:24:42 +0530 Subject: [PATCH 09/13] [feature] Auto-generate test-translation.json from .po --- scripts/generateTranslations.js | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/scripts/generateTranslations.js b/scripts/generateTranslations.js index b8ebb821c..6f2f6d71f 100644 --- a/scripts/generateTranslations.js +++ b/scripts/generateTranslations.js @@ -52,6 +52,6 @@ const finalJSON = { }; // Write output file with trailing newline -fs.writeFileSync(outputFilePath, `${JSON.stringify(finalJSON, null, 2) }\n`); - +fs.writeFileSync(outputFilePath, `${JSON.stringify(finalJSON, null, 2)}\n`); +// eslint-disable-next-line no-console console.log("✅ JSON generated!"); From 4717140bd3663a0aa4d1229d8627fcdc6d24f37e Mon Sep 17 00:00:00 2001 From: Kiruthiga A Date: Sun, 29 Mar 2026 11:38:46 +0530 Subject: [PATCH 10/13] Fix code style using Prettier & ESLint --- PR_DESCRIPTION.md | Bin 224 -> 229 bytes 1 file changed, 0 insertions(+), 0 deletions(-) diff --git a/PR_DESCRIPTION.md b/PR_DESCRIPTION.md index efd19d06e586636ce03ba0e3529c900fd57f83cb..b32469912fc0850966e24d1d750c0e1c9287cb2b 100644 GIT binary patch delta 20 ZcmaFB_>__D{r Date: Sun, 29 Mar 2026 12:04:15 +0530 Subject: [PATCH 11/13] [style] Fix ESLint & Prettier issues in generateTranslations.js --- scripts/generateTranslations.js | 64 ++++++++++++++++++++++++++------- 1 file changed, 51 insertions(+), 13 deletions(-) diff --git a/scripts/generateTranslations.js b/scripts/generateTranslations.js index 6f2f6d71f..5a67dca09 100644 --- a/scripts/generateTranslations.js +++ b/scripts/generateTranslations.js @@ -13,28 +13,65 @@ const outputFilePath = path.join( const poData = fs.readFileSync(poFilePath, "utf-8"); // Split lines -const lines = poData.split("\n"); +const lines = poData.split(/\r?\n/); // Parse translations const translations = {}; -let currentId = ""; -lines.forEach((line) => { - if (line.startsWith("msgid")) { - currentId = line.replace('msgid "', "").replace('"', "").trim(); +let currentId = null; +let currentMsgstr = []; +let activeField = null; // "msgid" | "msgstr" | null +let activeIndex = 0; + +const parseQuoted = (raw) => JSON.parse(raw); + +const flushEntry = () => { + if (currentId) { + translations[currentId] = { + msgid: currentId, + msgstr: currentMsgstr.length ? currentMsgstr : [""], + }; } + currentId = null; + currentMsgstr = []; + activeField = null; + activeIndex = 0; +}; - if (line.startsWith("msgstr")) { - const value = line.replace('msgstr "', "").replace('"', "").trim(); +for (const rawLine of lines) { + const line = rawLine.trim(); - if (currentId) { - translations[currentId] = { - msgid: currentId, - msgstr: [value], - }; + if (!line) { + flushEntry(); + continue; + } + + if (/^msgid\s+/.test(line)) { + flushEntry(); + currentId = parseQuoted(line.replace(/^msgid\s+/, "")); + activeField = "msgid"; + continue; + } + + const msgstrMatch = line.match(/^msgstr(?:\[(\d+)\])?\s+(.*)$/); + if (msgstrMatch) { + activeField = "msgstr"; + activeIndex = msgstrMatch[1] ? Number(msgstrMatch[1]) : 0; + currentMsgstr[activeIndex] = parseQuoted(msgstrMatch[2]); + continue; + } + + if (line.startsWith('"')) { + const chunk = parseQuoted(line); + if (activeField === "msgid" && currentId !== null) { + currentId += chunk; + } else if (activeField === "msgstr") { + currentMsgstr[activeIndex] = (currentMsgstr[activeIndex] || "") + chunk; } } -}); +} + +flushEntry(); // Prepare final JSON const finalJSON = { @@ -53,5 +90,6 @@ const finalJSON = { // Write output file with trailing newline fs.writeFileSync(outputFilePath, `${JSON.stringify(finalJSON, null, 2)}\n`); + // eslint-disable-next-line no-console console.log("✅ JSON generated!"); From a96ed5c4cdcb2cde7a8a42f3d5f62d9583a0a539 Mon Sep 17 00:00:00 2001 From: Kiruthiga A Date: Sun, 29 Mar 2026 12:25:00 +0530 Subject: [PATCH 12/13] Update generateTranslations.js --- scripts/generateTranslations.js | 25 ++++++++++++++++--------- 1 file changed, 16 insertions(+), 9 deletions(-) diff --git a/scripts/generateTranslations.js b/scripts/generateTranslations.js index 5a67dca09..02f45aeb0 100644 --- a/scripts/generateTranslations.js +++ b/scripts/generateTranslations.js @@ -23,7 +23,13 @@ let currentMsgstr = []; let activeField = null; // "msgid" | "msgstr" | null let activeIndex = 0; -const parseQuoted = (raw) => JSON.parse(raw); +const parseQuoted = (raw, lineNum) => { + try { + return JSON.parse(raw); + } catch (err) { + throw new Error(`Failed to parse quoted string at line ${lineNum}: ${raw}\nOriginal error: ${err.message}`); + } +}; const flushEntry = () => { if (currentId) { @@ -38,38 +44,39 @@ const flushEntry = () => { activeIndex = 0; }; -for (const rawLine of lines) { +lines.forEach((rawLine, idx) => { const line = rawLine.trim(); + const lineNum = idx + 1; // for parseQuoted if (!line) { flushEntry(); - continue; + return; // replaces continue } if (/^msgid\s+/.test(line)) { flushEntry(); - currentId = parseQuoted(line.replace(/^msgid\s+/, "")); + currentId = parseQuoted(line.replace(/^msgid\s+/, ""), lineNum); activeField = "msgid"; - continue; + return; } const msgstrMatch = line.match(/^msgstr(?:\[(\d+)\])?\s+(.*)$/); if (msgstrMatch) { activeField = "msgstr"; activeIndex = msgstrMatch[1] ? Number(msgstrMatch[1]) : 0; - currentMsgstr[activeIndex] = parseQuoted(msgstrMatch[2]); - continue; + currentMsgstr[activeIndex] = parseQuoted(msgstrMatch[2], lineNum); + return; } if (line.startsWith('"')) { - const chunk = parseQuoted(line); + const chunk = parseQuoted(line, lineNum); if (activeField === "msgid" && currentId !== null) { currentId += chunk; } else if (activeField === "msgstr") { currentMsgstr[activeIndex] = (currentMsgstr[activeIndex] || "") + chunk; } } -} +}); flushEntry(); From 9947d3b3ff8d5d2558e5f6a1b626346f2586e8bf Mon Sep 17 00:00:00 2001 From: Kiruthiga A Date: Sun, 29 Mar 2026 12:33:14 +0530 Subject: [PATCH 13/13] Update generateTranslations.js --- scripts/generateTranslations.js | 14 ++++++++++---- 1 file changed, 10 insertions(+), 4 deletions(-) diff --git a/scripts/generateTranslations.js b/scripts/generateTranslations.js index 02f45aeb0..93b62205c 100644 --- a/scripts/generateTranslations.js +++ b/scripts/generateTranslations.js @@ -23,16 +23,20 @@ let currentMsgstr = []; let activeField = null; // "msgid" | "msgstr" | null let activeIndex = 0; +// Parse quoted string with error context const parseQuoted = (raw, lineNum) => { try { return JSON.parse(raw); } catch (err) { - throw new Error(`Failed to parse quoted string at line ${lineNum}: ${raw}\nOriginal error: ${err.message}`); + throw new Error( + `Failed to parse quoted string at line ${lineNum}: ${raw}\nOriginal error: ${err.message}`, + ); } }; +// Flush current entry to translations const flushEntry = () => { - if (currentId) { + if (currentId !== null) { translations[currentId] = { msgid: currentId, msgstr: currentMsgstr.length ? currentMsgstr : [""], @@ -44,13 +48,14 @@ const flushEntry = () => { activeIndex = 0; }; +// Process each line lines.forEach((rawLine, idx) => { const line = rawLine.trim(); - const lineNum = idx + 1; // for parseQuoted + const lineNum = idx + 1; // for error messages if (!line) { flushEntry(); - return; // replaces continue + return; } if (/^msgid\s+/.test(line)) { @@ -78,6 +83,7 @@ lines.forEach((rawLine, idx) => { } }); +// Flush any remaining entry flushEntry(); // Prepare final JSON