08 · i18n · Native EN strategy + RU↔EN switch¶
Зачем этот файл¶
GOLDJAXE — холдинг для глобального B2B/B2C. Native English обязателен для: - investor-portal (US/EU LP audience) - .ai/.com/.org marketing pages - Certificate (LinkedIn share-friendly) - OG-images social sharing - Future GATE / Syndicate enterprise customers
Native ≠ machine translation. Также есть branding immutable (термины не переводятся: [ ACADEMY ], [ VISION ], INITIATION PROTOCOL).
Этот файл — правила переключения, decision tree «переводить или branding», JS-паттерны для динамических элементов (date / numbers / currency).
Branding vs translatable · decision tree¶
НЕ переводить (branding immutable)¶
| Категория | Примеры | Почему |
|---|---|---|
| Названия продуктов | goldjaxe.com / .ai / .org / academy / syndicate / gate |
Бренд |
| Wordmarks секций | [ ACADEMY ] / [ VISION ] / [ INNOVATIONS ] / [ VAULT ] |
Часть брендинга, square brackets identity |
| Course codes | INITIATION PROTOCOL / NEUROOPERATOR / AI-ARCHITECT / GOD MODE VIP / INVESTOR PORTAL |
Названия как в IT industry — Photoshop, Linux Kernel etc. |
| Tier коды | L0 / L1 / L2 / L3 / L4 / L5 |
Internal classification |
| Tech idents | JWT / cookie / Stripe / SWIFT / DDEX ERN / PWA / API |
Industry standard terms |
| Brand caps | ▸ CERTIFICATE OF COMPLETION / ▸ SERIAL · VERIFY / ▸ ISSUED TO |
Premium signature, EN universal |
| URLs | verify.goldjaxe.ai / academy.goldjaxe.ai |
Технические endpoints |
| Currency codes | USD / EUR / RUB / AED |
International convention |
Переводить (native EN/RU)¶
| Категория | RU | EN |
|---|---|---|
| UI кнопки | Купить / Продолжить / Скачать |
Buy / Continue / Download |
| Status labels | Активен / Заблокировано / В процессе |
Active / Locked / In progress |
| Cert text | ВЫДАН / Модулей / Архитектура Превосходства |
ISSUED TO / Modules / Architecture of Excellence |
| Section headings | Все протоколы / Мои протоколы |
All protocols / My protocols |
| Form fields | Имя / Email / Телефон |
Name / Email / Phone |
| Footer copyright | © 2026 GOLDJAXE |
© 2026 GOLDJAXE (символ universal) |
| Course descriptions | Понимание AI и план... |
Understanding AI and a plan... |
| Error messages | Что-то пошло не так |
Something went wrong |
Edge case · brand caps в RU контексте¶
▸ CERTIFICATE OF COMPLETION — это branding (premium signature) или translatable?
Решение CEO 2026-05-06: оставлять EN в обеих версиях (это premium международная signature как Made in USA, Premium Edition). Альтернатива — переводить ▸ СВИДЕТЕЛЬСТВО О ЗАВЕРШЕНИИ ломает premium feel.
См. feedback_certificate_canonical.md v4.4.17 update.
data-i18n + theme-lang.js pattern¶
Архитектура¶
window.ECO_I18N — глобальный namespace:
// ru.js
window.ECO_I18N = window.ECO_I18N || {};
window.ECO_I18N.ru = {
"common": { "back": "← Назад", "next": "Дальше →", ... },
"academy_certificate": { "issued_to": "▸ ВЫДАН", ... },
"academy_courses": { ... },
// ... namespace per screen / section
};
// en.js — параллельная structure
window.ECO_I18N.en = {
"common": { "back": "← Back", "next": "Next →", ... },
"academy_certificate": { "issued_to": "▸ ISSUED TO", ... },
"academy_courses": { ... },
};
HTML markup¶
<!-- Базовый -->
<div data-i18n="academy_certificate.issued_to">▸ ВЫДАН</div>
<!-- Inline span внутри dynamic content -->
<div class="cert-stat-num">4<span data-i18n="academy_certificate.stat_hours_unit">ч</span></div>
<!-- Кнопка с inline-content -->
<button data-i18n="academy_certificate.toolbar_print" onclick="window.print()">🖨 Печать</button>
theme-lang.js (apply mechanism)¶
theme-lang.js слушает клик [data-lang-btn] → меняет html[data-lang] + html[lang] → MutationObserver применяет text из window.ECO_I18N[lang][key] ко всем [data-i18n] элементам.
Default lang определяется script в <head>:
var l = localStorage.getItem('eco-lang') || 'ru';
document.documentElement.setAttribute('data-lang', l);
document.documentElement.lang = l;
Namespace conventions¶
common.X— кросс-экранные общие keys (sign in / log out / save / cancel)[product]_[screen].X— per-screen keys (academy_certificate / gate_dashboard / syndicate_landing)marketing.X— для landing pages (если кросс-product)
При добавлении нового экрана — создавать новый namespace [product]_[screen], не размывать в common.
Анти-pattern¶
❌ Hardcoded text без data-i18n атрибута (не переключится)
❌ JS textContent = '...' без проверки lang (override i18n)
❌ Дублирование keys в разных namespaces (single source of truth)
❌ Missing key в en.js (приведёт к видимому RU тексту в EN режиме)
4 паттерна i18n атрибутов (canonical · v4.7.x)¶
theme-lang.js поддерживает 3 атрибутa + есть bilingual JS-namespace для data в JS-литералах. Применяй правильный паттерн в зависимости от типа контента.
1. data-i18n · text content (90% случаев)¶
Для статичных текстов в HTML без inline-разметки.
<button data-i18n="academy_quiz.btn_check">[ ПРОВЕРИТЬ → ]</button>
<div class="card-title" data-i18n="academy_settings.card_account_title">Аккаунт</div>
Когда: простой текст, без <strong>, <code>, <em> внутри.
2. data-i18n-html · innerHTML с inline-разметкой¶
Для текстов с inline-эмфазами (<strong>, <code>, <em>, <a>).
<p data-i18n-html="academy_lesson.content_p2">
Первый принцип Архитектора — <strong>тот же подход, но к бизнес-процессам</strong>.
</p>
<p data-i18n-html="academy_lesson.content_with_meta">
Время вашего участия: <code>0 минут</code>. Работает 24/7.
</p>
В словаре пишется как HTML-строка с тегами:
"content_p2": "Первый принцип Архитектора — <strong>тот же подход, но к бизнес-процессам</strong>.",
"content_with_meta": "Время вашего участия: <code>0 минут</code>. Работает 24/7."
Когда: есть inline <strong> или <code> внутри текста. ⚠️ XSS-safe только для self-controlled словарей (наши i18n/*.js).
3. data-i18n-attr · атрибуты (placeholder, title, aria-label)¶
Для атрибутов формы, тултипов, accessibility.
<input type="text"
data-i18n-attr="placeholder:academy_settings.field_telegram_placeholder"
placeholder="@username" />
<button
data-i18n-attr="aria-label:academy_quiz.modal_close_aria,title:common.close"
aria-label="Закрыть"
title="Закрыть">×</button>
Синтаксис: attr1:key1,attr2:key2 (запятая-разделитель пар).
Когда: placeholder, title, aria-label, alt — любой атрибут вне textContent.
4. Bilingual JS-namespace · для data в JS-литералах¶
Когда в HTML текст вставляется через JS (innerHTML, textContent) и не покрывается data-i18n. Типичные случаи: QUIZ_DATA, MODULES_DATA, PATHS_DATA, dynamic tier-name.
Анти-pattern (не делать):
const QUIZ_DATA = [
{
question: "Какой процесс лучше всего подходит...",
options: ["...", "...", "...", "..."],
explanation: "Правильный ответ: 3...",
correct: 2
},
...
];
Canonical (v4.7.3 academy_quiz_data):
// 1. В JS только structure — id + answer index + type
const QUIZ_STRUCTURE = [
{ id: 'q1', correct: 2 },
{ id: 'q2', correct: 0 },
// ...
];
// 2. Тексты — отдельный namespace в i18n словарях
// ru.js
"academy_quiz_data": {
"q1_question": "Какой процесс <strong>лучше всего</strong>...",
"q1_opt0": "Стратегические переговоры с ключевым клиентом",
"q1_opt1": "...",
"q1_explanation": "<strong>Правильный ответ: 3.</strong>...",
// ... q2_*, q3_*, q4_*, q5_*
}
// en.js симметрично
// 3. Lookup helper в HTML JS
function getQuestionData(idx) {
const s = QUIZ_STRUCTURE[idx];
const lang = document.documentElement.getAttribute('data-lang') === 'en' ? 'en' : 'ru';
const dict = window.ECO_I18N[lang].academy_quiz_data;
return {
question: dict[s.id + '_question'],
options: [0,1,2,3].map(i => dict[s.id + '_opt' + i]),
explanation: dict[s.id + '_explanation'],
correct: s.correct
};
}
4 канонических bilingual namespace в академии (v4.7.5):
| Namespace | Источник | Ключи × 2 = JS-литералов | Релиз |
|---|---|---|---|
onboarding_paths |
4 пути × 9 полей (area / module / artifact / 3 steps × {title+desc}) | 36 × 2 = 72 | v4.7.0 |
academy_completed_modules |
6 модулей × 3 поля (title / nextTitle / nextDesc) | 18 × 2 = 36 | v4.7.0 |
academy_quiz_data |
5 вопросов × 6 полей (question / 4 options / explanation) | 30 × 2 = 60 | v4.7.3 |
academy_lesson_modules |
6 модулей × 4-5 lesson titles | 30 × 2 = 60 | v4.7.4 |
Сумма: 114 ключей × 2 = 228 переведённых JS-литералов через bilingual namespace pattern.
Когда применять: если в JS массиве/объекте есть RU-only тексты которые рендерятся в DOM. Если массив < 5 текстов — можно через data-i18n и static HTML, если ≥ 5 — bilingual namespace.
Reactive lang switching · MutationObserver pattern¶
theme-lang.js applyLang() перерисовывает все [data-i18n] / [data-i18n-html] / [data-i18n-attr] элементы автоматически. Но JS-rendered контент (tier-chip, форматированные даты, прогресс «8 / 29 lessons», динамические tx-rows) — нет. Для них применяй MutationObserver pattern.
Canonical скелет¶
(function() {
function getLang() {
const l = document.documentElement.getAttribute('data-lang')
|| document.documentElement.lang
|| 'ru';
return l === 'en' ? 'en' : 'ru';
}
function getDict(ns) {
const lang = getLang();
return (window.ECO_I18N && window.ECO_I18N[lang] && window.ECO_I18N[lang][ns]) || {};
}
function applyLangAware() {
const lang = getLang();
const d = getDict('academy_billing');
// Tier chip
document.getElementById('user-tier-chip').textContent =
userTier + ' · ' + (lang === 'en' ? 'ACTIVE' : 'АКТИВЕН');
// Date с locale
const locale = lang === 'en' ? 'en-GB' : 'ru-RU';
document.getElementById('tier-from').textContent =
tierFromDate.toLocaleDateString(locale, { day: 'numeric', month: 'long', year: 'numeric' });
// Re-render dynamic rows из i18n словаря
rerenderTransactions(lang, d);
}
// Initial render
applyLangAware();
// Reactive · re-apply on lang switch без перезагрузки страницы
new MutationObserver(applyLangAware).observe(document.documentElement, {
attributes: true,
attributeFilter: ['lang', 'data-lang']
});
})();
Применено в академии (v4.7.0–v4.7.4): dashboard, lesson-completed, profile, login, auth-magic-link, billing, lesson-quiz, lesson-viewer, settings.
Когда нужен MutationObserver¶
- Tier chip
L0 · АКТИВЕН ↔ L0 · ACTIVE - Даты с locale (
15 апреля 2026 ↔ 15 April 2026) - Тарифные суффиксы (
★ ФЛАГМАН ↔ ★ FLAGSHIP) через replace - Пагинация / прогресс с pluralization
- Tx-rows / lesson-rows / комментарии rendered через innerHTML
- Quiz options re-render при текущем вопросе (если bilingual namespace)
- Confirm/alert тексты в onclick handlers (lookup в момент клика)
Когда НЕ нужен¶
- Статичный текст в HTML с
data-i18n— theme-lang.js делает это сам - Кнопки и labels с
data-i18nатрибутами - Атрибуты с
data-i18n-attr
In-form language switchers · window.ecoLang.apply()¶
Если в форме (как в settings.html секция «Язык интерфейса») есть собственный switcher [data-page-lang="ru/en"] помимо header [data-lang-btn] — он должен дёргать window.ecoLang.apply(l), а не ставить attr вручную.
❌ Анти-pattern (v4.7.1 баг · CEO-feedback)¶
// Не работает реактивно — body не перерисуется до reload
document.querySelectorAll('[data-page-lang]').forEach(btn => {
btn.addEventListener('click', () => {
const l = btn.getAttribute('data-page-lang');
document.documentElement.setAttribute('data-lang', l); // <-- attr меняется
localStorage.setItem('eco-lang', l);
// Header switcher активность синхронизирована, но
// theme-lang.js applyLang() НЕ ВЫЗВАН → body остаётся на старом языке
});
});
Почему ломалось: theme-lang.js слушает только [data-lang-btn] clicks (binds в init()), не attribute-mutations. Мой ручной setAttribute('data-lang', l) атрибут устанавливал, но re-render data-i18n элементов не запускался.
✅ Canonical (v4.7.2 fix)¶
document.querySelectorAll('[data-page-lang]').forEach(btn => {
btn.addEventListener('click', () => {
const l = btn.getAttribute('data-page-lang');
if (window.ecoLang && window.ecoLang.apply) {
window.ecoLang.apply(l); // ✓ atomic: attr + localStorage + applyLang() + header sync
} else {
// Fallback если theme-lang.js не загрузился
document.documentElement.setAttribute('data-lang', l);
try { localStorage.setItem('eco-lang', l); } catch(e) {}
}
// Active state на самом form-switcher (header sync делает theme-lang.js)
document.querySelectorAll('[data-page-lang]').forEach(b =>
b.classList.toggle('eco-lang-btn-active', b.getAttribute('data-page-lang') === l)
);
});
});
window.ecoLang.apply() — public API экспортированный из theme-lang.js (строка 141 модуля). Atomic операция: устанавливает attr + localStorage + перерисовывает все [data-i18n] + синхронизирует active state header switcher.
Lang-aware JS-rendered content без if(user) блока¶
Legacy паттерн: chip / date / tier-name обновлялись внутри if (user) { ... }. Если user === null (demo-режим без логина) — chip/date оставались на дефолтном RU при EN-переключении.
❌ Анти-pattern (v4.7.1 legacy)¶
const user = window.EcoState.getUser();
if (user) {
// Только если залогинен — chip обновляется lang-aware
document.getElementById('user-tier-chip').textContent =
user.tier + ' · ' + (document.documentElement.lang === 'en' ? 'ACTIVE' : 'АКТИВЕН');
document.getElementById('field-name').value = user.name;
// ...
}
// Без user chip остаётся "L0 · АКТИВЕН" hardcoded в HTML, не реагирует на lang
✅ Canonical (v4.7.2 fix)¶
const user = window.EcoState && window.EcoState.getUser();
const userTier = user && user.tier ? user.tier : 'L0'; // ← дефолт L0
// User-зависимое — отдельный if (user) блок
if (user) {
document.getElementById('user-initial').textContent = (user.name || 'С').charAt(0).toUpperCase();
document.getElementById('field-name').value = user.name || '';
document.getElementById('field-email').value = user.email || '';
}
// Lang-aware — ВНЕ if (user), работает в demo тоже
function applyTierChip() {
const lang = document.documentElement.getAttribute('data-lang') === 'en' ? 'en' : 'ru';
document.getElementById('user-tier-chip').textContent =
userTier + ' · ' + (lang === 'en' ? 'ACTIVE' : 'АКТИВЕН');
}
applyTierChip();
new MutationObserver(applyTierChip).observe(document.documentElement, {
attributes: true,
attributeFilter: ['lang', 'data-lang']
});
Правило: lang-aware UI работает всегда (с user и без). Внутри if (user) — только user-specific data (имя, email, mock-history).
Validation · symmetry + JSON parse¶
Перед commit/push — обязательная статическая валидация.
Скрипт-валидатор (Python)¶
import re, json
for f in ['ui-kit/i18n/ru.js', 'ui-kit/i18n/en.js']:
s = open(f).read()
m = re.search(r'window\.ECO_I18N\.\w+ = (\{.*\});', s, re.DOTALL)
d = json.loads(m.group(1)) # parse валидность
nsk = sum(len(v) if isinstance(v, dict) else 1 for v in d.values())
print(f'{f}: ✓ {len(d)} namespaces · {nsk} keys')
# Symmetry RU vs EN
ru = json.loads(re.search(r'window\.ECO_I18N\.\w+ = (\{.*\});', open('ui-kit/i18n/ru.js').read(), re.DOTALL).group(1))
en = json.loads(re.search(r'window\.ECO_I18N\.\w+ = (\{.*\});', open('ui-kit/i18n/en.js').read(), re.DOTALL).group(1))
for ns in ['academy_quiz', 'academy_quiz_data', 'academy_lesson_modules']:
rk = set(ru.get(ns, {}).keys())
ek = set(en.get(ns, {}).keys())
diff = (rk - ek) | (ek - rk)
print(f'{ns}: ru={len(rk)} en={len(ek)} ' + ('✓ symmetric' if not diff else f'⚠ diff={diff}'))
Грамотная проверка покрытия¶
# data-i18n count в HTML
grep -c 'data-i18n' ecosystem-prototype/academy/lesson-quiz.html
# ?v= unique versions (должна быть одна свежая)
grep -oE '\?v=[0-9.]+' ecosystem-prototype/academy/lesson-quiz.html | sort -u
# RU lines outside i18n (sanity check after localization)
python3 -c "
import re
with open('lesson-quiz.html') as fh: s = fh.read()
clean = re.sub(r'<style[\s\S]*?</style>', '', s)
clean = re.sub(r'<script[\s\S]*?</script>', '', clean)
clean = re.sub(r'<[^>]+>', ' ', clean)
ru = [l.strip() for l in clean.split('\n') if re.search(r'[А-Яа-я]{3,}', l)]
print(f'{len(ru)} RU lines remain (should be ≤ data-i18n count)')
"
Метрика: data-i18n count ≥ RU lines count — иначе непокрытый RU выпрыгнет в EN режиме.
Verification · Chrome MCP testing pipeline¶
После HTML/CSS/JS правок — обязательная live-проверка через Chrome MCP (или ручной reload).
Скелет batch-проверки¶
// 1. Navigate с cache-bust
{ name: "navigate", input: { url: "http://localhost:8088/ecosystem-prototype/academy/lesson-quiz.html?_nocache=473", tabId } }
// 2. Wait для DOM ready
{ name: "computer", input: { action: "wait", duration: 2, tabId } }
// 3. Click EN button + JS-eval ключевых элементов
{ name: "javascript_tool", input: { action: "javascript_exec", text: "document.querySelector('[data-lang-btn=\"en\"]').click(); JSON.stringify({lang: document.documentElement.getAttribute('data-lang'), h1: document.querySelector('[data-i18n=\"academy_quiz.h1\"]')?.textContent, ...})", tabId }}
// 4. Screenshot для visual confirm
{ name: "computer", input: { action: "screenshot", tabId } }
Chrome MCP в browser_batch — atomic-операция¶
Используй mcp__Claude_in_Chrome__browser_batch для serialization (navigate → wait → click → screenshot одной транзакцией).
Что проверять¶
- [ ] Все
data-i18nэлементы переключились (черезObject.keys(window.ECO_I18N.en[ns])) - [ ] Bilingual JS-namespace загружен (
Object.keys(window.ECO_I18N.en.academy_quiz_data).length > 0) - [ ]
data-i18n-attrplaceholder/title переключились - [ ]
data-i18n-htmlсохранил inline<strong>теги - [ ] MutationObserver pattern работает (chip/date/tier-name обновляются)
- [ ] In-form lang switcher (если есть) работает реактивно
- [ ] No console errors
Cache invalidation · gotchas¶
?v= query bump — обязателен при правке словарей¶
При правке i18n/ru.js или i18n/en.js — обязательно поднимаешь ?v= на всех HTML-экранах которые их используют. Иначе браузер берёт старый закэшированный словарь и видит data-i18n ключи без перевода (фаллбек на default text).
# Bump на всех затронутых HTML
sed -i '' 's/?v=4.7.3.1/?v=4.7.4.1/g' ecosystem-prototype/academy/lesson-viewer.html
Chrome агрессивно кэширует HTML¶
Cache-Control: no-store в server response не всегда обходится Chrome. Симптом: после Edit + reload браузер показывает старый HTML.
Решения (по приоритету):
1. Force-reload Ctrl+Shift+R / Cmd+Shift+R (или location.reload(true) в JS)
2. URL parameter ?_nocache=N — самый надёжный для testing (Chrome видит уникальный URL → fetch заново)
3. DevTools → Network → Disable cache (для стабильной работы во время разработки)
В Chrome MCP testing pipeline всегда добавляй ?_nocache=N к URL.
Stale mirror server gotcha¶
Серверы (Python http.server, nginx) могут раздавать mirror snapshot вместо актуального working tree. Симптом: curl показывает старый файл, хотя на диске новый.
# Проверка cwd процесса
lsof -p $(ps aux | grep 'serve.py' | grep -v grep | awk '{print $2}' | head -1) | grep cwd
# Должен указывать на правильный design.goldjaxe.ai/ , не на /private/tmp/goldjaxe-mirror/
Прецедент v4.7.1: сервер на 8088 имел cwd /private/tmp/goldjaxe-mirror/ (snapshot 2026-05-04). Я давал CEO ссылки на 8088 — он видел stale файлы и считал что мои правки не применились. Fix: перезапуск с явным cd ROOT && python3 serve.py 8088.
Content body rule · CEO-feedback v4.7.4¶
«Это API placeholder» / «это data, не UI» — НЕ оправдание оставлять RU при EN. Любой видимый текст на UI должен переключаться.
Прецедент v4.7.4¶
Первая версия lesson-viewer.html локализовала UI (sidebar / breadcrumb / tabs / right-sidebar / 24 lesson rows) но оставила тело урока (h2/p/blockquote/ol — «Принцип 1: Процессы работают сами») в RU с отговоркой «в production читается из Academy LMS API».
CEO открыл EN и сказал: «здесь по центру на русском даже если англ раскладка».
Правило: demo content тоже локализуется. Production когда контент придёт из API — заменит i18n keys. Но в prototype/demo — i18n keys обязательны для всего видимого тела.
Решение: добавить content_* ключи (intro / h2_X / p1-pN / blockquote / h3_without / h3_with / lists / outro) в namespace экрана + расставить data-i18n / data-i18n-html на каждом параграфе.
Native EN качество · 7 правил из academy v4.7.x¶
Уроки из 5 релизов локализации академии (v4.7.0 → v4.7.5):
- Read aloud test — каждое EN-предложение прочитать вслух. Звучит как machine translation? Переписать.
- Idiomatic > literal — «Architecture of Dominance» (canonical CEO-approved) > «Architecture of Excellence» (machine). «What's the cost of delay?» > «What does it cost not to do this today?» «Spot the weak areas and lock the principles in» > «Open weak places and reinforce principles».
- Tone parity — RU sharp/punchy → EN sharp/punchy. «PICK THIS» вместо «выбирают». «Pick a tier» вместо «Choose a tariff».
- Cultural anchors — «$50k+ contract» вместо «$50k+ договор» (USD-style). «WhatsApp every day» (natural English phrasing). «1×100 leverage» (preserves brand metaphor).
- Verb form sustainability — «Processes work on their own» (verb sustains across uses) vs «Processes are working autonomously» (passive heavy).
- Punchy prepositions — «Assets over services» > «Assets vs services» > «Assets instead of services». Один char делает копию sharp.
- Brand context preservation — «Take a Kommo trial (14 days)» сохраняет product name. «GOLDJAXE [ ACADEMY ]» square brackets — branding immutable во всех языках.
13/13 academy completion (v4.7.5 final · 2026-05-07)¶
| # | Экран | Релиз | data-i18n | Bilingual JS |
|---|---|---|---|---|
| 1 | onboarding.html | v4.7.0 | 50 | onboarding_paths (36) |
| 2 | dashboard.html | v4.7.0 | 47 | — |
| 3 | courses.html | v4.7.0 | 48 | — |
| 4 | lesson-completed.html | v4.7.0 | 35 | academy_completed_modules (18) |
| 5 | profile.html | v4.7.0 | 53 | — |
| 6 | login.html | v4.7.0 | 14 | — |
| 7 | auth-magic-link.html | v4.7.0 | 22 | — |
| 8 | settings.html | v4.7.1 + v4.7.2 | 80 | — |
| 9 | billing.html | v4.7.1 | 44 | — |
| 10 | lesson-quiz.html | v4.7.3 | 63 | academy_quiz_data (30) |
| 11 | lesson-viewer.html | v4.7.4 | 112 | academy_lesson_modules (30) |
| 12 | certificate.html | v4.7.5 (bump) | 21 | — |
| 13 | investor-portal.html | v4.7.5 (bump) | 20 | — |
Cumulative:
- ~609 data-i18n атрибутов в HTML × 13 экранов
- 4 bilingual JS namespace · 114 ключей × 2 = 228 переведённых JS-литералов
- ~1,330 ключей в i18n/ru.js + i18n/en.js (симметрия RU/EN ✓)
- 5 релизов · 7 commits · ~3,700 строк изменений
7 закрытых прецедентов v4.7.x (для синдиката / gate-admin / .ai/landing)¶
- page-lang switcher reactive (v4.7.2) — in-form switcher должен вызывать
window.ecoLang.apply(l), не ручную установку attr. theme-lang.js слушает только[data-lang-btn]clicks. - applyLangAware из IIFE (v4.7.2) — chip/date/JS-литералы должны быть lang-aware независимо от
if (user). Demo-режим не должен видеть RU при EN. - MutationObserver на html[lang] (v4.7.0+) — canonical для re-render JS-rendered контента без перезагрузки страницы. 7+ экранов академии используют.
- Bilingual JS-namespace для data-в-литералах (v4.7.0+) —
QUIZ_DATA→QUIZ_STRUCTURE+getQuestionData()lookup. 4 namespace в академии (228 JS-литералов). - Cache invalidation pitfall (v4.7.1+) — Chrome агрессивно кэширует HTML,
Cache-Control: no-storeне всегда помогает. В testing использовать?_nocache=N. - Локализация content body (v4.7.4) — «это API placeholder» НЕ оправдание для RU при EN. Demo content тоже локализуется.
- Stale mirror server gotcha (v4.7.1) — сервер может раздавать snapshot вместо working tree. Перед debug
lsof -p PID | grep cwdдля верификации.
Re-entry для Claude (новая сессия · syndicate / gate-admin / .ai)¶
«прочти контекст academy-i18n-v4.7» → этот файл (08-i18n.md) + memory feedback_academy_i18n_canonical.md + ROOT_LOG entry от 2026-05-07.
Применить к новому экрану:
1. Прочитать существующий HTML — посчитать grep -c 'data-i18n' (текущее покрытие)
2. Найти namespace в i18n/ru.js (grep -n '"<product>_<screen>"' ui-kit/i18n/ru.js) — расширить или создать
3. Найти JS-литералы с RU-only текстами — вынести в bilingual namespace
4. Расставить data-i18n / data-i18n-html / data-i18n-attr атрибуты в HTML body (без trogania immutable: branding caps, tier-codes, JWT/cookie/n8n)
5. Применить MutationObserver pattern для chip/date/dynamic
6. Если есть in-form lang switcher → window.ecoLang.apply(l) pattern
7. Bump ?v= query на затронутом HTML
8. Validate JSON + symmetry RU↔EN
9. Live test через Chrome MCP (RU↔EN navigation + JS-eval)
10. Native EN review (read aloud test, 7 правил выше)
11. Commit + push + CEO ревью
Реактивные элементы (date / numbers / currency)¶
Что НЕ покрывается i18n keys (потому что динамическое):
- Даты: 06 МАЯ 2026 / 06 MAY 2026
- Числа с unit: 4ч / 4h
- Currency conversions: 149₽ / $149
- Pluralization: 1 курс / 2 курса / 5 курсов
- Percentages с context: 28% завершено / 28% completed
Pattern: MutationObserver на html[lang]¶
function updateCertDate() {
const isEn = document.documentElement.lang === 'en';
const months = isEn
? ['JANUARY','FEBRUARY','MARCH','APRIL','MAY','JUNE','JULY','AUGUST','SEPTEMBER','OCTOBER','NOVEMBER','DECEMBER']
: ['ЯНВАРЯ','ФЕВРАЛЯ','МАРТА','АПРЕЛЯ','МАЯ','ИЮНЯ','ИЮЛЯ','АВГУСТА','СЕНТЯБРЯ','ОКТЯБРЯ','НОЯБРЯ','ДЕКАБРЯ'];
const el = document.getElementById('cert-date');
if (el) {
const now = new Date();
el.textContent = now.getDate().toString().padStart(2,'0') + ' ' + months[now.getMonth()] + ' ' + now.getFullYear();
}
}
updateCertDate(); // initial
new MutationObserver(updateCertDate).observe(
document.documentElement,
{ attributes: true, attributeFilter: ['lang','data-lang'] }
);
Применяется на v4.4.17 cert.html. Reactive ко всем lang switches.
Pattern: inner span для unit¶
Для 4ч / 4h — number static, unit translatable через i18n key:
<div class="cert-stat-num">4<span data-i18n="academy_certificate.stat_hours_unit">ч</span></div>
Pattern: pluralization (RU complex)¶
RU имеет 3 формы plural (1 / 2-4 / 5+), EN имеет 2 (1 / many).
function pluralRu(n, one, few, many) {
const mod10 = n % 10;
const mod100 = n % 100;
if (mod10 === 1 && mod100 !== 11) return one;
if (mod10 >= 2 && mod10 <= 4 && (mod100 < 10 || mod100 >= 20)) return few;
return many;
}
// Usage
const courseLabel = isEn
? (n === 1 ? 'course' : 'courses')
: pluralRu(n, 'курс', 'курса', 'курсов');
Pattern: currency conversion¶
При показе цен:
- RU primary: 149₽ (rouble symbol after)
- EN primary: $149 (dollar before)
- Можно показать оба 149₽ ≈ $1.50 для transparency
function formatPrice(rub, lang) {
if (lang === 'en') return `$${(rub / 95).toFixed(2)}`;
return `${rub.toLocaleString('ru-RU')} ₽`;
}
Date / number / currency formats¶
Полная таблица locale-specific formatting:
| Тип | RU | EN |
|---|---|---|
| Date | 06 МАЯ 2026 (DD MONTH YYYY) |
06 MAY 2026 (DD MONTH YYYY · UK style) |
| Date short | 06.05.2026 |
05/06/2026 (US) или 06/05/2026 (UK) |
| Time | 14:30 |
2:30 PM (US) или 14:30 (UK 24h) |
| Currency RUB | 14 900 ₽ (space thousands, символ после) |
14,900 ₽ (если EN фраза с RUB) |
| Currency USD | $149 |
$149 |
| Currency EUR | €149 |
€149 |
| Phone | +7 (XXX) XXX-XX-XX |
+7 (XXX) XXX-XX-XX (international format same) |
| Percentage | 28% |
28% |
| Time duration | ~6-8 ч |
~6-8h |
| Decimal | 0,5 (запятая) |
0.5 (точка) |
| Big numbers | 1 234 567 (space separator) |
1,234,567 (comma) |
| Date range | с 06.05 по 12.05 |
May 6 — May 12 |
Decision¶
GOLDJAXE использует UK style для EN (DD MONTH YYYY · 24h time) — universal для US/EU/UK audience. US format May 6, 2026 НЕ используется (избегает confusion с EU readers).
Hardcoded EN audit (Призма 4 план)¶
Project: project_native_en_localization.md. Запускать после v4.4.13+ финал (~2026-05-15+).
4 этапа¶
Этап 1 · 5 критических экранов¶
Native copywriter 18+ лет (Upwork ~$300):
- landing.ai (главная .ai)
- landing.com (.com VISION)
- landing.org (.org VAULT)
- syndicate/landing (forum entry)
- investor-portal (LP-facing)
Этап 2 · i18n/en.js audit (Upwork ~$200)¶
Coverage scan vs ru.js: - Missing keys (RU есть, EN нет) - Outdated keys (расходятся по смыслу) - Native polish existing translations
Скрипт для audit:
const ruKeys = Object.keys(window.ECO_I18N.ru).flatMap(ns =>
Object.keys(window.ECO_I18N.ru[ns]).map(k => `${ns}.${k}`)
);
const enKeys = Object.keys(window.ECO_I18N.en).flatMap(ns =>
Object.keys(window.ECO_I18N.en[ns]).map(k => `${ns}.${k}`)
);
const missing = ruKeys.filter(k => !enKeys.includes(k));
console.log('Missing in EN:', missing);
Этап 3 · Hardcoded EN в HTML (Upwork ~$300)¶
Поиск text без data-i18n:
grep -rn '>\(.*\)<' ecosystem-prototype/ | grep -v 'data-i18n'
Wrap каждый в data-i18n + добавить key в обе language files.
Этап 4 · Marketing copy native polish (Upwork ~$700)¶
856 строк marketing text → native copywriter 18+ лет polish: - Headlines / subheadlines - Feature descriptions - Pricing copy - CTAs - Social proof testimonials
Бюджет общий: ~$1.5K через Upwork.
Альтернатива · Claude polish¶
CEO ранее решил: полировка EN перевода через Claude (не подрядчик), потому что Claude знает контекст GOLDJAXE.
См. project_native_en_localization.md для финального решения.
investor-portal special (EN-first + RU UI overlay)¶
Investor portal targets US/EU LP. Особый case:
Поведение¶
- HTML
lang="en"default (EN-first) - Контент EN canonical (хедлайны / описания / CTAs)
- RU UI overlay = инструменты управления (lang switcher / theme toggle)
- При switch на RU — переводится только UI, контент остаётся EN
Markup pattern¶
<html lang="en" data-lang="en">
<!-- ... -->
<a href="#reports" class="font-mono uppercase text-[12.5px] text-white opacity-70"
data-i18n="investor.nav_reports">Reports</a>
Если data-lang="ru" через theme-lang.js → текст переключается на «Отчёты». Но дефолт en.
Why¶
Investor decision-makers — глобальные LP / family offices / institutional. Они ожидают EN-first. RU overlay — для удобства Сергея personally при работе с интерфейсом.
См. feedback_brand_endorsement_strategy.md + feedback_holding_positioning_vs_infobiz.md.
i18n quality bar¶
Native EN check list (per text)¶
- [ ] Не machine translation (естественность, не дословный перевод)
- [ ] Idiomatic (используем native idioms)
- [ ] Tone matches RU (если RU formal — EN formal; если RU casual — EN casual)
- [ ] Punctuation согласно EN style (не RU кавычки
«»в EN) - [ ] Capitalization согласно EN convention (Title Case в headlines, sentence case в body)
- [ ] Spacing single (не double space после punctuation)
- [ ] Hyphens vs em-dashes correct (em-dash для prosaic breaks
—)
RU quality check list¶
- [ ] Без англицизмов (см.
06-anti-patterns.mdгруппа D) - [ ] Падежи правильные (особенно при interpolation)
- [ ] Pluralization correct (3 формы)
- [ ] Не «гугло-русский» (естественные обороты)
- [ ] Capitalization Sentence case (НЕ Title Case как EN)
Когда читать¶
- Перед добавлением переключаемого текста — обязательно
- При создании нового экрана с public-facing контентом — да
- Перед Призмой 4 (EN перевод audit) — обязательно
- При работе с investor-portal / marketing landings — обязательно
- При создании multi-currency pricing UI — да (currency formats секция)