Read-only mirrors. Development, issues and pull requests are on GitHub.

summaryrefslogtreecommitdiffhomepage
path: root/frontend/src
diff options
context:
space:
mode:
authorLeitwolf2026-10-03 15:41:34 +0300
committerGitHub2026-10-03 15:41:34 +0300
commit721049419bea10c07ca8a4f3b206908c6b673d49 (patch)
tree88760295e3d9791302dd1e80fa641e12a3964546 /frontend/src
parent3bb41fe5c59dcf25edfd868e0d6eca67e4a6e504 (diff)
parent35d4b930ceea4353650be59372dcfe9ce0972788 (diff)
downloadwebsite-721049419bea10c07ca8a4f3b206908c6b673d49.tar.gz
Merge pull request #12 from Progmasoft/feature/hebrew-locale
Add Hebrew, follow the browser language, and gate the backend docs
Diffstat (limited to 'frontend/src')
-rw-r--r--frontend/src/App.vue20
-rw-r--r--frontend/src/locales.ts81
-rw-r--r--frontend/src/pages/GettingStartedPage.vue64
-rw-r--r--frontend/src/pages/HomePage.vue70
-rw-r--r--frontend/src/pages/OverviewPage.vue118
-rw-r--r--frontend/src/preferences.ts49
-rw-r--r--frontend/src/seo.ts19
-rw-r--r--frontend/src/styles/site.css22
8 files changed, 413 insertions, 30 deletions
diff --git a/frontend/src/App.vue b/frontend/src/App.vue
index 1aaa109..56b38f5 100644
--- a/frontend/src/App.vue
+++ b/frontend/src/App.vue
@@ -4,7 +4,8 @@
<script setup lang="ts">
import { computed } from "vue";
import { RouterLink, RouterView } from "vue-router";
-import { locale, theme } from "./preferences";
+import { localeLabels } from "./locales";
+import { locale, supportedLocales, theme } from "./preferences";
const labels = {
"en-US": {
@@ -40,6 +41,17 @@ const labels = {
status: "В активной разработке",
footer: "Проект Progmasoft. Visual X# — самостоятельный язык со своей идентичностью.",
},
+ "he-IL": {
+ home: "בית",
+ overview: "סקירה",
+ start: "צעדים ראשונים",
+ forum: "פורום",
+ source: "קוד מקור",
+ theme: "החלפת ערכת הצבעים",
+ language: "שפה",
+ status: "בפיתוח פעיל",
+ footer: "פרויקט של Progmasoft. ל-Visual X#‎ זהות שפה עצמאית.",
+ },
} as const;
const copy = computed(() => labels[locale.value]);
@@ -66,9 +78,9 @@ function toggleTheme() {
<label class="language-control">
<span class="sr-only">{{ copy.language }}</span>
<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>
+ <option v-for="supported in supportedLocales" :key="supported" :value="supported">
+ {{ localeLabels[supported] }}
+ </option>
</select>
</label>
<button class="icon-button" type="button" :aria-label="copy.theme" @click="toggleTheme">
diff --git a/frontend/src/locales.ts b/frontend/src/locales.ts
new file mode 100644
index 0000000..4d99d09
--- /dev/null
+++ b/frontend/src/locales.ts
@@ -0,0 +1,81 @@
+// SPDX-FileCopyrightText: 2026 Progmasoft <support@progmasoft.com>
+// SPDX-License-Identifier: AGPL-3.0-or-later WITH AdditionRef-Progmasoft-Patent-Grant-1.1
+
+export const supportedLocales = ["en-US", "de-DE", "ru-RU", "he-IL"] as const;
+export type Locale = (typeof supportedLocales)[number];
+
+/** The language of a visitor whose browser asks for none that is published. */
+export const fallbackLocale: Locale = "en-US";
+
+/** The short label of each language in the language menu. */
+export const localeLabels: Record<Locale, string> = {
+ "en-US": "EN",
+ "de-DE": "DE",
+ "ru-RU": "RU",
+ "he-IL": "HE",
+};
+
+// Primary language subtags and the published locale each one selects. `iw` is
+// the former code for Hebrew, which some browsers still report.
+const localeByLanguage = new Map<string, Locale>([
+ ["en", "en-US"],
+ ["de", "de-DE"],
+ ["ru", "ru-RU"],
+ ["he", "he-IL"],
+ ["iw", "he-IL"],
+]);
+
+export function isLocale(value: unknown): value is Locale {
+ return supportedLocales.some((supported) => supported === value);
+}
+
+/**
+ * The first published locale among the languages a browser prefers.
+ *
+ * The argument is the list a browser reports, most preferred first. Only the
+ * primary language of each entry is compared, so `de-AT` selects German.
+ */
+export function browserLocale(languages: readonly string[]): Locale {
+ for (const language of languages) {
+ const primary = String(language).split("-", 1)[0] ?? "";
+ const locale = localeByLanguage.get(primary.toLowerCase());
+ if (locale) return locale;
+ }
+ return fallbackLocale;
+}
+
+/**
+ * The language a page starts in: the language the visitor chose, and without
+ * a choice the language of the browser.
+ */
+export function initialLocale(chosen: string | null, languages: readonly string[]): Locale {
+ return isLocale(chosen) ? chosen : browserLocale(languages);
+}
+
+/**
+ * Whether a language change may be remembered as the visitor's choice.
+ *
+ * A choice needs a visitor: a change that reaches the page before anyone has
+ * clicked, tapped or typed in it did not come from the language menu, so it
+ * changes the page but is not stored. The argument is the browser's
+ * `navigator.userActivation`; a browser without it cannot tell, and there a
+ * change counts as a choice.
+ */
+export function isVisitorChoice(activation: { hasBeenActive: boolean } | undefined): boolean {
+ return activation?.hasBeenActive ?? true;
+}
+
+/**
+ * The direction in which the text of a language runs.
+ *
+ * Only text follows it. The page layout, the navigation and code samples keep
+ * their left-to-right arrangement in every language.
+ */
+export function textDirection(locale: Locale): "ltr" | "rtl" {
+ return locale === "he-IL" ? "rtl" : "ltr";
+}
+
+/** The arrow that points onward in the reading direction of a language. */
+export function forwardArrow(locale: Locale): string {
+ return textDirection(locale) === "rtl" ? "←" : "→";
+}
diff --git a/frontend/src/pages/GettingStartedPage.vue b/frontend/src/pages/GettingStartedPage.vue
index abbd8d9..ecde15f 100644
--- a/frontend/src/pages/GettingStartedPage.vue
+++ b/frontend/src/pages/GettingStartedPage.vue
@@ -4,7 +4,7 @@
<script setup lang="ts">
import { computed } from "vue";
import { RouterLink } from "vue-router";
-import { locale } from "../preferences";
+import { arrow, locale } from "../preferences";
const text = {
"en-US": {
@@ -184,6 +184,65 @@ const text = {
spec: "Публичная спецификация",
examples: "Больше примеров",
},
+ "he-IL": {
+ navOverview: "סקירה",
+ navStart: "צעדים ראשונים",
+ section: "תיעוד / צעדים ראשונים",
+ title: "מתחילים מקוד המקור",
+ lead: "Visual X#‎ נמצאת בפיתוח פעיל. לעת עתה המאגר הוא נקודת הכניסה האמינה לבניית המהדר ולבדיקה אילו יכולות של השפה פועלות מקצה לקצה.",
+ step1: "1. השגת המאגר",
+ step1Text:
+ "שכפלו את מאגר המהדר יחד עם תת־המודולים שלו. השתמשו במחשב פיתוח נתמך עם Windows 10/11 או עם גרסה עדכנית של macOS.",
+ step2: "2. בדיקת שרשרת הכלים",
+ step2Text:
+ "פקודת הפיתוח של המאגר, הכתובה ב-Go, בודקת את הדרישות המקומיות. המהדר משתמש ב-GHC2024 ל-frontend הכתוב ב-Haskell, ב-Clang/LLVM לשלבי ה-native ב-C++20, ב-Bazel לבניית חלקי ה-native וב-Temurin JDK 25 ל-DSL של הפרויקטים.",
+ platform:
+ "מחשבי הפיתוח הנתמכים הם Windows 10/11 ו-macOS Sequoia/Tahoe. ב-Windows נעשה שימוש ב-clang-cl וב-LLD עצמאיים של LLVM יחד עם Windows SDK וספריות ה-CRT/STL של MSVC. ב-macOS יש להתקין את Xcode Command Line Tools; יישום Xcode המלא אינו נדרש.",
+ toolchainHelp:
+ "אם הבדיקה מדווחת על כלים חסרים, ה-bootstrap האופציונלי יכול להתקין את הדרישות דרך מנהל החבילות של המערכת. לאחר מכן פתחו מסוף חדש כדי ששינויי PATH ייכנסו לתוקף. LLVM_ROOT יכול להצביע על תחילית ההתקנה של חבילת הפיתוח של LLVM; אין לכתוב נתיב ייחודי למחשב בקובצי הפרויקט.",
+ bootstrap: "הכנת מחשב אופציונלית",
+ step3: "3. בנייה ובדיקה",
+ step3Text:
+ "בנו את מהדר ה-native והריצו את חבילות בדיקות החוזה שלו. פקודת ה-Go מתזמרת את הבדיקות; Bazel נשאר הבעלים של גרף הבנייה של C++20. Cabal ו-Gradle מטפלים בגבולות של Haskell ושל Kotlin.",
+ step4: "4. קריאה ובדיקה של תוכנית אמיתית",
+ step4Text:
+ "בדוגמה HelloWorld נקודת הכניסה היא מתודה של מחלקה. Visual X#‎ אינה משתמשת בפונקציה ברמה העליונה כנקודת הכניסה לתוכנית. בדקו את קובץ המקור המדויק, ואז בנו אותו בתת־הקבוצה הנוכחית של המהדר.",
+ fileCheck: "בדיקת קובץ מקור יחיד",
+ fileBuild: "בניית קובץ הרצה בקוד מכונה",
+ subset:
+ "תת־הקבוצה של קוד המקור שמתקבלת צרה מהמפרט הציבורי המלא. בנייה מוצלחת היא ראיה חזקה יותר לתוכנית מסוימת מאשר דוגמת תחביר במפרט; הרצת בדיקות מוצלחת היא ראיה חזקה יותר למשפחת יכולות.",
+ step5: "5. לעקוב אחר החוזה הציבורי",
+ step5Text:
+ "המפרט הציבורי מתאר את הסמנטיקה המיועדת של השפה, ומסמכי המאגר מפרטים את המימוש ואת הכלים. הפלט של ‎-Help במהדר מתאר את הדגלים שהקובץ המותקן מקבל.",
+ links: [
+ {
+ title: "מפרט השפה",
+ detail: "חוזי תחביר וסמנטיקה, כולל דוגמאות תקינות ושגויות.",
+ href: "https://github.com/Progmasoft/visual-xsharp/tree/main/Spec",
+ },
+ {
+ title: "מדריך בנייה",
+ detail: "מחשבים רשמיים, איתור שרשרת הכלים ויעדי הבנייה של חלקי ה-native.",
+ 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;
@@ -248,7 +307,8 @@ vxs build -Help</code></pre>
<p>{{ c.noteText }}</p>
</div>
<div class="doc-actions">
- <RouterLink class="button button-primary" to="/docs/overview">{{ c.more }} →</RouterLink
+ <RouterLink class="button button-primary" to="/docs/overview"
+ >{{ c.more }} {{ arrow }}</RouterLink
><a class="inline-link" href="https://github.com/Progmasoft/visual-xsharp/tree/main/Spec"
>{{ c.spec }} ↗</a
><a
diff --git a/frontend/src/pages/HomePage.vue b/frontend/src/pages/HomePage.vue
index 9b0d617..f432632 100644
--- a/frontend/src/pages/HomePage.vue
+++ b/frontend/src/pages/HomePage.vue
@@ -4,7 +4,7 @@
<script setup lang="ts">
import { computed } from "vue";
import { RouterLink } from "vue-router";
-import { locale } from "../preferences";
+import { arrow, locale } from "../preferences";
const translations = {
"en-US": {
@@ -193,6 +193,66 @@ const translations = {
disclaimer:
"Статус: язык и компилятор развиваются. Примеры в спецификации могут описывать задуманное поведение раньше его полной поддержки компилятором.",
},
+ "he-IL": {
+ 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: "המאגר מכיל את השפה, את שלבי ה-native של המהדר, את כלי הפרויקט, דוגמאות ומסמכי פיתוח. האתר הזה הוא מדריך אל העבודה הזאת, לא הצהרה על שרשרת כלים מוגמרת.",
+ link: "למאגר",
+ href: "https://github.com/Progmasoft/visual-xsharp",
+ },
+ ],
+ journey: "להכיר את השפה",
+ journeyText:
+ "קראו את הסקירה כדי להבין את התכנון, ואז המשיכו למדריך הצעדים הראשונים עם שרשרת הכלים הנוכחית. עבודה שטרם הושלמה מסומנת ביושר.",
+ disclaimer:
+ "מצב: שפה ומהדר מתפתחים. דוגמאות במפרט עשויות לתאר התנהגות מתוכננת לפני שקיימת לה תמיכה מלאה במהדר.",
+ },
} as const;
const c = computed(() => translations[locale.value]);
const example = `namespace Examples.HelloWorld;
@@ -214,7 +274,7 @@ public class HelloWorld {
<p class="hero-intro">{{ c.intro }}</p>
<div class="hero-actions">
<RouterLink class="button button-primary" to="/docs/getting-started"
- >{{ c.start }} <span aria-hidden="true">→</span></RouterLink
+ >{{ c.start }} <span aria-hidden="true">{{ arrow }}</span></RouterLink
><RouterLink class="button button-quiet" to="/docs/overview">{{ c.overview }}</RouterLink>
</div>
<p class="release-note">{{ c.stage }}</p>
@@ -239,7 +299,7 @@ public class HelloWorld {
<span class="kicker">02 / SOURCE</span>
<h2 id="example-heading">{{ c.codeTitle }}</h2>
<p>{{ c.codeCaption }}</p>
- <RouterLink class="inline-link" to="/docs/overview">{{ c.overview }} →</RouterLink>
+ <RouterLink class="inline-link" to="/docs/overview">{{ c.overview }} {{ arrow }}</RouterLink>
</div>
<div class="code-card">
<div class="code-bar"><span>HelloWorld.vxs</span></div>
@@ -268,7 +328,9 @@ public class HelloWorld {
<h2>{{ c.journey }}</h2>
<p>{{ c.journeyText }}</p>
</div>
- <RouterLink class="button button-primary" to="/docs/overview">{{ c.overview }} →</RouterLink>
+ <RouterLink class="button button-primary" to="/docs/overview"
+ >{{ c.overview }} {{ arrow }}</RouterLink
+ >
</section>
<p class="status-disclaimer section-wrap">{{ c.disclaimer }}</p>
</template>
diff --git a/frontend/src/pages/OverviewPage.vue b/frontend/src/pages/OverviewPage.vue
index b61347a..af40b98 100644
--- a/frontend/src/pages/OverviewPage.vue
+++ b/frontend/src/pages/OverviewPage.vue
@@ -4,7 +4,7 @@
<script setup lang="ts">
import { computed } from "vue";
import { RouterLink } from "vue-router";
-import { locale } from "../preferences";
+import { arrow, locale } from "../preferences";
const text = {
"en-US": {
@@ -352,6 +352,120 @@ const text = {
spec: "Читать спецификацию",
source: "Смотреть исходный код",
},
+ "he-IL": {
+ 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#‎ דורשת סביבת פיתוח מסוימת?",
+ a: "לא. תהליך הפיתוח הנתמך משתמש בכלי שורת פקודה ב-Windows וב-macOS; הפרויקט Xide הוא ממשק עתידי, לא דרישה מוקדמת.",
+ },
+ ],
+ items: [
+ {
+ title: "סמנטיקה לפני קיצורי דרך",
+ body: "לשפת המקור יש מפרט כתוב. המהדר מפריד בין תחביר מנותח, מפוענח ובעל טיפוסים, כך שהמעברים המאוחרים מקבלים משמעות מפורשת במקום לנחש.",
+ },
+ {
+ title: "בעלות שאפשר לעקוב אחריה",
+ body: "ההתנהגות של הפניות ושל ערכים מוגדרת דרך הרכב הטיפוסים. ספירת הפניות אוטומטית וכללי העתקה־בעת־כתיבה הם חלק ממודל השפה; התמיכה בזמן ריצה ובתרגום לשלבים הנמוכים עדיין בהשלמה.",
+ },
+ {
+ title: "תהליך הידור מכוון לקוד מכונה",
+ body: "ה-frontend מתרגם את התוכנית דרך Core ו-CorePrep, ולאחר מכן Xpp ו-Xmm מתאימים אותה ליצירת הקוד של LLVM. הגבולות מאפשרים אימות והודעות אבחון בכל שלב.",
+ },
+ ],
+ not: "מה היא איננה",
+ notText:
+ "Visual X#‎ אינה מוצגת כתחליף מוגמר לשפות ייצור מבוססות. המפרט מתעד את הסמנטיקה המיועדת; את הכיסוי שהמהדר אכן בונה יש לבדוק בנפרד. אתר השפה הזה אינו מארח חשבונות או ניהול חבילות.",
+ next: "רוצים להתעמק?",
+ nextText: "התחילו בתוכנית קטנה, ואז עיינו במפרט הציבורי ובמאגר קוד המקור.",
+ start: "צעדים ראשונים",
+ spec: "לקריאת המפרט",
+ source: "לצפייה בקוד המקור",
+ },
} as const;
const c = computed(() => text[locale.value]);
</script>
@@ -412,7 +526,7 @@ const c = computed(() => text[locale.value]);
<p>{{ c.nextText }}</p>
<div class="doc-actions">
<RouterLink class="button button-primary" to="/docs/getting-started"
- >{{ c.start }} →</RouterLink
+ >{{ c.start }} {{ arrow }}</RouterLink
><a
class="button button-quiet"
href="https://github.com/Progmasoft/visual-xsharp/tree/main/Spec"
diff --git a/frontend/src/preferences.ts b/frontend/src/preferences.ts
index e5078bc..a809bab 100644
--- a/frontend/src/preferences.ts
+++ b/frontend/src/preferences.ts
@@ -1,32 +1,49 @@
// SPDX-FileCopyrightText: 2026 Progmasoft <support@progmasoft.com>
// SPDX-License-Identifier: AGPL-3.0-or-later WITH AdditionRef-Progmasoft-Patent-Grant-1.1
-import { ref, watch } from "vue";
+import { computed, ref, watch } from "vue";
+import {
+ forwardArrow,
+ initialLocale,
+ isVisitorChoice,
+ textDirection,
+ type Locale,
+} from "./locales";
-export const supportedLocales = ["en-US", "de-DE", "ru-RU"] as const;
-export type Locale = (typeof supportedLocales)[number];
+export { supportedLocales, type Locale } from "./locales";
export type Theme = "dark" | "light";
-function storedLocale(): Locale {
- const stored = localStorage.getItem("vxs-locale");
- return supportedLocales.find((supported) => supported === stored) ?? "en-US";
-}
+// Holds a language only after the visitor picked one in the menu. The earlier
+// `vxs-locale` key was written on every visit, so it cannot tell a choice
+// from the former English default and is no longer read.
+const localeChoiceKey = "vxs-locale-choice";
function storedTheme(): Theme {
return localStorage.getItem("vxs-theme") === "light" ? "light" : "dark";
}
-export const locale = ref<Locale>(storedLocale());
+export const locale = ref<Locale>(
+ initialLocale(localStorage.getItem(localeChoiceKey), navigator.languages ?? []),
+);
export const theme = ref<Theme>(storedTheme());
-watch(
- locale,
- (value) => {
- document.documentElement.lang = value;
- localStorage.setItem("vxs-locale", value);
- },
- { immediate: true },
-);
+/** The arrow that points onward in the current language. */
+export const arrow = computed(() => forwardArrow(locale.value));
+
+function showLocale(value: Locale): void {
+ document.documentElement.lang = value;
+ document.documentElement.dataset.textDirection = textDirection(value);
+}
+
+showLocale(locale.value);
+// The language changes through the menu. It is remembered only when a
+// visitor has acted on the page, so nothing but a person makes a choice.
+watch(locale, (value) => {
+ showLocale(value);
+ if (isVisitorChoice(navigator.userActivation)) {
+ localStorage.setItem(localeChoiceKey, value);
+ }
+});
watch(
theme,
diff --git a/frontend/src/seo.ts b/frontend/src/seo.ts
index 16b147d..0df93f0 100644
--- a/frontend/src/seo.ts
+++ b/frontend/src/seo.ts
@@ -1,7 +1,7 @@
// SPDX-FileCopyrightText: 2026 Progmasoft <support@progmasoft.com>
// SPDX-License-Identifier: AGPL-3.0-or-later WITH AdditionRef-Progmasoft-Patent-Grant-1.1
-import type { Locale } from "./preferences";
+import type { Locale } from "./locales";
export type PublicRoute = "/" | "/docs/overview" | "/docs/getting-started";
@@ -62,6 +62,23 @@ const metadata: Record<Locale, Record<PublicRoute, PageMetadata>> = {
"Клонируйте Visual X#, проверьте набор инструментов, соберите и протестируйте компилятор и изучите первую программу с текущим CLI.",
},
},
+ "he-IL": {
+ "/": {
+ 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/src/styles/site.css b/frontend/src/styles/site.css
index f7e8a6b..96a1a0c 100644
--- a/frontend/src/styles/site.css
+++ b/frontend/src/styles/site.css
@@ -594,7 +594,7 @@ p {
margin-left: auto;
}
.doc-layout {
- grid-template-columns: 1fr;
+ grid-template-columns: minmax(0, 1fr);
gap: 27px;
}
.doc-sidebar {
@@ -656,3 +656,23 @@ p {
animation-duration: 0.01ms !important;
}
}
+
+/*
+ * Right-to-left text. Only the text follows the direction of the language:
+ * the page layout, the navigation and code keep their left-to-right
+ * arrangement. Blocks of prose run right to left, and short pieces such as
+ * links, buttons and list items take the direction of their own first
+ * letter, so a product name on its own still reads left to right.
+ */
+[data-text-direction="rtl"] :is(h1, h2, h3, h4, h5, h6, p, blockquote, figcaption, dt, dd) {
+ direction: rtl;
+}
+
+[data-text-direction="rtl"] :is(a, button, label, li, small, strong, span, option) {
+ unicode-bidi: plaintext;
+}
+
+[data-text-direction="rtl"] :is(code, pre, kbd, samp) {
+ direction: ltr;
+ unicode-bidi: isolate;
+}