diff options
| author | Leitwolf11 | 2026-10-03 01:43:43 +0300 |
|---|---|---|
| committer | Leitwolf11 | 2026-10-03 01:43:43 +0300 |
| commit | f25c01f1d173b9e206565207472b58ca51f90803 (patch) | |
| tree | c4244396690eb895815b89802a8c5cb7261f47a9 /frontend | |
| parent | 1045265e9ae018cb3d2d94a47d780888c26d04a2 (diff) | |
| download | website-feature/russian-locale.tar.gz | |
Add Russian to the language site and the forum placeholderfeature/russian-locale
Russian (ru-RU) is the third locale of the language site: the shell, the
home, overview and getting-started pages, and the page metadata are fully
translated, and the language menu offers it. The documentation sidebar reads
its labels from the page messages instead of a two-way locale test, and the
stored preference is accepted only when it names a supported locale.
The forum placeholder gets the same three languages. Its language control
is now a menu instead of a two-state button, and an unknown stored or
selected value keeps the current language.
The site API lists the new locale. Verified locally: prettier, the frontend
tests, vue-tsc and the Vite build, the backend tests, and a browser run of
the home and overview pages in Russian.
Diffstat (limited to 'frontend')
| -rw-r--r-- | frontend/src/App.vue | 12 | ||||
| -rw-r--r-- | frontend/src/pages/GettingStartedPage.vue | 71 | ||||
| -rw-r--r-- | frontend/src/pages/HomePage.vue | 62 | ||||
| -rw-r--r-- | frontend/src/pages/OverviewPage.vue | 127 | ||||
| -rw-r--r-- | frontend/src/preferences.ts | 6 | ||||
| -rw-r--r-- | frontend/src/seo.ts | 17 | ||||
| -rw-r--r-- | frontend/tests/forum.test.mjs | 39 | ||||
| -rw-r--r-- | frontend/tests/seo.test.mjs | 16 |
8 files changed, 330 insertions, 20 deletions
diff --git a/frontend/src/App.vue b/frontend/src/App.vue index d13addf..1aaa109 100644 --- a/frontend/src/App.vue +++ b/frontend/src/App.vue @@ -29,6 +29,17 @@ const labels = { status: "In aktiver Entwicklung", footer: "Ein Progmasoft-Projekt. Visual X# hat eine eigenständige Sprachidentität.", }, + "ru-RU": { + home: "Главная", + overview: "Обзор", + start: "Начало работы", + forum: "Форум", + source: "Исходный код", + theme: "Сменить цветовую тему", + language: "Язык", + status: "В активной разработке", + footer: "Проект Progmasoft. Visual X# — самостоятельный язык со своей идентичностью.", + }, } as const; const copy = computed(() => labels[locale.value]); @@ -57,6 +68,7 @@ function toggleTheme() { <select v-model="locale" :aria-label="copy.language"> <option value="en-US">EN</option> <option value="de-DE">DE</option> + <option value="ru-RU">RU</option> </select> </label> <button class="icon-button" type="button" :aria-label="copy.theme" @click="toggleTheme"> diff --git a/frontend/src/pages/GettingStartedPage.vue b/frontend/src/pages/GettingStartedPage.vue index 78a6889..abbd8d9 100644 --- a/frontend/src/pages/GettingStartedPage.vue +++ b/frontend/src/pages/GettingStartedPage.vue @@ -8,6 +8,8 @@ import { locale } from "../preferences"; const text = { "en-US": { + navOverview: "Overview", + navStart: "Getting started", section: "DOCUMENTATION / GETTING STARTED", title: "Start with the source", lead: "Visual X# is in active development. For now, the repository is the reliable entry point for building the compiler and checking which language features run end to end.", @@ -65,6 +67,8 @@ const text = { examples: "More examples", }, "de-DE": { + navOverview: "Überblick", + navStart: "Erste Schritte", section: "DOKUMENTATION / ERSTE SCHRITTE", title: "Mit dem Quellcode beginnen", lead: "Visual X# wird aktiv entwickelt. Das Repository ist derzeit der zuverlässige Einstieg, um den Compiler zu bauen und den tatsächlich funktionierenden Sprachumfang zu prüfen.", @@ -121,6 +125,65 @@ const text = { spec: "Öffentliche Spezifikation", examples: "Weitere Beispiele", }, + "ru-RU": { + navOverview: "Обзор", + navStart: "Начало работы", + section: "ДОКУМЕНТАЦИЯ / НАЧАЛО РАБОТЫ", + title: "Начните с исходного кода", + lead: "Visual X# находится в активной разработке. Пока что репозиторий — надёжная отправная точка, чтобы собрать компилятор и проверить, какие возможности языка работают от начала до конца.", + step1: "1. Получите репозиторий", + step1Text: + "Клонируйте репозиторий компилятора вместе с подмодулями. Используйте поддерживаемую машину разработки с Windows 10/11 или актуальной macOS.", + step2: "2. Проверьте набор инструментов", + step2Text: + "Команда разработки на Go из репозитория проверяет локальные требования. Компилятор использует GHC2024 для фронтенда на Haskell, Clang/LLVM для нативных стадий на C++20, Bazel для нативных сборок и Temurin JDK 25 для DSL проектов.", + platform: + "Поддерживаемые машины разработки — Windows 10/11 и macOS Sequoia/Tahoe. В Windows используются отдельные LLVM clang-cl и LLD вместе с Windows SDK и библиотеками MSVC CRT/STL. В macOS установите Xcode Command Line Tools; полное приложение Xcode не требуется.", + toolchainHelp: + "Если проверка сообщает об отсутствующих инструментах, необязательная начальная настройка может установить их через менеджер пакетов системы. После этого откройте новый терминал, чтобы изменения PATH вступили в силу. LLVM_ROOT может указывать на каталог установки LLVM для разработки; не записывайте путь, относящийся к конкретной машине, в файлы проекта.", + bootstrap: "Необязательная начальная настройка системы", + step3: "3. Соберите и протестируйте", + step3Text: + "Соберите нативный компилятор и запустите его нативные контрактные наборы тестов. Команда на Go координирует проверки; графом сборки C++20 по-прежнему владеет Bazel. Cabal и Gradle отвечают за свои области — Haskell и Kotlin.", + step4: "4. Прочитайте и проверьте настоящую программу", + step4Text: + "В примере HelloWorld точкой входа служит метод класса. В Visual X# функция верхнего уровня не является точкой входа программы. Проверьте сам исходный файл, а затем соберите его текущим подмножеством компилятора.", + fileCheck: "Проверить один исходный файл", + fileBuild: "Собрать нативный исполняемый файл", + subset: + "Принимаемое подмножество исходного кода уже полной публичной спецификации. Успешная сборка — более сильное свидетельство для конкретной программы, чем синтаксический пример в спецификации; успешный прогон тестов — более сильное свидетельство для семейства возможностей.", + step5: "5. Следуйте публичному контракту", + step5Text: + "Задуманную семантику языка смотрите в публичной спецификации, а сведения о реализации и инструментах — в каталоге Documents репозитория. Вывод -Help компилятора описывает флаги, которые принимает установленный исполняемый файл.", + links: [ + { + title: "Спецификация языка", + detail: "Синтаксические и семантические контракты с допустимыми и недопустимыми примерами.", + href: "https://github.com/Progmasoft/visual-xsharp/tree/main/Spec", + }, + { + title: "Руководство по сборке", + detail: "Официальные платформы, поиск набора инструментов и цели нативной сборки.", + href: "https://github.com/Progmasoft/visual-xsharp/blob/main/Documents/BUILDING.md", + }, + { + title: "Справочник по CLI", + detail: "Текущие команды, флаги, поведение артефактов и состояние реализации.", + href: "https://github.com/Progmasoft/visual-xsharp/blob/main/Documents/CLI.md", + }, + { + title: "Примеры программ", + detail: "Примеры исходного кода рядом с вариантами на C# 13, C++20 и Java 21.", + href: "https://github.com/Progmasoft/visual-xsharp/tree/main/Examples", + }, + ], + note: "Состояние реализации", + noteText: + "Пример из спецификации не доказывает поддержку в компиляторе. Прежде чем полагаться на возможность, запустите тесты репозитория и сверьтесь с актуальными заметками о реализации. Готовые двоичные сборки и стабильный процесс установки пока не обещаны.", + more: "Понять замысел", + spec: "Публичная спецификация", + examples: "Больше примеров", + }, } as const; const c = computed(() => text[locale.value]); const hello = `namespace Examples.HelloWorld; @@ -137,12 +200,8 @@ public class HelloWorld { <div class="doc-layout section-wrap"> <aside class="doc-sidebar"> <span class="kicker">VISUAL X#</span - ><RouterLink to="/docs/overview">{{ - locale === "de-DE" ? "Überblick" : "Overview" - }}</RouterLink - ><RouterLink to="/docs/getting-started" aria-current="page">{{ - locale === "de-DE" ? "Erste Schritte" : "Getting started" - }}</RouterLink + ><RouterLink to="/docs/overview">{{ c.navOverview }}</RouterLink + ><RouterLink to="/docs/getting-started" aria-current="page">{{ c.navStart }}</RouterLink ><a href="https://github.com/Progmasoft/visual-xsharp/tree/main/Spec">Specification ↗</a> </aside> <article class="doc-content"> diff --git a/frontend/src/pages/HomePage.vue b/frontend/src/pages/HomePage.vue index ac11833..9b0d617 100644 --- a/frontend/src/pages/HomePage.vue +++ b/frontend/src/pages/HomePage.vue @@ -131,6 +131,68 @@ const translations = { disclaimer: "Status: Sprache und Compiler entwickeln sich weiter. Beispiele in der Spezifikation können dem derzeitigen Implementierungsstand voraus sein.", }, + "ru-RU": { + eyebrow: "Язык для ясной разработки", + title: "Сделать понятной всю программу.", + intro: + "Visual X# стремится соединить выразительный исходный код с явным владением, прозрачным конвейером компилятора и нативным результатом. Он создаётся открыто: спецификация описывает задуманный язык, а охват реализации пока растёт.", + start: "Начать чтение", + overview: "Почему Visual X#?", + github: "Следить за разработкой", + stage: "Язык и компилятор в активной разработке", + why: "Почему Visual X#?", + whyIntro: + "Хорошие инструменты помогают рассуждать о том, что означает ваш код, чем он владеет и что компилятор сделает дальше.", + features: [ + { + number: "01", + title: "Читаемый код, явный смысл", + text: "Спецификация языка отделяет синтаксис от семантических правил и помечает в примерах допустимые и недопустимые формы. Чем меньше скрытых допущений, тем проще обсуждать большие кодовые базы.", + }, + { + number: "02", + title: "Владение — часть языка", + text: "Ссылочные типы, типы-значения и типы с копированием при записи различаются; AARC и явные промежуточные операции входят в проект компилятора. Это направление в процессе реализации, а не заявление о готовой поддержке во время выполнения.", + }, + { + number: "03", + title: "Компилятор, который можно изучить", + text: "У разбора, разрешения имён, проверки типов, Core, Xpp и Xmm отдельные обязанности. Путь к LLVM и нативному исполняемому файлу виден, а не скрыт в чёрном ящике.", + }, + ], + codeTitle: "Знакомая отправная точка", + codeCaption: + "Первая программа намеренно обычна. Глубина замысла проявляется по мере роста программ.", + statusLabel: "ТЕКУЩЕЕ СОСТОЯНИЕ", + statusTitle: "Проект, который можно изучить. Компилятор, который ещё растёт.", + statusLead: + "У спецификации и реализации разные задачи. Мы показываем обе, чтобы задуманное правило можно было отличить от возможности, которую уже можно запустить.", + statusItems: [ + { + title: "Прочитайте контракт", + body: "Публичная спецификация фиксирует грамматику и семантику языка, включая примеры, различающие допустимые и недопустимые формы. Она может описывать поведение раньше, чем его поддержит компилятор.", + link: "Открыть спецификацию", + href: "https://github.com/Progmasoft/visual-xsharp/tree/main/Spec", + }, + { + title: "Проверьте реализацию", + body: "Текущий компилятор, тесты его компонентов и справочник по CLI показывают, какие пути исходного кода и результаты работают сейчас. Новую возможность сначала проверьте на небольшой программе.", + link: "Читать справочник по CLI", + href: "https://github.com/Progmasoft/visual-xsharp/blob/main/Documents/CLI.md", + }, + { + title: "Следите за работой", + body: "В репозитории находятся язык, нативные стадии компилятора, инструменты проектов, примеры и документы для разработчиков. Этот сайт — путеводитель по этой работе, а не заявление о готовом наборе инструментов.", + link: "Перейти в репозиторий", + href: "https://github.com/Progmasoft/visual-xsharp", + }, + ], + journey: "Познакомьтесь с языком", + journeyText: + "Прочитайте обзор, чтобы понять замысел, затем следуйте руководству по началу работы с текущим набором инструментов. Незавершённое мы честно помечаем.", + disclaimer: + "Статус: язык и компилятор развиваются. Примеры в спецификации могут описывать задуманное поведение раньше его полной поддержки компилятором.", + }, } as const; const c = computed(() => translations[locale.value]); const example = `namespace Examples.HelloWorld; diff --git a/frontend/src/pages/OverviewPage.vue b/frontend/src/pages/OverviewPage.vue index 55529c6..b61347a 100644 --- a/frontend/src/pages/OverviewPage.vue +++ b/frontend/src/pages/OverviewPage.vue @@ -8,6 +8,8 @@ import { locale } from "../preferences"; const text = { "en-US": { + navOverview: "Overview", + navStart: "Getting started", section: "DOCUMENTATION / OVERVIEW", title: "What Visual X# is trying to solve", lead: "Visual X# is designed around a simple question: can expressive code remain understandable when ownership, types, and native compilation matter? This page explains the direction, not a promise that every feature is finished.", @@ -121,6 +123,8 @@ const text = { source: "View source", }, "de-DE": { + navOverview: "Überblick", + navStart: "Erste Schritte", section: "DOKUMENTATION / ÜBERBLICK", title: "Welches Problem Visual X# lösen will", lead: "Visual X# stellt eine Frage: Kann ausdrucksstarker Code verständlich bleiben, wenn Ownership, Typen und native Kompilierung wichtig sind? Diese Seite erklärt das Ziel, nicht die Fertigstellung aller Funktionen.", @@ -233,6 +237,121 @@ const text = { spec: "Spezifikation lesen", source: "Quellcode ansehen", }, + "ru-RU": { + navOverview: "Обзор", + navStart: "Начало работы", + section: "ДОКУМЕНТАЦИЯ / ОБЗОР", + title: "Какую задачу решает Visual X#", + lead: "Visual X# строится вокруг простого вопроса: может ли выразительный код оставаться понятным, когда важны владение, типы и нативная компиляция? Эта страница объясняет направление и не обещает, что каждая возможность уже готова.", + goals: "Цели проектирования", + modelHeading: "Несколько решений языка", + modelIntro: + "Это проектные контракты, описанные в публичной спецификации. Прежде чем считать контракт возможностью, пригодной для реальной работы, проверьте текущий охват компилятора.", + model: [ + { + title: "Точка входа принадлежит классу", + body: "Программа начинается в методе public static void Main() без параметров в настроенном классе входа. Класс может находиться в любом исходном файле; его пространство имён не обязано повторять каталоги.", + }, + { + title: "Типы определяют владение", + body: "Тип остаётся типом-значением, только если каждый вложенный обобщённый аргумент — тип-значение. Ссылочный тип в любом месте обобщённой структуры делает результат ссылочным типом.", + }, + { + title: "Единая стандартная библиотека", + body: "Пространства имён System содержат базовые средства: консольный ввод-вывод, массивы, текст и математику. Пространство имён стандартной библиотеки не является отдельно объявляемой зависимостью-пакетом.", + }, + { + title: "Нативный результат без видимости готового конвейера", + body: "Для реализованного подмножества у компилятора есть путь от исходного кода до нативного кода. Цель — более широкий охват семантики, проверяемые промежуточные границы и надёжная диагностика по мере роста языка.", + }, + ], + pipelineHeading: "У компилятора видимые границы", + pipelineIntro: + "Архитектура намеренно разделена, чтобы каждая стадия могла отклонить некорректный вход и сохранить установленное предыдущей стадией.", + pipeline: [ + { + title: "Исходный код и синтаксис", + body: "Лексер и парсер на Haskell строят разобранное AST, сохраняя позиции в исходном коде для диагностики.", + }, + { + title: "Имена и типы", + body: "Переименование, разрешение имён и проверка типов создают отдельные формы AST: разрешённую и типизированную.", + }, + { + title: "Семантика языка", + body: "Раскрытие синтаксического сахара и оптимизации Core выполняются до внутреннего адаптера CorePrep.", + }, + { + title: "Нативная адаптация", + body: "Стадии Xpp и Xmm на C++20 переносят проверенное, не зависящее от целевой платформы представление к LLVM.", + }, + { + title: "Исполняемый файл", + body: "LLVM понижает проверенную программу до объектного кода, после чего шаг компоновки для платформы создаёт артефакт .vxse.", + }, + ], + boundariesHeading: "Где проходят границы сегодня", + boundaries: [ + { + label: "Публичный контракт языка", + value: "Примеры в Spec/ и связанные руководства в Documents/", + }, + { + label: "Фактическая реализация", + value: "Тесты компилятора, поддерживаемое подмножество и явная диагностика", + }, + { + label: "Описание проекта", + value: "Visual.XSharp.kts, который интерпретирует система проектов на Kotlin", + }, + { + label: "Сайт", + value: "Введение в язык и руководства; без аккаунтов и управления пакетами", + }, + ], + questionHeading: "Частые вопросы", + questions: [ + { + q: "Готов ли Visual X# к использованию в продакшене?", + a: "Общего заявления о готовности к продакшену нет. Компилятор и среда выполнения ещё развиваются. Оценивайте конкретную возможность по тестам репозитория и заметкам к выпускам.", + }, + { + q: "Является ли публичная спецификация исполняемым набором тестов?", + a: "Не целиком. В ней есть задуманные допустимые и недопустимые фрагменты. Некоторые примеры описывают семантику до появления полной поддержки в компиляторе.", + }, + { + q: "Зачем показывать промежуточные стадии?", + a: "У каждой стадии есть владелец и граница проверки. Так поведение компилятора можно проверить, а поздние стадии не придумывают смысл исходного языка.", + }, + { + q: "Нужна ли для Visual X# определённая IDE?", + a: "Нет. Поддерживаемый процесс разработки использует инструменты командной строки в Windows и macOS; проект Xide — будущий интерфейс, а не обязательное условие.", + }, + ], + items: [ + { + title: "Семантика важнее обходных путей", + body: "У исходного языка есть письменная спецификация. Компилятор разделяет разобранный, разрешённый и типизированный синтаксис, чтобы поздние проходы получали явный смысл, а не догадывались.", + }, + { + title: "Владение, за которым можно проследить", + body: "Поведение ссылок и значений определяется составом типов. Автоматический подсчёт ссылок и правила копирования при записи входят в модель языка; поддержка во время выполнения и понижение ещё завершаются.", + }, + { + title: "Продуманный нативный конвейер", + body: "Фронтенд понижает программу через Core и CorePrep, затем Xpp и Xmm подготавливают её к генерации кода LLVM. Границы позволяют выполнять проверку и диагностику на каждой стадии.", + }, + ], + not: "Чем он не является", + notText: + "Visual X# не представлен как готовая замена устоявшимся языкам для продакшена. Спецификация фиксирует задуманную семантику; фактически собираемый охват компилятора следует проверять отдельно. На этом сайте языка нет аккаунтов и управления пакетами.", + next: "Хотите узнать больше?", + nextText: + "Начните с небольшой программы, а затем изучите публичную спецификацию и репозиторий с исходным кодом.", + start: "Начало работы", + spec: "Читать спецификацию", + source: "Смотреть исходный код", + }, } as const; const c = computed(() => text[locale.value]); </script> @@ -241,12 +360,8 @@ const c = computed(() => text[locale.value]); <div class="doc-layout section-wrap"> <aside class="doc-sidebar"> <span class="kicker">VISUAL X#</span - ><RouterLink to="/docs/overview" aria-current="page">{{ - locale === "de-DE" ? "Überblick" : "Overview" - }}</RouterLink - ><RouterLink to="/docs/getting-started">{{ - locale === "de-DE" ? "Erste Schritte" : "Getting started" - }}</RouterLink + ><RouterLink to="/docs/overview" aria-current="page">{{ c.navOverview }}</RouterLink + ><RouterLink to="/docs/getting-started">{{ c.navStart }}</RouterLink ><a href="https://github.com/Progmasoft/visual-xsharp/tree/main/Spec">Specification ↗</a> </aside> <article class="doc-content"> diff --git a/frontend/src/preferences.ts b/frontend/src/preferences.ts index 878bdce..e5078bc 100644 --- a/frontend/src/preferences.ts +++ b/frontend/src/preferences.ts @@ -3,11 +3,13 @@ import { ref, watch } from "vue"; -export type Locale = "en-US" | "de-DE"; +export const supportedLocales = ["en-US", "de-DE", "ru-RU"] as const; +export type Locale = (typeof supportedLocales)[number]; export type Theme = "dark" | "light"; function storedLocale(): Locale { - return localStorage.getItem("vxs-locale") === "de-DE" ? "de-DE" : "en-US"; + const stored = localStorage.getItem("vxs-locale"); + return supportedLocales.find((supported) => supported === stored) ?? "en-US"; } function storedTheme(): Theme { diff --git a/frontend/src/seo.ts b/frontend/src/seo.ts index ded4b84..16b147d 100644 --- a/frontend/src/seo.ts +++ b/frontend/src/seo.ts @@ -45,6 +45,23 @@ const metadata: Record<Locale, Record<PublicRoute, PageMetadata>> = { "Visual X# klonen, Toolchain prüfen, Compiler bauen und testen und das erste Programm mit der aktuellen CLI untersuchen.", }, }, + "ru-RU": { + "/": { + title: "Visual X# — Сделать понятной всю программу", + description: + "Visual X# — нативный язык программирования с читаемым исходным кодом, явным владением и прозрачным конвейером компилятора.", + }, + "/docs/overview": { + title: "Обзор · Visual X#", + description: + "Цели проектирования Visual X#, правила типов и владения, стадии компилятора и граница между спецификацией и реализацией.", + }, + "/docs/getting-started": { + title: "Начало работы · Visual X#", + description: + "Клонируйте Visual X#, проверьте набор инструментов, соберите и протестируйте компилятор и изучите первую программу с текущим CLI.", + }, + }, }; function setMeta(selector: string, content: string): void { diff --git a/frontend/tests/forum.test.mjs b/frontend/tests/forum.test.mjs index 38771aa..c04df30 100644 --- a/frontend/tests/forum.test.mjs +++ b/frontend/tests/forum.test.mjs @@ -18,6 +18,7 @@ function createElement() { const attributes = new Map(); return { textContent: "", + value: "", listeners, attributes, addEventListener(name, callback) { @@ -29,6 +30,10 @@ function createElement() { click() { listeners.get("click")(); }, + choose(value) { + this.value = value; + listeners.get("change")(); + }, }; } @@ -65,7 +70,8 @@ test("forum starts in English and dark mode without offering sign-up", () => { const { document, elements } = loadForum(); assert.equal(document.documentElement.lang, "en"); assert.equal(document.documentElement.dataset.theme, "dark"); - assert.equal(elements.get("language").textContent, "DE"); + assert.equal(elements.get("language").value, "en"); + assert.equal(elements.get("language").attributes.get("aria-label"), "Language"); assert.match(elements.get("title").textContent, /isn't open yet/); assert.match(elements.get("message").textContent, /no accounts, posts/); assert.doesNotMatch(elements.get("message").textContent, /sign up/i); @@ -73,18 +79,39 @@ test("forum starts in English and dark mode without offering sign-up", () => { test("forum switches language and keeps the choice", () => { const { document, elements, values } = loadForum(); - elements.get("language").click(); + elements.get("language").choose("de"); assert.equal(document.documentElement.lang, "de"); assert.equal(values.get("vxs-forum-language"), "de"); - assert.equal(elements.get("language").textContent, "EN"); + assert.equal(elements.get("language").value, "de"); assert.match(elements.get("title").textContent, /nicht geöffnet/); assert.match(document.title, /Visual X#/); - assert.equal(elements.get("language").attributes.get("aria-label"), "Zu Englisch wechseln"); - elements.get("language").click(); + assert.equal(elements.get("language").attributes.get("aria-label"), "Sprache"); + elements.get("language").choose("ru"); + assert.equal(document.documentElement.lang, "ru"); + assert.equal(values.get("vxs-forum-language"), "ru"); + assert.match(elements.get("title").textContent, /Форум пока не открыт/); + assert.match(elements.get("message").textContent, /нет ни аккаунтов, ни сообщений/); + assert.equal(elements.get("language").attributes.get("aria-label"), "Язык"); + elements.get("language").choose("en"); assert.equal(document.documentElement.lang, "en"); assert.equal(values.get("vxs-forum-language"), "en"); }); +test("forum ignores a language it does not offer", () => { + const { document, elements, values } = loadForum({ "vxs-forum-language": "tr" }); + assert.equal(document.documentElement.lang, "en"); + elements.get("language").choose("constructor"); + assert.equal(document.documentElement.lang, "en"); + assert.equal(elements.get("language").value, "en"); + assert.equal(values.get("vxs-forum-language"), "tr"); +}); + +test("forum restores a saved Russian preference", () => { + const { document, elements } = loadForum({ "vxs-forum-language": "ru" }); + assert.equal(document.documentElement.lang, "ru"); + assert.equal(elements.get("language").value, "ru"); +}); + test("forum restores the saved German and light preferences", () => { const { document, elements } = loadForum({ "vxs-forum-language": "de", @@ -92,7 +119,7 @@ test("forum restores the saved German and light preferences", () => { }); assert.equal(document.documentElement.lang, "de"); assert.equal(document.documentElement.dataset.theme, "light"); - assert.equal(elements.get("language").textContent, "EN"); + assert.equal(elements.get("language").value, "de"); }); test("forum theme button toggles and persists in both directions", () => { diff --git a/frontend/tests/seo.test.mjs b/frontend/tests/seo.test.mjs index c7014b7..45bb6de 100644 --- a/frontend/tests/seo.test.mjs +++ b/frontend/tests/seo.test.mjs @@ -75,3 +75,19 @@ test("a missing optional metadata tag does not stop the page title", () => { applyPageMetadata("/docs/getting-started", "de-DE"); assert.equal(globalThis.document.title, "Erste Schritte · Visual X#"); }); + +test("the Russian routes have their own titles and keep the canonical URLs", () => { + const { document, elements } = fakeDocument(); + applyPageMetadata("/", "ru-RU"); + assert.match(document.title, /^Visual X# — Сделать понятной всю программу$/); + assert.match(elements.get('meta[name="description"]').content, /нативный язык программирования/); + assert.equal(elements.get('link[rel="canonical"]').href, "https://xsharp-lang.xyz/"); + applyPageMetadata("/docs/overview", "ru-RU"); + assert.equal(document.title, "Обзор · Visual X#"); + applyPageMetadata("/docs/getting-started", "ru-RU"); + assert.equal(document.title, "Начало работы · Visual X#"); + assert.equal( + elements.get('link[rel="canonical"]').href, + "https://xsharp-lang.xyz/docs/getting-started", + ); +}); |