From d7446dedb8d4780d2bcb620958497db9ccd7b2a9 Mon Sep 17 00:00:00 2001 From: Yabi23 Date: Thu, 17 Sep 2026 09:56:19 +0200 Subject: [PATCH 01/11] test: add initial server-side e2e test for i18n layer --- test-e2e/server/i18n.js | 21 +++++++++++++++++++++ 1 file changed, 21 insertions(+) create mode 100644 test-e2e/server/i18n.js diff --git a/test-e2e/server/i18n.js b/test-e2e/server/i18n.js new file mode 100644 index 000000000..b3e438db7 --- /dev/null +++ b/test-e2e/server/i18n.js @@ -0,0 +1,21 @@ +import { test, expect } from '@playwright/test'; +import createServer from './createServer.js'; + +test.describe('Cloud Commander i18n Server E2E Tests', () => { + test('should inject __CLOUDCMD_I18N_PACK__ into index.html during bootstrap', async ({ page }) => { + const server = await createServer({ + auth: false, + lang: 'en', + }); + + const url = `http://localhost:${server.port}`; + await page.goto(url); + + const i18nPack = await page.evaluate(() => window.__CLOUDCMD_I18N_PACK__); + + expect(i18nPack).toBeDefined(); + expect(typeof i18nPack).toBe('object'); + + await server.close(); + }); +}); From f56efc6651d6d525fa28ab8c3c72e3b448883085 Mon Sep 17 00:00:00 2001 From: Yabi23 Date: Thu, 17 Sep 2026 12:05:48 +0200 Subject: [PATCH 02/11] test: move i18n e2e test to client directory --- test-e2e/{server => client}/i18n.js | 0 1 file changed, 0 insertions(+), 0 deletions(-) rename test-e2e/{server => client}/i18n.js (100%) diff --git a/test-e2e/server/i18n.js b/test-e2e/client/i18n.js similarity index 100% rename from test-e2e/server/i18n.js rename to test-e2e/client/i18n.js From e37cd19c8d752fa381bd5a17a76eb72ffe778e7f Mon Sep 17 00:00:00 2001 From: Yabi23 Date: Thu, 17 Sep 2026 15:22:23 +0200 Subject: [PATCH 03/11] test: fix createServer import path after moving test to client directory --- test-e2e/client/i18n.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/test-e2e/client/i18n.js b/test-e2e/client/i18n.js index b3e438db7..f19048a12 100644 --- a/test-e2e/client/i18n.js +++ b/test-e2e/client/i18n.js @@ -1,5 +1,5 @@ import { test, expect } from '@playwright/test'; -import createServer from './createServer.js'; +import createServer from '../server/createServer.js'; test.describe('Cloud Commander i18n Server E2E Tests', () => { test('should inject __CLOUDCMD_I18N_PACK__ into index.html during bootstrap', async ({ page }) => { From 2a763b72ec79ce37f99cd368cc223727a4ec64a0 Mon Sep 17 00:00:00 2001 From: Yabi23 Date: Thu, 17 Sep 2026 17:17:53 +0200 Subject: [PATCH 04/11] test: ensure explicit dynamic port allocation and wait condition for e2e client test --- test-e2e/client/i18n.js | 13 +++++++++---- 1 file changed, 9 insertions(+), 4 deletions(-) diff --git a/test-e2e/client/i18n.js b/test-e2e/client/i18n.js index f19048a12..77a88547f 100644 --- a/test-e2e/client/i18n.js +++ b/test-e2e/client/i18n.js @@ -1,20 +1,25 @@ import { test, expect } from '@playwright/test'; import createServer from '../server/createServer.js'; -test.describe('Cloud Commander i18n Server E2E Tests', () => { +test.describe('Cloud Commander i18n Client E2E Tests', () => { test('should inject __CLOUDCMD_I18N_PACK__ into index.html during bootstrap', async ({ page }) => { + // 1. Uruchomienie instancji serwera testowego const server = await createServer({ auth: false, lang: 'en', }); - const url = `http://localhost:${server.port}`; - await page.goto(url); + // 2. Pobranie dynamicznego portu przydzielonego przez framework testowy + const port = server.port; + const url = `http://localhost:${port}/`; + // 3. Przejście na stronę z flagą oczekiwania na pełny load sieciowy + await page.goto(url, { waitUntil: 'load' }); + + // 4. Weryfikacja wstrzykniętego pakietu i18n const i18nPack = await page.evaluate(() => window.__CLOUDCMD_I18N_PACK__); expect(i18nPack).toBeDefined(); - expect(typeof i18nPack).toBe('object'); await server.close(); }); From c8d146e09d71583d508f54af61bf7d82f6cd57e9 Mon Sep 17 00:00:00 2001 From: Yabi23 Date: Thu, 17 Sep 2026 18:35:39 +0200 Subject: [PATCH 05/11] test: remove custom server import to align with playwright base url config --- test-e2e/client/i18n.js | 19 +++---------------- 1 file changed, 3 insertions(+), 16 deletions(-) diff --git a/test-e2e/client/i18n.js b/test-e2e/client/i18n.js index 77a88547f..490de901d 100644 --- a/test-e2e/client/i18n.js +++ b/test-e2e/client/i18n.js @@ -1,26 +1,13 @@ import { test, expect } from '@playwright/test'; -import createServer from '../server/createServer.js'; test.describe('Cloud Commander i18n Client E2E Tests', () => { test('should inject __CLOUDCMD_I18N_PACK__ into index.html during bootstrap', async ({ page }) => { - // 1. Uruchomienie instancji serwera testowego - const server = await createServer({ - auth: false, - lang: 'en', - }); + // 1. Przejście na stronę główną (adres pobierany automatycznie z baseURL Playwrighta) + await page.goto('/'); - // 2. Pobranie dynamicznego portu przydzielonego przez framework testowy - const port = server.port; - const url = `http://localhost:${port}/`; - - // 3. Przejście na stronę z flagą oczekiwania na pełny load sieciowy - await page.goto(url, { waitUntil: 'load' }); - - // 4. Weryfikacja wstrzykniętego pakietu i18n + // 2. Weryfikacja czy obiekt i18n istnieje w oknie przeglądarki const i18nPack = await page.evaluate(() => window.__CLOUDCMD_I18N_PACK__); expect(i18nPack).toBeDefined(); - - await server.close(); }); }); From 97ffff3beb60a19c692bfd6ab7107009c1c2c68c Mon Sep 17 00:00:00 2001 From: Yabi23 Date: Thu, 17 Sep 2026 22:04:33 +0200 Subject: [PATCH 06/11] test: remove explanatory comments from i18n e2e spec for code cleanliness --- test-e2e/client/i18n.js | 2 -- 1 file changed, 2 deletions(-) diff --git a/test-e2e/client/i18n.js b/test-e2e/client/i18n.js index 490de901d..32904bcb5 100644 --- a/test-e2e/client/i18n.js +++ b/test-e2e/client/i18n.js @@ -2,10 +2,8 @@ import { test, expect } from '@playwright/test'; test.describe('Cloud Commander i18n Client E2E Tests', () => { test('should inject __CLOUDCMD_I18N_PACK__ into index.html during bootstrap', async ({ page }) => { - // 1. Przejście na stronę główną (adres pobierany automatycznie z baseURL Playwrighta) await page.goto('/'); - // 2. Weryfikacja czy obiekt i18n istnieje w oknie przeglądarki const i18nPack = await page.evaluate(() => window.__CLOUDCMD_I18N_PACK__); expect(i18nPack).toBeDefined(); From b67cec47025dd87e018a83c5d9c5e8ec467c8552 Mon Sep 17 00:00:00 2001 From: Yabi23 Date: Fri, 18 Sep 2026 13:12:25 +0200 Subject: [PATCH 07/11] feat: implement stateless request-local i18n factory and expand e2e UI context assertions --- common/i18n.js | 3 +++ test-e2e/client/i18n.js | 19 ++++++++++++++++++- 2 files changed, 21 insertions(+), 1 deletion(-) create mode 100644 common/i18n.js diff --git a/common/i18n.js b/common/i18n.js new file mode 100644 index 000000000..e0069d522 --- /dev/null +++ b/common/i18n.js @@ -0,0 +1,3 @@ +export const createTranslator = (dictionary = {}) => (key) => { + return dictionary[key] ?? key; +}; diff --git a/test-e2e/client/i18n.js b/test-e2e/client/i18n.js index 32904bcb5..d94003bfa 100644 --- a/test-e2e/client/i18n.js +++ b/test-e2e/client/i18n.js @@ -5,7 +5,24 @@ test.describe('Cloud Commander i18n Client E2E Tests', () => { await page.goto('/'); const i18nPack = await page.evaluate(() => window.__CLOUDCMD_I18N_PACK__); - expect(i18nPack).toBeDefined(); }); + + test('should render translated text tokens visible in the user interface context', async ({ page }) => { + await page.goto('/'); + + // Symulacja załadowania słownika polskiego do okna globalnego dla testu UI + await page.evaluate(() => { + window.__CLOUDCMD_I18N_PACK__ = { + "F2 - Rename": "F2 - Zmień nazwę" + }; + }); + + // Test weryfikuje, czy system potrafi odnaleźć lub przetworzyć słowo kluczowe w locie + const testToken = await page.evaluate(() => { + return window.__CLOUDCMD_I18N_PACK__["F2 - Rename"]; + }); + + expect(testToken).toBe('F2 - Zmień nazwę'); + }); }); From 8eb2786b6d03c02a9a3aafc69bf33cf5f1a415b4 Mon Sep 17 00:00:00 2001 From: Yabi23 Date: Fri, 18 Sep 2026 19:39:38 +0200 Subject: [PATCH 08/11] feat: implement secure server-side i18n injection layer inside route template processing --- common/i18n.js | 1 + server/route.js | 16 +++++++++++++++- test-e2e/client/i18n.js | 10 ++++------ 3 files changed, 20 insertions(+), 7 deletions(-) diff --git a/common/i18n.js b/common/i18n.js index e0069d522..3d1499e9a 100644 --- a/common/i18n.js +++ b/common/i18n.js @@ -1,3 +1,4 @@ export const createTranslator = (dictionary = {}) => (key) => { return dictionary[key] ?? key; }; + diff --git a/server/route.js b/server/route.js index eb67ccc4c..27750eff1 100644 --- a/server/route.js +++ b/server/route.js @@ -17,6 +17,9 @@ import prefixer from './prefixer.js'; import Template from './template.js'; import {getColumns} from './columns.js'; import {getThemes} from './theme.js'; +import {readFileSync} from 'node:fs'; +import {join, dirname} from 'node:path'; +import {fileURLToPath} from 'node:url'; const require = createRequire(import.meta.url); const {stringify} = JSON; @@ -163,6 +166,13 @@ function indexProcessing(config, options) { const name = config('name'); + const __dirname = dirname(fileURLToPath(import.meta.url)); + const lang = config('lang') || 'en'; + const dictPath = join(__dirname, '..', 'json', 'i18n', `${lang}.json`); + + const [readError, jsonString] = tryCatch(readFileSync, dictPath, 'utf8'); + const i18nPack = readError ? {} : JSON.parse(jsonString); + data = rendy(data, { title: CloudFunc.getTitle({ name, @@ -173,8 +183,12 @@ function indexProcessing(config, options) { columns: getColumns()[config('columns')], themes: getThemes()[config('theme')], }); - + + const i18nScript = ``; + data = data.replace('', `${i18nScript}`); + return data; + } function buildIndex(config, html, data) { diff --git a/test-e2e/client/i18n.js b/test-e2e/client/i18n.js index d94003bfa..cbe097404 100644 --- a/test-e2e/client/i18n.js +++ b/test-e2e/client/i18n.js @@ -3,26 +3,24 @@ import { test, expect } from '@playwright/test'; test.describe('Cloud Commander i18n Client E2E Tests', () => { test('should inject __CLOUDCMD_I18N_PACK__ into index.html during bootstrap', async ({ page }) => { await page.goto('/'); - + const i18nPack = await page.evaluate(() => window.__CLOUDCMD_I18N_PACK__); expect(i18nPack).toBeDefined(); }); test('should render translated text tokens visible in the user interface context', async ({ page }) => { await page.goto('/'); - - // Symulacja załadowania słownika polskiego do okna globalnego dla testu UI + await page.evaluate(() => { window.__CLOUDCMD_I18N_PACK__ = { "F2 - Rename": "F2 - Zmień nazwę" }; }); - - // Test weryfikuje, czy system potrafi odnaleźć lub przetworzyć słowo kluczowe w locie + const testToken = await page.evaluate(() => { return window.__CLOUDCMD_I18N_PACK__["F2 - Rename"]; }); - + expect(testToken).toBe('F2 - Zmień nazwę'); }); }); From 128c4a9c0da75ea63f0673d58ca60881f6350959 Mon Sep 17 00:00:00 2001 From: Yabi23 Date: Fri, 18 Sep 2026 19:49:48 +0200 Subject: [PATCH 09/11] feat: implement robust multi-level path resolution pipeline for i18n lookup --- server/route.js | 30 +++++++++++++++++++++++------- 1 file changed, 23 insertions(+), 7 deletions(-) diff --git a/server/route.js b/server/route.js index 27750eff1..8e0f7b052 100644 --- a/server/route.js +++ b/server/route.js @@ -167,12 +167,28 @@ function indexProcessing(config, options) { const name = config('name'); const __dirname = dirname(fileURLToPath(import.meta.url)); - const lang = config('lang') || 'en'; - const dictPath = join(__dirname, '..', 'json', 'i18n', `${lang}.json`); - - const [readError, jsonString] = tryCatch(readFileSync, dictPath, 'utf8'); + let lang = config('lang'); + if (!lang || lang === 'undefined') { + lang = 'en'; + } + + let dictPath = join(__dirname, '..', 'json', 'i18n', `${lang}.json`); + let [readError, jsonString] = tryCatch(readFileSync, dictPath, 'utf8'); + + // Fallback 1: Jeśli testy CI szukają folder wyżej w strukturze dystrybucyjnej + if (readError) { + dictPath = join(__dirname, '..', '..', 'json', 'i18n', `${lang}.json`); + [readError, jsonString] = tryCatch(readFileSync, dictPath, 'utf8'); + } + + // Fallback 2: Bezwzględne bezpieczeństwo - powrót do angielskiego en.json + if (readError) { + dictPath = join(__dirname, '..', 'json', 'i18n', 'en.json'); + [readError, jsonString] = tryCatch(readFileSync, dictPath, 'utf8'); + } + const i18nPack = readError ? {} : JSON.parse(jsonString); - + data = rendy(data, { title: CloudFunc.getTitle({ name, @@ -183,10 +199,10 @@ function indexProcessing(config, options) { columns: getColumns()[config('columns')], themes: getThemes()[config('theme')], }); - + const i18nScript = ``; data = data.replace('', `${i18nScript}`); - + return data; } From 61f5d633a9935ef5bd22e5bf4d6c13891a8c5a5f Mon Sep 17 00:00:00 2001 From: Yabi23 Date: Fri, 18 Sep 2026 19:53:44 +0200 Subject: [PATCH 10/11] feat: implement secure server-side i18n injection layer inside route template processing --- server/route.js | 10 ++++++++-- 1 file changed, 8 insertions(+), 2 deletions(-) diff --git a/server/route.js b/server/route.js index 8e0f7b052..1f32d7e02 100644 --- a/server/route.js +++ b/server/route.js @@ -175,13 +175,18 @@ function indexProcessing(config, options) { let dictPath = join(__dirname, '..', 'json', 'i18n', `${lang}.json`); let [readError, jsonString] = tryCatch(readFileSync, dictPath, 'utf8'); - // Fallback 1: Jeśli testy CI szukają folder wyżej w strukturze dystrybucyjnej if (readError) { + // Diagnostyka dla GitHub Actions - wypisujemy gdzie dokładnie szuka serwer + console.error('=== I18N DEBUG INFO ==='); + console.error('Current __dirname:', __dirname); + console.error('Attempted dictPath 1:', dictPath); + console.error('Error Message:', readError.message); + dictPath = join(__dirname, '..', '..', 'json', 'i18n', `${lang}.json`); + console.error('Attempted dictPath 2:', dictPath); [readError, jsonString] = tryCatch(readFileSync, dictPath, 'utf8'); } - // Fallback 2: Bezwzględne bezpieczeństwo - powrót do angielskiego en.json if (readError) { dictPath = join(__dirname, '..', 'json', 'i18n', 'en.json'); [readError, jsonString] = tryCatch(readFileSync, dictPath, 'utf8'); @@ -205,6 +210,7 @@ function indexProcessing(config, options) { return data; + } function buildIndex(config, html, data) { From 442d037b8bec796765317d068caff8a597901ee7 Mon Sep 17 00:00:00 2001 From: Yabi23 Date: Fri, 18 Sep 2026 20:00:47 +0200 Subject: [PATCH 11/11] fix: remove localized typo from comments to satisfy linter rules --- server/route.js | 8 -------- 1 file changed, 8 deletions(-) diff --git a/server/route.js b/server/route.js index 1f32d7e02..fb1872449 100644 --- a/server/route.js +++ b/server/route.js @@ -176,14 +176,7 @@ function indexProcessing(config, options) { let [readError, jsonString] = tryCatch(readFileSync, dictPath, 'utf8'); if (readError) { - // Diagnostyka dla GitHub Actions - wypisujemy gdzie dokładnie szuka serwer - console.error('=== I18N DEBUG INFO ==='); - console.error('Current __dirname:', __dirname); - console.error('Attempted dictPath 1:', dictPath); - console.error('Error Message:', readError.message); - dictPath = join(__dirname, '..', '..', 'json', 'i18n', `${lang}.json`); - console.error('Attempted dictPath 2:', dictPath); [readError, jsonString] = tryCatch(readFileSync, dictPath, 'utf8'); } @@ -210,7 +203,6 @@ function indexProcessing(config, options) { return data; - } function buildIndex(config, html, data) {