diff options
| author | Leitwolf | 2026-10-03 15:41:34 +0300 |
|---|---|---|
| committer | GitHub | 2026-10-03 15:41:34 +0300 |
| commit | 721049419bea10c07ca8a4f3b206908c6b673d49 (patch) | |
| tree | 88760295e3d9791302dd1e80fa641e12a3964546 /frontend/src | |
| parent | 3bb41fe5c59dcf25edfd868e0d6eca67e4a6e504 (diff) | |
| parent | 35d4b930ceea4353650be59372dcfe9ce0972788 (diff) | |
| download | website-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.vue | 20 | ||||
| -rw-r--r-- | frontend/src/locales.ts | 81 | ||||
| -rw-r--r-- | frontend/src/pages/GettingStartedPage.vue | 64 | ||||
| -rw-r--r-- | frontend/src/pages/HomePage.vue | 70 | ||||
| -rw-r--r-- | frontend/src/pages/OverviewPage.vue | 118 | ||||
| -rw-r--r-- | frontend/src/preferences.ts | 49 | ||||
| -rw-r--r-- | frontend/src/seo.ts | 19 | ||||
| -rw-r--r-- | frontend/src/styles/site.css | 22 |
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; +} |