feat(legal): bilingual (RU + EN) legal pages with a language + theme switcher
CI / changes (pull_request) Successful in 2s
CI / unit (pull_request) Successful in 10s
CI / integration (pull_request) Successful in 21s
CI / ui (pull_request) Successful in 1m14s
CI / conformance (pull_request) Successful in 10s
CI / gate (pull_request) Successful in 0s
CI / deploy (pull_request) Successful in 1m48s

Add English versions of the privacy policy, EULA and offer (ui/legal/*_en.md) and render each legal page as one self-contained bilingual document: both language bodies plus a header language toggle and theme toggle (no back), a small inline ES5 script that switches language client-side (no reload, default Russian + persisted) and applies the theme (system default + persisted override) — mirroring the landing. The offer's English view transliterates the Russian product names spliced from the live catalog.

renderLegalHtml now takes both language sources; renderOffer splices the price list into both; the renderer bakes and reads the _en sources and pre-renders the static pages at boot. Also wrap the /offer/ contour probe in the same retry+timeout as the legal probe (the offer page is now bilingual and larger). Docs + renderer/ui tests updated.
This commit is contained in:
Ilia Denisov
2026-07-13 13:22:56 +02:00
parent 7df078f5ed
commit de5ab9186c
16 changed files with 1018 additions and 140 deletions
+14 -8
View File
@@ -1,21 +1,27 @@
// Unit test for the static legal pages: renderPrivacy / renderEula wrap the owner-edited markdown in
// the shared legal-page chrome (bundled renderLegalHtml) with the right title + canonical URL and no
// dynamic data. The prose rendering itself is unit-tested in ui/ (offer.test.ts); here we assert the
// per-page presets.
// Unit test for the static legal pages: renderPrivacy / renderEula wrap the owner-edited RU + EN
// markdown in the shared bilingual legal-page chrome (bundled renderLegalHtml) with the right title +
// canonical URL and no dynamic data. The prose rendering itself is unit-tested in ui/ (offer.test.ts);
// here we assert the per-page presets and that both languages reach the document.
import test from 'node:test';
import assert from 'node:assert/strict';
import { renderPrivacy, renderEula } from '../src/legal.mjs';
test('renderPrivacy wraps the markdown in the privacy-page chrome', () => {
const html = renderPrivacy('# Политика конфиденциальности\n\n**1.1.** ИНН 290210610742.');
test('renderPrivacy wraps the RU + EN markdown in the privacy-page chrome', () => {
const html = renderPrivacy(
'# Политика конфиденциальности\n\n**1.1.** ИНН 290210610742.',
'# Privacy Policy\n\n**1.1.** TIN 290210610742.',
);
assert.ok(html.includes('<!doctype html>'), 'a standalone document');
assert.ok(html.includes('<title>Политика конфиденциальности — Эрудит</title>'), 'the privacy title');
assert.ok(html.includes('data-title-en="Privacy Policy — Erudit"'), 'the English title for the toggle');
assert.ok(html.includes('href="https://erudit-game.ru/privacy/"'), 'the privacy canonical URL');
assert.ok(html.includes('290210610742'), 'the prose reached the document');
assert.ok(html.includes('<main data-lang="en" hidden>'), 'the English body is present, hidden by default');
});
test('renderEula wraps the markdown in the EULA-page chrome', () => {
const html = renderEula('# Лицензионное соглашение\n\nтекст');
test('renderEula wraps the RU + EN markdown in the EULA-page chrome', () => {
const html = renderEula('# Лицензионное соглашение\n\nтекст', '# Terms of Use\n\ntext');
assert.ok(html.includes('<title>Пользовательское соглашение — Эрудит</title>'), 'the EULA title');
assert.ok(html.includes('data-title-en="Terms of Use — Erudit"'), 'the English title for the toggle');
assert.ok(html.includes('href="https://erudit-game.ru/eula/"'), 'the EULA canonical URL');
});
+13 -11
View File
@@ -1,7 +1,7 @@
// Unit test for the public-offer page assembly: renderOffer splices the backend's price-list
// markdown into the offer at the pricing marker and renders it with the shared renderOfferHtml
// (bundled from ui/src/lib/offer). The prose rendering itself is unit-tested in ui/ (offer.test.ts);
// here we assert the substitution and that the tables reach the rendered HTML.
// Unit test for the public-offer page assembly: renderOffer splices the backend's price-list markdown
// into BOTH language sources of the offer at the pricing marker and renders them with the shared
// renderOfferHtml (bundled from ui/src/lib/offer). The prose rendering itself is unit-tested in ui/
// (offer.test.ts); here we assert the substitution reaches both languages and the tables render.
import test from 'node:test';
import assert from 'node:assert/strict';
import { renderOffer, pricingMarker } from '../src/offer.mjs';
@@ -11,19 +11,21 @@ const tables =
'| --- | --- | --- | --- |\n' +
'| 50 «Фишек» | 200.00 | 30 | 100 |';
test('splices the price list into the offer and renders the tables to HTML', () => {
const md = `# Публичная оферта\n\n**4.4.** Стоимость Товаров:\n\n${pricingMarker}\n`;
const html = renderOffer(md, tables);
// The marker is gone and the projected table reached the document as a real HTML table.
assert.ok(!html.includes(pricingMarker), 'pricing marker must be substituted');
test('splices the price list into both language sources and renders the tables', () => {
const ru = `# Публичная оферта\n\n**4.4.** Стоимость Товаров:\n\n${pricingMarker}\n`;
const en = `# Public offer\n\n**4.4.** Cost of the Goods:\n\n${pricingMarker}\n`;
const html = renderOffer(ru, en, tables);
// The marker is gone from both languages and the projected table reached the document.
assert.ok(!html.includes(pricingMarker), 'the pricing marker must be substituted in both languages');
assert.ok(html.includes('<table>'), 'the price list must render as an HTML table');
assert.ok(html.includes('<td>200.00</td>'), 'a rouble price cell must be present');
assert.ok(html.includes('50 «Фишек»'), 'the product title must be present');
// The offer chrome from the shared renderer is intact.
assert.ok(html.includes('<h1>Публичная оферта</h1>'), 'the Russian heading rendered');
assert.ok(html.includes('<h1>Public offer</h1>'), 'the English heading rendered');
assert.ok(html.includes('<!doctype html>'));
});
test('a "$" in a title is substituted literally, not as a replacement pattern', () => {
const html = renderOffer(`x ${pricingMarker} y`, '| $5 pack | 1 |');
const html = renderOffer(`ru ${pricingMarker}`, `en ${pricingMarker}`, '| $5 pack | 1 |');
assert.ok(html.includes('$5 pack'), 'a "$" in the tables must survive substitution');
});