Merge branch 'main' into sameh/firms-window-label

This commit is contained in:
Sameh Khamis authored and GitHub committed 2026-11-10 10:27:01 -08:00
commit bb45aacb24
18 files changed
+3735 -37

No files matched your search

+40
View File
@@ -0,0 +1,40 @@
name: Announce merged PRs in Discord
on:
pull_request_target:
types: [closed]
permissions: {}
jobs:
announce:
if: github.repository == 'bilawalsidhu/gods-eye-view' && github.event.pull_request.merged == true
runs-on: ubuntu-latest
timeout-minutes: 2
steps:
# This uses the trusted base workflow and never checks out or runs PR code.
- name: Post to Discord contributions
shell: bash
env:
DISCORD_WEBHOOK_URL: ${{ secrets.DISCORD_CONTRIBUTIONS_WEBHOOK_URL }}
run: |
if [[ ! "$DISCORD_WEBHOOK_URL" =~ ^https://discord\.com/api/webhooks/[0-9]+/[A-Za-z0-9_-]+$ ]]; then
echo '::error::Set DISCORD_CONTRIBUTIONS_WEBHOOK_URL to the Discord channel webhook URL.'
exit 1
fi
payload=$(jq -ce '
.pull_request | {
allowed_mentions: {parse: []},
embeds: [{
title: ("Merged #" + (.number | tostring) + ": " + (.title | .[0:115])),
url: .html_url,
footer: {text: ("By " + .user.login + " · into " + (.base.ref | .[0:100]))}
}]
}
' "$GITHUB_EVENT_PATH")
curl --silent --show-error --fail --output /dev/null \
--connect-timeout 10 --max-time 20 --retry 3 --retry-max-time 50 \
--header 'Content-Type: application/json' --data-binary "$payload" \
--url "$DISCORD_WEBHOOK_URL?wait=true"
+5
View File
@@ -0,0 +1,5 @@
- Add Hebrew (he), the second right-to-left language: the title, rails, dock
and corner row mirror, numbers keep Western digits, and plurals use the
one/two/other Hebrew forms. Noto Sans Hebrew joins the font stacks and the
style, plural and glossary notes arrive in docs/translations/he.md
(Hebrew, @meirm, #1594).
+14 -10
View File
@@ -4647,7 +4647,9 @@ test:track` 43 tracking invariants · headless QA harnesses under
### Interface localization (October 2026) ### Interface localization (October 2026)
Shipped locales: **en** (source and fallback), **ar**, **de**, **es**, **fr**, **it**, **ja**, **pt** (Brazilian Portuguese), **ru**, **uk**, **zh-CN** and **zh-Hant**. The code lives in Shipped locales: **en** (source and fallback), **ar**, **de**, **es**, **fr**, **he**,
**it**, **ja**, **pt** (Brazilian Portuguese), **ru**, **uk**, **zh-CN** and
**zh-Hant**. The code lives in
`src/i18n/`. How to mark UI text: [`docs/I18N.md`](./I18N.md); translator workflow: `src/i18n/`. How to mark UI text: [`docs/I18N.md`](./I18N.md); translator workflow:
[`docs/TRANSLATORS.md`](./TRANSLATORS.md). [`docs/TRANSLATORS.md`](./TRANSLATORS.md).
@@ -4661,19 +4663,21 @@ Shipped locales: **en** (source and fallback), **ar**, **de**, **es**, **fr**, *
`navigator.languages` → English. Any `pt` tag (`pt-BR`, `pt-PT`) selects `pt`. Chinese tags default to `zh-CN`; Traditional Chinese tags `navigator.languages` → English. Any `pt` tag (`pt-BR`, `pt-PT`) selects `pt`. Chinese tags default to `zh-CN`; Traditional Chinese tags
select `zh-Hant`. Explicit `Hans` selects `zh-CN` regardless of region. `?lang=` is not stored, and share links drop select `zh-Hant`. Explicit `Hans` selects `zh-CN` regardless of region. `?lang=` is not stored, and share links drop
it, so a recipient keeps their own language. The globe picker in the it, so a recipient keeps their own language. The globe picker in the
bottom-right corner (bottom-left in Arabic) (`src/ui/localePicker.js`) stores the choice, drops bottom-right corner (bottom-left in Arabic and Hebrew)
(`src/ui/localePicker.js`) stores the choice, drops
`?lang=` (keeps it only when storage is blocked) and switches in place: `?lang=` (keeps it only when storage is blocked) and switches in place:
views re-render, code that published translated text words it again through views re-render, code that published translated text words it again through
`onLocaleChange`, and `<html lang>`/`dir` and the page's `data-i18n` markup `onLocaleChange`, and `<html lang>`/`dir` and the page's `data-i18n` markup
follow. Nothing is lost; a toast already on screen keeps its words. follow. Nothing is lost; a toast already on screen keeps its words.
- **Right to left.** Arabic (`ar`) is in `RTL_LOCALES`, so `<html dir="rtl">`: - **Right to left.** Arabic (`ar`) and Hebrew (`he`) are in `RTL_LOCALES`, so
the title, style indicator, both panel stacks and their icon rails swap `<html dir="rtl">`: the title, style indicator, both panel stacks and their
sides through logical properties (`inset-inline-*`, `margin-inline-*`), the icon rails swap sides through logical properties (`inset-inline-*`,
dock and corner row reverse their order, and `src/ui/styles/rtl.css` holds `margin-inline-*`), the dock and corner row reverse their order, and
the rest: no letter-spacing on Arabic text, Noto Sans Arabic in the font `src/ui/styles/rtl.css` holds the rest: no letter-spacing on Arabic and
stacks, the intel HUD kept as a left-to-right instrument overlay, FM seek Hebrew text, Noto Sans Arabic and Noto Sans Hebrew in the font stacks, the
and the radio dial kept left to right. Intl formatters get `ar-u-nu-latn`, intel HUD kept as a left-to-right instrument overlay, FM seek and the radio
so numbers and dates keep Western digits. dial kept left to right. Intl formatters get `ar-u-nu-latn`, so numbers and
dates keep Western digits.
- **Gate.** `npm run i18n` (and `src/i18n/catalog.test.mjs` under `npm test`) - **Gate.** `npm run i18n` (and `src/i18n/catalog.test.mjs` under `npm test`)
extracts every message and fails on missing, empty, unused, extracts every message and fails on missing, empty, unused,
placeholder-mismatched, still-English (plural forms included) or placeholder-mismatched, still-English (plural forms included) or
+5 -4
View File
@@ -1,6 +1,6 @@
# Translating God's Eye View # Translating God's Eye View
Shipped languages: **English** (source and fallback), **Arabic** (`ar`), **German** (`de`), **Spanish** (`es`), **French** (`fr`), **Italian** (`it`), **Japanese** (`ja`), **Brazilian Portuguese** (`pt`), **Russian** (`ru`), **Ukrainian** (`uk`), **Simplified Chinese** (`zh-CN`) and **Traditional Chinese** (`zh-Hant`). Shipped languages: **English** (source and fallback), **Arabic** (`ar`), **German** (`de`), **Spanish** (`es`), **French** (`fr`), **Hebrew** (`he`), **Italian** (`it`), **Japanese** (`ja`), **Brazilian Portuguese** (`pt`), **Russian** (`ru`), **Ukrainian** (`uk`), **Simplified Chinese** (`zh-CN`) and **Traditional Chinese** (`zh-Hant`).
## How it works ## How it works
@@ -45,9 +45,10 @@ Edit `src/i18n/locales/<code>.json` and run `npm run i18n`. To see your change,
1. Create `src/i18n/locales/<code>.json`, fill it from `npm run i18n -- --missing <code>` (start from `{}`), and translate every entry. 1. Create `src/i18n/locales/<code>.json`, fill it from `npm run i18n -- --missing <code>` (start from `{}`), and translate every entry.
2. Import it in `src/i18n/index.js` and add it to `CATALOGS`. 2. Import it in `src/i18n/index.js` and add it to `CATALOGS`.
3. Add the code to `LOCALES` and its own-language name to `LOCALE_NAMES` in `src/i18n/locale.js`. Right-to-left languages also go in `RTL_LOCALES`; the chrome mirrors through logical CSS properties (`inset-inline-*`, `margin-inline-*`), and `src/ui/styles/rtl.css` holds what those cannot express (see the Arabic notes). If the language should keep Western digits, map it in `INTL_TAGS` in `locale.js`. 3. Add the code to `LOCALES` and its own-language name to `LOCALE_NAMES` in `src/i18n/locale.js`. Right-to-left languages also go in `RTL_LOCALES`; the chrome mirrors through logical CSS properties (`inset-inline-*`, `margin-inline-*`), and `src/ui/styles/rtl.css` holds what those cannot express (see the Arabic notes). If the language should keep Western digits, map it in `INTL_TAGS` in `locale.js`.
4. Add `src/i18n/locales/<code>.json` next to `ru.json` in `scripts/package-boundaries.json`. 4. Add `src/i18n/locales/<code>.json` next to `ru.json` in `scripts/package-boundaries.json` and in `scripts/format-scope.json`.
5. Write `docs/translations/<code>.md`: glossary, plural forms, units and style (copy the structure of `ru.md`). 5. Write `docs/translations/<code>.md`: glossary, plural forms, units and style (copy the structure of `ru.md`).
6. Run `npm run i18n` and `npm test`, then check the panels in the app at narrow widths. 6. If the language is not written in Latin letters, also add it to `NON_LATIN_LOCALES` in `src/hudSummaryResponse.js` — that guard falls back to the translated text when a summary provider answers in English.
7. Run `npm run i18n` and `npm test`, then check the panels in the app at narrow widths.
## Rules ## Rules
@@ -63,4 +64,4 @@ These are contracts, not copy: layer ids and registry keys, status enums and oth
## Language notes ## Language notes
Each language keeps its glossary, plural forms, units and style in its own file, `docs/translations/<code>.md`: [Arabic](./translations/ar.md), [German](./translations/de.md), [Spanish](./translations/es.md), [French](./translations/fr.md), [Italian](./translations/it.md), [Japanese](./translations/ja.md), [Brazilian Portuguese](./translations/pt.md), [Russian](./translations/ru.md), [Ukrainian](./translations/uk.md), [Simplified Chinese](./translations/zh-CN.md), [Traditional Chinese](./translations/zh-Hant.md). Read it before translating, and add a term there when you settle on one. Each language keeps its glossary, plural forms, units and style in its own file, `docs/translations/<code>.md`: [Arabic](./translations/ar.md), [German](./translations/de.md), [Spanish](./translations/es.md), [French](./translations/fr.md), [Hebrew](./translations/he.md), [Italian](./translations/it.md), [Japanese](./translations/ja.md), [Brazilian Portuguese](./translations/pt.md), [Russian](./translations/ru.md), [Ukrainian](./translations/uk.md), [Simplified Chinese](./translations/zh-CN.md), [Traditional Chinese](./translations/zh-Hant.md). Read it before translating, and add a term there when you settle on one.
+64
View File
@@ -0,0 +1,64 @@
# Hebrew (`he`)
Catalog: `src/i18n/locales/he.json`. General rules: [TRANSLATORS.md](../TRANSLATORS.md).
## Style
- Address the user with a masculine imperative, Google style: הסתר, הצג, נסה שוב, התקרב. Menu items and tooltips take the noun form: הסרת {name}, הרחבת {name}, שינוי הצבע של {name}.
- Sentence case: Hebrew has no case forms, so UPPERCASE English is normal sentence style here; short HUD labels stay short, favor an abbreviation over a wrapped line.
- Units follow the catalog: ק"מ, מ׳, קמ"ש, שנ׳, דק׳, שע׳; an abbreviation always ends in the geresh (׳). Instrument readouts and formatted shared-unit labels keep the Latin units (km, NM, FT, KTS, MW, KM/S); catalog prose outside those readouts keeps ק"מ/מ׳. Codes like HDG, FOV, CAL, SRC, REC stay exactly as in English — see `same-as-english.json`.
## Digits and direction
- Western digits 0–9 only, like every catalog: "5 מטוסים", "26.08.2026". Dates in scene labels use dd.mm.yyyy.
- `{direction}` values are adverbs (שמאלה, ימינה, ישר); maneuver templates are written to place them after the verb: פנה {direction} אל {road}.
- Values inserted by t() are isolated (U+2068…U+2069); do not add manual RTL marks — write the sentence as natural Hebrew and let isolation do the rest.
## Plurals
A `tn()` entry needs three forms under `Intl.PluralRules('he')`:
| Form | Counts | Example |
| ------- | ---------------------------- | -------------- |
| `one` | 1 and fractions below 1 | 1 מצלמה · 0.5 |
| `two` | 2 | 2 מצלמות |
| `other` | 0, 3, 10, 100, integers… | 5 מצלמות |
With {count} shown as a digit, `two` and `other` share the plural noun (2 מצלמות, 5 מצלמות); `one` takes the singular (1 מצלמה). For constructs that resist agreement with a noun (אין, הכול, זמין), phrase around the number: "{count} תחנות מדווחות" is fine, but prefer "ממתין: {count}" over a mismatched genitive chain.
## Glossary
| English | עברית |
| ---------------------------------------------- | ----------------------------------------------------- |
| layer / data layer | שכבה / שכבת נתונים |
| feed | פיד |
| source / data source / Provider Settings | מקור / מקור נתונים / הגדרות הספקים |
| LIVE / live data / LIVE TRACK | חי / נתונים חיים / מעקב חי |
| SIMULATED | מדומה |
| stale / degraded / unavailable | מיושן / מופחת / לא זמין |
| key (API key) / POWER UP | מפתח / שדרוג |
| globe / map / map source / basemap | גלובוס / מפה / מקור מפה / מפת בסיס |
| aircraft / flight / military flight | מטוס (כלי טיס in class lists) / טיסה / טיסה צבאית |
| vessel / ship / ships | אונייה / אוניות |
| satellite | לוויין |
| launch (rocket) / space mission | שיגור / משימת חלל |
| contact / tracking | מגע / עקיבה |
| installation / site | מתקן / אתר |
| camera / CCTV camera / ALPR camera | מצלמה / מצלמת CCTV / מצלמת ALPR |
| earthquake / active fire / cyclone | רעידת אדמה / שריפה פעילה / ציקלון |
| scene / shot | סצנה / שוט |
| briefing / panel / dock | תדריך / פאנל / עגינה |
| visual preset / style | ערכת ראייה / סגנון |
| Scope (circular viewport mask) / keyhole | סקופ / חור המנעול |
| heading (compass °) / course | כיוון / מהלך |
| HDG / BRG / CRS / FOV / DEST / SPD / RANGE | בלי שינוי — קודי מכשיר |
| advisory (NHC/CPHC cyclone) | עדכון; Advisory center — מרכז הסערה לפי העדכון |
| Street View (Google) | Street View, בלי תרגום |
| Street Level / Street Level layer | Street Level, בלי תרגום (שם שכבה) |
| WHOT/BHOT, Ironbow, CRT, NVG, FLIR | בלי שינוי — קודים ושמות מצבים |
| PREV / NEXT | הקודם / הבא |
| anchor / data pack / asset bundle (scene) | עוגן / חבילת נתונים / חבילת נכסים |
| forecast (weather) | תחזית |
| witness / geolocated source | עד ראייה / מקור מאוכן |
| contained (wildland fire) / FRP | הכלה / FRP, בלי שינוי |
+1 -1
View File
@@ -21,7 +21,7 @@
<link rel="stylesheet" href="/style.css" /> <link rel="stylesheet" href="/style.css" />
<link rel="preconnect" href="https://fonts.googleapis.com" /> <link rel="preconnect" href="https://fonts.googleapis.com" />
<link rel="preconnect" href="https://fonts.gstatic.com" crossorigin /> <link rel="preconnect" href="https://fonts.gstatic.com" crossorigin />
<link href="https://fonts.googleapis.com/css2?family=JetBrains+Mono:wght@300;400;500;600;700&family=Inter:wght@300;400;500;600&family=Noto+Sans+Arabic:wght@400;500;600;700&display=swap" rel="stylesheet" /> <link href="https://fonts.googleapis.com/css2?family=JetBrains+Mono:wght@300;400;500;600;700&family=Inter:wght@300;400;500;600&family=Noto+Sans+Arabic:wght@400;500;600;700&family=Noto+Sans+Hebrew:wght@400;500;600;700&display=swap" rel="stylesheet" />
<!-- Icon font subset to the glyphs this app renders. Keep `icon_names` in sync <!-- Icon font subset to the glyphs this app renders. Keep `icon_names` in sync
with the sources: src/materialSymbolsSubset.test.mjs fails when a source with the sources: src/materialSymbolsSubset.test.mjs fails when a source
names a glyph this list is missing, because a missing glyph does not draw names a glyph this list is missing, because a missing glyph does not draw
+1
View File
@@ -1294,6 +1294,7 @@
"src/i18n/locales/de.json", "src/i18n/locales/de.json",
"src/i18n/locales/es.json", "src/i18n/locales/es.json",
"src/i18n/locales/fr.json", "src/i18n/locales/fr.json",
"src/i18n/locales/he.json",
"src/i18n/locales/it.json", "src/i18n/locales/it.json",
"src/i18n/locales/ja.json", "src/i18n/locales/ja.json",
"src/i18n/locales/pt.json", "src/i18n/locales/pt.json",
+46
View File
@@ -73,6 +73,7 @@
"src/i18n/locales/de.json", "src/i18n/locales/de.json",
"src/i18n/locales/es.json", "src/i18n/locales/es.json",
"src/i18n/locales/fr.json", "src/i18n/locales/fr.json",
"src/i18n/locales/he.json",
"src/i18n/locales/it.json", "src/i18n/locales/it.json",
"src/i18n/locales/ja.json", "src/i18n/locales/ja.json",
"src/i18n/locales/pt.json", "src/i18n/locales/pt.json",
@@ -656,6 +657,7 @@
"src/i18n/locales/de.json", "src/i18n/locales/de.json",
"src/i18n/locales/es.json", "src/i18n/locales/es.json",
"src/i18n/locales/fr.json", "src/i18n/locales/fr.json",
"src/i18n/locales/he.json",
"src/i18n/locales/it.json", "src/i18n/locales/it.json",
"src/i18n/locales/ja.json", "src/i18n/locales/ja.json",
"src/i18n/locales/pt.json", "src/i18n/locales/pt.json",
@@ -704,6 +706,7 @@
"src/i18n/locales/de.json", "src/i18n/locales/de.json",
"src/i18n/locales/es.json", "src/i18n/locales/es.json",
"src/i18n/locales/fr.json", "src/i18n/locales/fr.json",
"src/i18n/locales/he.json",
"src/i18n/locales/it.json", "src/i18n/locales/it.json",
"src/i18n/locales/ja.json", "src/i18n/locales/ja.json",
"src/i18n/locales/pt.json", "src/i18n/locales/pt.json",
@@ -745,6 +748,7 @@
"src/i18n/locales/de.json", "src/i18n/locales/de.json",
"src/i18n/locales/es.json", "src/i18n/locales/es.json",
"src/i18n/locales/fr.json", "src/i18n/locales/fr.json",
"src/i18n/locales/he.json",
"src/i18n/locales/it.json", "src/i18n/locales/it.json",
"src/i18n/locales/ja.json", "src/i18n/locales/ja.json",
"src/i18n/locales/pt.json", "src/i18n/locales/pt.json",
@@ -788,6 +792,7 @@
"src/i18n/locales/de.json", "src/i18n/locales/de.json",
"src/i18n/locales/es.json", "src/i18n/locales/es.json",
"src/i18n/locales/fr.json", "src/i18n/locales/fr.json",
"src/i18n/locales/he.json",
"src/i18n/locales/it.json", "src/i18n/locales/it.json",
"src/i18n/locales/ja.json", "src/i18n/locales/ja.json",
"src/i18n/locales/pt.json", "src/i18n/locales/pt.json",
@@ -815,6 +820,7 @@
"src/i18n/locales/de.json", "src/i18n/locales/de.json",
"src/i18n/locales/es.json", "src/i18n/locales/es.json",
"src/i18n/locales/fr.json", "src/i18n/locales/fr.json",
"src/i18n/locales/he.json",
"src/i18n/locales/it.json", "src/i18n/locales/it.json",
"src/i18n/locales/ja.json", "src/i18n/locales/ja.json",
"src/i18n/locales/pt.json", "src/i18n/locales/pt.json",
@@ -845,6 +851,7 @@
"src/i18n/locales/de.json", "src/i18n/locales/de.json",
"src/i18n/locales/es.json", "src/i18n/locales/es.json",
"src/i18n/locales/fr.json", "src/i18n/locales/fr.json",
"src/i18n/locales/he.json",
"src/i18n/locales/it.json", "src/i18n/locales/it.json",
"src/i18n/locales/ja.json", "src/i18n/locales/ja.json",
"src/i18n/locales/pt.json", "src/i18n/locales/pt.json",
@@ -882,6 +889,7 @@
"src/i18n/locales/de.json", "src/i18n/locales/de.json",
"src/i18n/locales/es.json", "src/i18n/locales/es.json",
"src/i18n/locales/fr.json", "src/i18n/locales/fr.json",
"src/i18n/locales/he.json",
"src/i18n/locales/it.json", "src/i18n/locales/it.json",
"src/i18n/locales/ja.json", "src/i18n/locales/ja.json",
"src/i18n/locales/pt.json", "src/i18n/locales/pt.json",
@@ -951,6 +959,7 @@
"src/i18n/locales/de.json", "src/i18n/locales/de.json",
"src/i18n/locales/es.json", "src/i18n/locales/es.json",
"src/i18n/locales/fr.json", "src/i18n/locales/fr.json",
"src/i18n/locales/he.json",
"src/i18n/locales/it.json", "src/i18n/locales/it.json",
"src/i18n/locales/ja.json", "src/i18n/locales/ja.json",
"src/i18n/locales/pt.json", "src/i18n/locales/pt.json",
@@ -978,6 +987,7 @@
"src/i18n/locales/de.json", "src/i18n/locales/de.json",
"src/i18n/locales/es.json", "src/i18n/locales/es.json",
"src/i18n/locales/fr.json", "src/i18n/locales/fr.json",
"src/i18n/locales/he.json",
"src/i18n/locales/it.json", "src/i18n/locales/it.json",
"src/i18n/locales/ja.json", "src/i18n/locales/ja.json",
"src/i18n/locales/pt.json", "src/i18n/locales/pt.json",
@@ -1180,6 +1190,7 @@
"src/i18n/locales/de.json", "src/i18n/locales/de.json",
"src/i18n/locales/es.json", "src/i18n/locales/es.json",
"src/i18n/locales/fr.json", "src/i18n/locales/fr.json",
"src/i18n/locales/he.json",
"src/i18n/locales/it.json", "src/i18n/locales/it.json",
"src/i18n/locales/ja.json", "src/i18n/locales/ja.json",
"src/i18n/locales/pt.json", "src/i18n/locales/pt.json",
@@ -1372,6 +1383,7 @@
"src/i18n/locales/de.json", "src/i18n/locales/de.json",
"src/i18n/locales/es.json", "src/i18n/locales/es.json",
"src/i18n/locales/fr.json", "src/i18n/locales/fr.json",
"src/i18n/locales/he.json",
"src/i18n/locales/it.json", "src/i18n/locales/it.json",
"src/i18n/locales/ja.json", "src/i18n/locales/ja.json",
"src/i18n/locales/pt.json", "src/i18n/locales/pt.json",
@@ -1459,6 +1471,7 @@
"src/i18n/locales/de.json", "src/i18n/locales/de.json",
"src/i18n/locales/es.json", "src/i18n/locales/es.json",
"src/i18n/locales/fr.json", "src/i18n/locales/fr.json",
"src/i18n/locales/he.json",
"src/i18n/locales/it.json", "src/i18n/locales/it.json",
"src/i18n/locales/ja.json", "src/i18n/locales/ja.json",
"src/i18n/locales/pt.json", "src/i18n/locales/pt.json",
@@ -1521,6 +1534,7 @@
"src/i18n/locales/de.json", "src/i18n/locales/de.json",
"src/i18n/locales/es.json", "src/i18n/locales/es.json",
"src/i18n/locales/fr.json", "src/i18n/locales/fr.json",
"src/i18n/locales/he.json",
"src/i18n/locales/it.json", "src/i18n/locales/it.json",
"src/i18n/locales/ja.json", "src/i18n/locales/ja.json",
"src/i18n/locales/pt.json", "src/i18n/locales/pt.json",
@@ -1598,6 +1612,7 @@
"src/i18n/locales/de.json", "src/i18n/locales/de.json",
"src/i18n/locales/es.json", "src/i18n/locales/es.json",
"src/i18n/locales/fr.json", "src/i18n/locales/fr.json",
"src/i18n/locales/he.json",
"src/i18n/locales/it.json", "src/i18n/locales/it.json",
"src/i18n/locales/ja.json", "src/i18n/locales/ja.json",
"src/i18n/locales/pt.json", "src/i18n/locales/pt.json",
@@ -1635,6 +1650,7 @@
"src/i18n/locales/de.json", "src/i18n/locales/de.json",
"src/i18n/locales/es.json", "src/i18n/locales/es.json",
"src/i18n/locales/fr.json", "src/i18n/locales/fr.json",
"src/i18n/locales/he.json",
"src/i18n/locales/it.json", "src/i18n/locales/it.json",
"src/i18n/locales/ja.json", "src/i18n/locales/ja.json",
"src/i18n/locales/pt.json", "src/i18n/locales/pt.json",
@@ -1676,6 +1692,7 @@
"src/i18n/locales/de.json", "src/i18n/locales/de.json",
"src/i18n/locales/es.json", "src/i18n/locales/es.json",
"src/i18n/locales/fr.json", "src/i18n/locales/fr.json",
"src/i18n/locales/he.json",
"src/i18n/locales/it.json", "src/i18n/locales/it.json",
"src/i18n/locales/ja.json", "src/i18n/locales/ja.json",
"src/i18n/locales/pt.json", "src/i18n/locales/pt.json",
@@ -1716,6 +1733,7 @@
"src/i18n/locales/de.json", "src/i18n/locales/de.json",
"src/i18n/locales/es.json", "src/i18n/locales/es.json",
"src/i18n/locales/fr.json", "src/i18n/locales/fr.json",
"src/i18n/locales/he.json",
"src/i18n/locales/it.json", "src/i18n/locales/it.json",
"src/i18n/locales/ja.json", "src/i18n/locales/ja.json",
"src/i18n/locales/pt.json", "src/i18n/locales/pt.json",
@@ -1764,6 +1782,7 @@
"src/i18n/locales/de.json", "src/i18n/locales/de.json",
"src/i18n/locales/es.json", "src/i18n/locales/es.json",
"src/i18n/locales/fr.json", "src/i18n/locales/fr.json",
"src/i18n/locales/he.json",
"src/i18n/locales/it.json", "src/i18n/locales/it.json",
"src/i18n/locales/ja.json", "src/i18n/locales/ja.json",
"src/i18n/locales/pt.json", "src/i18n/locales/pt.json",
@@ -1816,6 +1835,7 @@
"src/i18n/locales/de.json", "src/i18n/locales/de.json",
"src/i18n/locales/es.json", "src/i18n/locales/es.json",
"src/i18n/locales/fr.json", "src/i18n/locales/fr.json",
"src/i18n/locales/he.json",
"src/i18n/locales/it.json", "src/i18n/locales/it.json",
"src/i18n/locales/ja.json", "src/i18n/locales/ja.json",
"src/i18n/locales/pt.json", "src/i18n/locales/pt.json",
@@ -1863,6 +1883,7 @@
"src/i18n/locales/de.json", "src/i18n/locales/de.json",
"src/i18n/locales/es.json", "src/i18n/locales/es.json",
"src/i18n/locales/fr.json", "src/i18n/locales/fr.json",
"src/i18n/locales/he.json",
"src/i18n/locales/it.json", "src/i18n/locales/it.json",
"src/i18n/locales/ja.json", "src/i18n/locales/ja.json",
"src/i18n/locales/pt.json", "src/i18n/locales/pt.json",
@@ -1926,6 +1947,7 @@
"src/i18n/locales/de.json", "src/i18n/locales/de.json",
"src/i18n/locales/es.json", "src/i18n/locales/es.json",
"src/i18n/locales/fr.json", "src/i18n/locales/fr.json",
"src/i18n/locales/he.json",
"src/i18n/locales/it.json", "src/i18n/locales/it.json",
"src/i18n/locales/ja.json", "src/i18n/locales/ja.json",
"src/i18n/locales/pt.json", "src/i18n/locales/pt.json",
@@ -1963,6 +1985,7 @@
"src/i18n/locales/de.json", "src/i18n/locales/de.json",
"src/i18n/locales/es.json", "src/i18n/locales/es.json",
"src/i18n/locales/fr.json", "src/i18n/locales/fr.json",
"src/i18n/locales/he.json",
"src/i18n/locales/it.json", "src/i18n/locales/it.json",
"src/i18n/locales/ja.json", "src/i18n/locales/ja.json",
"src/i18n/locales/pt.json", "src/i18n/locales/pt.json",
@@ -2020,6 +2043,7 @@
"src/i18n/locales/de.json", "src/i18n/locales/de.json",
"src/i18n/locales/es.json", "src/i18n/locales/es.json",
"src/i18n/locales/fr.json", "src/i18n/locales/fr.json",
"src/i18n/locales/he.json",
"src/i18n/locales/it.json", "src/i18n/locales/it.json",
"src/i18n/locales/ja.json", "src/i18n/locales/ja.json",
"src/i18n/locales/pt.json", "src/i18n/locales/pt.json",
@@ -2091,6 +2115,7 @@
"src/i18n/locales/de.json", "src/i18n/locales/de.json",
"src/i18n/locales/es.json", "src/i18n/locales/es.json",
"src/i18n/locales/fr.json", "src/i18n/locales/fr.json",
"src/i18n/locales/he.json",
"src/i18n/locales/it.json", "src/i18n/locales/it.json",
"src/i18n/locales/ja.json", "src/i18n/locales/ja.json",
"src/i18n/locales/pt.json", "src/i18n/locales/pt.json",
@@ -2132,6 +2157,7 @@
"src/i18n/locales/de.json", "src/i18n/locales/de.json",
"src/i18n/locales/es.json", "src/i18n/locales/es.json",
"src/i18n/locales/fr.json", "src/i18n/locales/fr.json",
"src/i18n/locales/he.json",
"src/i18n/locales/it.json", "src/i18n/locales/it.json",
"src/i18n/locales/ja.json", "src/i18n/locales/ja.json",
"src/i18n/locales/pt.json", "src/i18n/locales/pt.json",
@@ -2182,6 +2208,7 @@
"src/i18n/locales/de.json", "src/i18n/locales/de.json",
"src/i18n/locales/es.json", "src/i18n/locales/es.json",
"src/i18n/locales/fr.json", "src/i18n/locales/fr.json",
"src/i18n/locales/he.json",
"src/i18n/locales/it.json", "src/i18n/locales/it.json",
"src/i18n/locales/ja.json", "src/i18n/locales/ja.json",
"src/i18n/locales/pt.json", "src/i18n/locales/pt.json",
@@ -2212,6 +2239,7 @@
"src/i18n/locales/de.json", "src/i18n/locales/de.json",
"src/i18n/locales/es.json", "src/i18n/locales/es.json",
"src/i18n/locales/fr.json", "src/i18n/locales/fr.json",
"src/i18n/locales/he.json",
"src/i18n/locales/it.json", "src/i18n/locales/it.json",
"src/i18n/locales/ja.json", "src/i18n/locales/ja.json",
"src/i18n/locales/pt.json", "src/i18n/locales/pt.json",
@@ -2276,6 +2304,7 @@
"src/i18n/locales/de.json", "src/i18n/locales/de.json",
"src/i18n/locales/es.json", "src/i18n/locales/es.json",
"src/i18n/locales/fr.json", "src/i18n/locales/fr.json",
"src/i18n/locales/he.json",
"src/i18n/locales/it.json", "src/i18n/locales/it.json",
"src/i18n/locales/ja.json", "src/i18n/locales/ja.json",
"src/i18n/locales/pt.json", "src/i18n/locales/pt.json",
@@ -2354,6 +2383,7 @@
"src/i18n/locales/de.json", "src/i18n/locales/de.json",
"src/i18n/locales/es.json", "src/i18n/locales/es.json",
"src/i18n/locales/fr.json", "src/i18n/locales/fr.json",
"src/i18n/locales/he.json",
"src/i18n/locales/it.json", "src/i18n/locales/it.json",
"src/i18n/locales/ja.json", "src/i18n/locales/ja.json",
"src/i18n/locales/pt.json", "src/i18n/locales/pt.json",
@@ -3304,6 +3334,7 @@
"src/i18n/locales/de.json", "src/i18n/locales/de.json",
"src/i18n/locales/es.json", "src/i18n/locales/es.json",
"src/i18n/locales/fr.json", "src/i18n/locales/fr.json",
"src/i18n/locales/he.json",
"src/i18n/locales/it.json", "src/i18n/locales/it.json",
"src/i18n/locales/ja.json", "src/i18n/locales/ja.json",
"src/i18n/locales/pt.json", "src/i18n/locales/pt.json",
@@ -4042,6 +4073,7 @@
"src/i18n/locales/de.json", "src/i18n/locales/de.json",
"src/i18n/locales/es.json", "src/i18n/locales/es.json",
"src/i18n/locales/fr.json", "src/i18n/locales/fr.json",
"src/i18n/locales/he.json",
"src/i18n/locales/it.json", "src/i18n/locales/it.json",
"src/i18n/locales/ja.json", "src/i18n/locales/ja.json",
"src/i18n/locales/pt.json", "src/i18n/locales/pt.json",
@@ -4226,6 +4258,7 @@
"src/i18n/locales/de.json", "src/i18n/locales/de.json",
"src/i18n/locales/es.json", "src/i18n/locales/es.json",
"src/i18n/locales/fr.json", "src/i18n/locales/fr.json",
"src/i18n/locales/he.json",
"src/i18n/locales/it.json", "src/i18n/locales/it.json",
"src/i18n/locales/ja.json", "src/i18n/locales/ja.json",
"src/i18n/locales/pt.json", "src/i18n/locales/pt.json",
@@ -4259,6 +4292,7 @@
"src/i18n/locales/de.json", "src/i18n/locales/de.json",
"src/i18n/locales/es.json", "src/i18n/locales/es.json",
"src/i18n/locales/fr.json", "src/i18n/locales/fr.json",
"src/i18n/locales/he.json",
"src/i18n/locales/it.json", "src/i18n/locales/it.json",
"src/i18n/locales/ja.json", "src/i18n/locales/ja.json",
"src/i18n/locales/pt.json", "src/i18n/locales/pt.json",
@@ -4313,6 +4347,7 @@
"src/i18n/locales/de.json", "src/i18n/locales/de.json",
"src/i18n/locales/es.json", "src/i18n/locales/es.json",
"src/i18n/locales/fr.json", "src/i18n/locales/fr.json",
"src/i18n/locales/he.json",
"src/i18n/locales/it.json", "src/i18n/locales/it.json",
"src/i18n/locales/ja.json", "src/i18n/locales/ja.json",
"src/i18n/locales/pt.json", "src/i18n/locales/pt.json",
@@ -4351,6 +4386,7 @@
"src/i18n/locales/de.json", "src/i18n/locales/de.json",
"src/i18n/locales/es.json", "src/i18n/locales/es.json",
"src/i18n/locales/fr.json", "src/i18n/locales/fr.json",
"src/i18n/locales/he.json",
"src/i18n/locales/it.json", "src/i18n/locales/it.json",
"src/i18n/locales/ja.json", "src/i18n/locales/ja.json",
"src/i18n/locales/pt.json", "src/i18n/locales/pt.json",
@@ -4457,6 +4493,7 @@
"src/i18n/locales/de.json", "src/i18n/locales/de.json",
"src/i18n/locales/es.json", "src/i18n/locales/es.json",
"src/i18n/locales/fr.json", "src/i18n/locales/fr.json",
"src/i18n/locales/he.json",
"src/i18n/locales/it.json", "src/i18n/locales/it.json",
"src/i18n/locales/ja.json", "src/i18n/locales/ja.json",
"src/i18n/locales/pt.json", "src/i18n/locales/pt.json",
@@ -4530,6 +4567,7 @@
"src/i18n/locales/de.json", "src/i18n/locales/de.json",
"src/i18n/locales/es.json", "src/i18n/locales/es.json",
"src/i18n/locales/fr.json", "src/i18n/locales/fr.json",
"src/i18n/locales/he.json",
"src/i18n/locales/it.json", "src/i18n/locales/it.json",
"src/i18n/locales/ja.json", "src/i18n/locales/ja.json",
"src/i18n/locales/pt.json", "src/i18n/locales/pt.json",
@@ -4822,6 +4860,7 @@
"src/i18n/locales/de.json", "src/i18n/locales/de.json",
"src/i18n/locales/es.json", "src/i18n/locales/es.json",
"src/i18n/locales/fr.json", "src/i18n/locales/fr.json",
"src/i18n/locales/he.json",
"src/i18n/locales/it.json", "src/i18n/locales/it.json",
"src/i18n/locales/ja.json", "src/i18n/locales/ja.json",
"src/i18n/locales/pt.json", "src/i18n/locales/pt.json",
@@ -4877,6 +4916,7 @@
"src/i18n/locales/de.json", "src/i18n/locales/de.json",
"src/i18n/locales/es.json", "src/i18n/locales/es.json",
"src/i18n/locales/fr.json", "src/i18n/locales/fr.json",
"src/i18n/locales/he.json",
"src/i18n/locales/it.json", "src/i18n/locales/it.json",
"src/i18n/locales/ja.json", "src/i18n/locales/ja.json",
"src/i18n/locales/pt.json", "src/i18n/locales/pt.json",
@@ -4940,6 +4980,7 @@
"src/i18n/locales/de.json", "src/i18n/locales/de.json",
"src/i18n/locales/es.json", "src/i18n/locales/es.json",
"src/i18n/locales/fr.json", "src/i18n/locales/fr.json",
"src/i18n/locales/he.json",
"src/i18n/locales/it.json", "src/i18n/locales/it.json",
"src/i18n/locales/ja.json", "src/i18n/locales/ja.json",
"src/i18n/locales/pt.json", "src/i18n/locales/pt.json",
@@ -4964,6 +5005,7 @@
"src/i18n/locales/de.json", "src/i18n/locales/de.json",
"src/i18n/locales/es.json", "src/i18n/locales/es.json",
"src/i18n/locales/fr.json", "src/i18n/locales/fr.json",
"src/i18n/locales/he.json",
"src/i18n/locales/it.json", "src/i18n/locales/it.json",
"src/i18n/locales/ja.json", "src/i18n/locales/ja.json",
"src/i18n/locales/pt.json", "src/i18n/locales/pt.json",
@@ -5034,6 +5076,7 @@
"src/i18n/locales/de.json", "src/i18n/locales/de.json",
"src/i18n/locales/es.json", "src/i18n/locales/es.json",
"src/i18n/locales/fr.json", "src/i18n/locales/fr.json",
"src/i18n/locales/he.json",
"src/i18n/locales/it.json", "src/i18n/locales/it.json",
"src/i18n/locales/ja.json", "src/i18n/locales/ja.json",
"src/i18n/locales/pt.json", "src/i18n/locales/pt.json",
@@ -5083,6 +5126,7 @@
"src/i18n/locales/de.json", "src/i18n/locales/de.json",
"src/i18n/locales/es.json", "src/i18n/locales/es.json",
"src/i18n/locales/fr.json", "src/i18n/locales/fr.json",
"src/i18n/locales/he.json",
"src/i18n/locales/it.json", "src/i18n/locales/it.json",
"src/i18n/locales/ja.json", "src/i18n/locales/ja.json",
"src/i18n/locales/pt.json", "src/i18n/locales/pt.json",
@@ -5290,6 +5334,7 @@
"src/i18n/locales/de.json", "src/i18n/locales/de.json",
"src/i18n/locales/es.json", "src/i18n/locales/es.json",
"src/i18n/locales/fr.json", "src/i18n/locales/fr.json",
"src/i18n/locales/he.json",
"src/i18n/locales/it.json", "src/i18n/locales/it.json",
"src/i18n/locales/ja.json", "src/i18n/locales/ja.json",
"src/i18n/locales/pt.json", "src/i18n/locales/pt.json",
@@ -5336,6 +5381,7 @@
"src/i18n/locales/de.json", "src/i18n/locales/de.json",
"src/i18n/locales/es.json", "src/i18n/locales/es.json",
"src/i18n/locales/fr.json", "src/i18n/locales/fr.json",
"src/i18n/locales/he.json",
"src/i18n/locales/it.json", "src/i18n/locales/it.json",
"src/i18n/locales/ja.json", "src/i18n/locales/ja.json",
"src/i18n/locales/pt.json", "src/i18n/locales/pt.json",
+1
View File
@@ -143,6 +143,7 @@ for (const [locale, summary] of [
['ja', '東京上空のLive Flights'], ['ja', '東京上空のLive Flights'],
['zh-CN', '北京航班stale数据'], ['zh-CN', '北京航班stale数据'],
['ru', 'Остин Live-Flights частично'], ['ru', 'Остин Live-Flights частично'],
['he', 'תל אביב stale טיסות'],
]) { ]) {
test(`English flush against other text is an echo: ${summary}`, () => { test(`English flush against other text is an echo: ${summary}`, () => {
assert.ok(hudSummaryHasEnglish(summary, locale, ['stale', 'Live Flights'])); assert.ok(hudSummaryHasEnglish(summary, locale, ['stale', 'Live Flights']));
+1 -1
View File
@@ -110,7 +110,7 @@ const SUMMARY_LANGUAGES = Object.freeze({
const UNSPACED_LOCALES = /^(?:ja|zh)(?:-|$)/; const UNSPACED_LOCALES = /^(?:ja|zh)(?:-|$)/;
/** Shipped locales not written in Latin letters. */ /** Shipped locales not written in Latin letters. */
const NON_LATIN_LOCALES = /^(?:ar|ja|ru|uk|zh)(?:-|$)/; const NON_LATIN_LOCALES = /^(?:ar|ja|ru|uk|zh|he)(?:-|$)/;
/** Scripts whose letters extend a word; any other neighbour ends it. */ /** Scripts whose letters extend a word; any other neighbour ends it. */
const WORD_SCRIPTS = [ const WORD_SCRIPTS = [
+6
View File
@@ -41,6 +41,12 @@ test('normalizeLocale folds regional variants and rejects unshipped tags', () =>
assert.equal(normalizeLocale('de'), 'de'); assert.equal(normalizeLocale('de'), 'de');
assert.equal(normalizeLocale('de-AT'), 'de'); assert.equal(normalizeLocale('de-AT'), 'de');
assert.equal(normalizeLocale('de_CH'), 'de'); assert.equal(normalizeLocale('de_CH'), 'de');
// Hebrew keeps Western digits like its catalog; regional tags fold to he,
// and the legacy tag iw is still what some browsers and OSes label Hebrew.
assert.equal(normalizeLocale('he'), 'he');
assert.equal(normalizeLocale('he-IL'), 'he');
assert.equal(normalizeLocale('iw'), 'he');
assert.equal(normalizeLocale('iw-IL'), 'he');
assert.equal(normalizeLocale('fr'), 'fr'); assert.equal(normalizeLocale('fr'), 'fr');
assert.equal(normalizeLocale('fr-CA'), 'fr'); assert.equal(normalizeLocale('fr-CA'), 'fr');
assert.equal(normalizeLocale('FR_be'), 'fr'); assert.equal(normalizeLocale('FR_be'), 'fr');
+2
View File
@@ -22,6 +22,7 @@ import ar from './locales/ar.json' with { type: 'json' };
import de from './locales/de.json' with { type: 'json' }; import de from './locales/de.json' with { type: 'json' };
import es from './locales/es.json' with { type: 'json' }; import es from './locales/es.json' with { type: 'json' };
import fr from './locales/fr.json' with { type: 'json' }; import fr from './locales/fr.json' with { type: 'json' };
import he from './locales/he.json' with { type: 'json' };
import it from './locales/it.json' with { type: 'json' }; import it from './locales/it.json' with { type: 'json' };
import ja from './locales/ja.json' with { type: 'json' }; import ja from './locales/ja.json' with { type: 'json' };
import pt from './locales/pt.json' with { type: 'json' }; import pt from './locales/pt.json' with { type: 'json' };
@@ -35,6 +36,7 @@ const CATALOGS = Object.freeze({
de, de,
es, es,
fr, fr,
he,
it, it,
ja, ja,
pt, pt,
+6 -2
View File
@@ -14,6 +14,7 @@ export const LOCALES = Object.freeze([
'de', 'de',
'es', 'es',
'fr', 'fr',
'he',
'it', 'it',
'ja', 'ja',
'pt', 'pt',
@@ -30,6 +31,7 @@ export const LOCALE_NAMES = Object.freeze({
de: 'Deutsch', de: 'Deutsch',
es: 'Español', es: 'Español',
fr: 'Français', fr: 'Français',
he: 'עברית',
it: 'Italiano', it: 'Italiano',
ja: '日本語', ja: '日本語',
pt: 'Português (Brasil)', pt: 'Português (Brasil)',
@@ -40,7 +42,7 @@ export const LOCALE_NAMES = Object.freeze({
}); });
/** Right-to-left locales. */ /** Right-to-left locales. */
const RTL_LOCALES = Object.freeze(['ar']); const RTL_LOCALES = Object.freeze(['ar', 'he']);
/** Whether a shipped locale reads right to left. */ /** Whether a shipped locale reads right to left. */
export function isRtlLocale(locale) { export function isRtlLocale(locale) {
@@ -62,7 +64,8 @@ export function intlLocale(locale) {
/** /**
* Map any language tag to a shipped locale: 'ru-RU' and 'RU' → 'ru', * Map any language tag to a shipped locale: 'ru-RU' and 'RU' → 'ru',
* 'ar-EG' and 'ar-SA' → 'ar', * 'ar-EG' and 'ar-SA' → 'ar', 'he-IL' and 'he' → 'he', and the
* legacy tag 'iw'/'iw-IL' → 'he',
* 'de-AT' and 'de_CH' → 'de', 'ja-JP' → 'ja', * 'de-AT' and 'de_CH' → 'de', 'ja-JP' → 'ja',
* 'pt-BR' and 'pt-PT' → 'pt' (Brazilian Portuguese), * 'pt-BR' and 'pt-PT' → 'pt' (Brazilian Portuguese),
* 'zh', 'zh-CN' and 'zh-Hans-SG' → 'zh-CN'; * 'zh', 'zh-CN' and 'zh-Hans-SG' → 'zh-CN';
@@ -78,6 +81,7 @@ export function normalizeLocale(candidate) {
? 'zh-Hant' ? 'zh-Hant'
: 'zh-CN'; : 'zh-CN';
} }
if (primary === 'iw') return 'he';
return LOCALES.includes(primary) ? primary : null; return LOCALES.includes(primary) ? primary : null;
} }
File diff suppressed because it is too large. Load diff
+97
View File
@@ -1190,5 +1190,102 @@
"acartia.io ↗", "acartia.io ↗",
"iNaturalist ↗", "iNaturalist ↗",
"iNaturalist" "iNaturalist"
],
"he": [
"{speed} kn",
"GEO",
"SAR",
"EARTH WATCH",
"CRT",
"NVG",
"FLIR",
"AIS: --",
"REC",
"HDG {value}°",
"BRG {value}°",
"BRG —",
"CRS {value}°",
"FOV {value}°",
"CAL {value}",
"SRC {value}",
"▲ {frp} MW",
"≥ {value} MW",
"Intel Picture",
"Kinetic 30d",
"Border Crisis",
"Iran Picture",
"Iran Kinetic",
"Yemen FLOT",
"Latest Drop",
"1090 MHz",
"978 MHz UAT",
"ALT {altitude} FT · GS {speed} KT · TRK {track}",
"V/S {rate} FPM",
"Sentinel-2",
"Landsat 8/9",
"Google Street View",
"GOOGLE STREET VIEW",
"AIS: {name}",
"{type} SPD: {speed} HDG: {heading}",
"POS: {time}Z",
"{source} HTTP {status}",
"NNE",
"NE",
"ENE",
"ESE",
"SE",
"SSE",
"SSW",
"SW",
"WSW",
"WNW",
"NW",
"NNW",
"WHOT/BHOT",
"HDG",
"FOV",
"HGT",
"ΔN",
"ΔE",
"CAL · {badge}",
"DEST ---°",
"SQUAWK {code} · {status}",
"DEST {bearing}",
"CCTV",
"S2 GhostMaps",
"Street Level",
"STREET LEVEL",
"km/h",
"mph",
"hPa",
"kt",
"kts",
"ft",
"km",
"min",
"dB",
"KTS",
"FT",
"FLT",
"MIL",
"WX",
"MSG/S",
"FM MHZ",
"Acartia",
"CC BY 4.0",
"Salish Sea",
"iNaturalist",
"Be Whale Wise",
"MIC",
"STARLINK",
"STARLINK ···",
"STARLINK ✕",
"{range} NM",
"NORAD {id}",
"EPOCH {time}",
"TLE",
"{value} KM",
"{value} KM/S",
"Ironbow"
] ]
} }
+18 -10
View File
@@ -7,9 +7,10 @@
*/ */
/** Shared visual tokens used by every world-overlay source. */ /** Shared visual tokens used by every world-overlay source. */
// Map-card fonts: JetBrains Mono has no Arabic glyphs and the generic // Map-card fonts: JetBrains Mono has no Arabic or Hebrew glyphs and the
// monospace fallback is a fixed-pitch Arabic face that stretches every word; // generic monospace fallback is a fixed-pitch Arabic face that stretches every
// Arabic text falls through to Noto Sans Arabic (loaded with the page fonts). // word; Arabic falls through to Noto Sans Arabic, Hebrew to Noto Sans Hebrew
// (both loaded with the page fonts).
export const WORLD_OVERLAY_STYLE = Object.freeze({ export const WORLD_OVERLAY_STYLE = Object.freeze({
background: 'rgba(4, 12, 16, 0.82)', background: 'rgba(4, 12, 16, 0.82)',
selectedBackground: 'rgba(5, 18, 24, 0.94)', selectedBackground: 'rgba(5, 18, 24, 0.94)',
@@ -19,13 +20,20 @@ export const WORLD_OVERLAY_STYLE = Object.freeze({
detail: 'rgba(147, 161, 173, 0.92)', detail: 'rgba(147, 161, 173, 0.92)',
leader: 'rgba(147, 213, 228, 0.58)', leader: 'rgba(147, 213, 228, 0.58)',
accent: '#6be8ff', accent: '#6be8ff',
fontLabel: '500 10px "JetBrains Mono", "Noto Sans Arabic", monospace', fontLabel:
fontTrack: '600 10px "JetBrains Mono", "Noto Sans Arabic", monospace', '500 10px "JetBrains Mono", "Noto Sans Arabic", "Noto Sans Hebrew", monospace',
fontTitle: '600 12px "JetBrains Mono", "Noto Sans Arabic", monospace', fontTrack:
fontDetail: '500 10.5px "JetBrains Mono", "Noto Sans Arabic", monospace', '600 10px "JetBrains Mono", "Noto Sans Arabic", "Noto Sans Hebrew", monospace',
fontSelected: '600 13px "JetBrains Mono", "Noto Sans Arabic", monospace', fontTitle:
fontTrackedTitle: '600 13px "JetBrains Mono", "Noto Sans Arabic", monospace', '600 12px "JetBrains Mono", "Noto Sans Arabic", "Noto Sans Hebrew", monospace',
fontTrackedDetail: '500 11px "JetBrains Mono", "Noto Sans Arabic", monospace', fontDetail:
'500 10.5px "JetBrains Mono", "Noto Sans Arabic", "Noto Sans Hebrew", monospace',
fontSelected:
'600 13px "JetBrains Mono", "Noto Sans Arabic", "Noto Sans Hebrew", monospace',
fontTrackedTitle:
'600 13px "JetBrains Mono", "Noto Sans Arabic", "Noto Sans Hebrew", monospace',
fontTrackedDetail:
'500 11px "JetBrains Mono", "Noto Sans Arabic", "Noto Sans Hebrew", monospace',
radius: 4, radius: 4,
anchorDotRadius: 3.2, anchorDotRadius: 3.2,
anchorDotStrokeWidth: 1, anchorDotStrokeWidth: 1,
+4
View File
@@ -52,6 +52,7 @@ test('the current language comes first, the rest by their own names', () => {
'fr', 'fr',
'it', 'it',
'pt', 'pt',
'he',
'ar', 'ar',
'zh-CN', 'zh-CN',
'zh-Hant', 'zh-Hant',
@@ -75,6 +76,7 @@ test('the current language comes first, the rest by their own names', () => {
'pt', 'pt',
'ru', 'ru',
'uk', 'uk',
'he',
'ar', 'ar',
'zh-CN', 'zh-CN',
'zh-Hant', 'zh-Hant',
@@ -98,6 +100,7 @@ test('the current language comes first, the rest by their own names', () => {
'pt', 'pt',
'ru', 'ru',
'uk', 'uk',
'he',
'ar', 'ar',
'zh-CN', 'zh-CN',
'zh-Hant', 'zh-Hant',
@@ -122,6 +125,7 @@ test('the current language comes first, the rest by their own names', () => {
'pt', 'pt',
'ru', 'ru',
'uk', 'uk',
'he',
'zh-CN', 'zh-CN',
'zh-Hant', 'zh-Hant',
'ja', 'ja',
+31 -9
View File
@@ -1,13 +1,15 @@
/* Right-to-left languages (Arabic). The chrome mirrors through logical /* Right-to-left languages (Arabic, Hebrew). The chrome mirrors through
properties in the component sheets (inset-inline-*, margin-inline-*); this logical properties in the component sheets (inset-inline-*,
sheet holds only what logical properties cannot express. */ margin-inline-*); this sheet holds only what logical properties cannot
express. */
/* Arabic letters join: letter-spacing tears every word into separate glyphs. /* Arabic letters join: letter-spacing tears every word into separate glyphs.
Hebrew letters do not join, but tracking only spaces their words out.
Latin marked translate="no" and the intel HUD's Latin telemetry keep their Latin marked translate="no" and the intel HUD's Latin telemetry keep their
tracking; the HUD's translated lines are released below. Unlike cjk.css, a tracking; the HUD's translated lines are released below. Unlike cjk.css, a
container that merely holds a translate="no" span is not spared: its own container that merely holds a translate="no" span is not spared: its own
Arabic text would come apart. */ text would come apart. */
html:lang(ar) html:is(:lang(ar), :lang(he))
body body
:not(#intel-hud, #intel-hud *, [translate='no'], [translate='no'] *) { :not(#intel-hud, #intel-hud *, [translate='no'], [translate='no'] *) {
letter-spacing: 0 !important; letter-spacing: 0 !important;
@@ -30,8 +32,7 @@ html[dir='rtl'] #intel-hud :is(.hud-mode, .hud-summary-label, .hud-summary) {
letter apart. Noto Sans Arabic (loaded with the other Google fonts; its letter apart. Noto Sans Arabic (loaded with the other Google fonts; its
files download only when the page shows Arabic) leads the mono stack, so files download only when the page shows Arabic) leads the mono stack, so
the spaces between Arabic words come from it too and not from the the spaces between Arabic words come from it too and not from the
fixed-pitch Latin face. The intel and cockpit HUDs keep JetBrains Mono for fixed-pitch Latin face. */
their Latin telemetry and column-aligned digits. */
html:lang(ar) { html:lang(ar) {
--font-mono: --font-mono:
'Noto Sans Arabic', 'JetBrains Mono', 'SF Mono', 'Fira Code', 'Geeza Pro', 'Noto Sans Arabic', 'JetBrains Mono', 'SF Mono', 'Fira Code', 'Geeza Pro',
@@ -40,9 +41,23 @@ html:lang(ar) {
'Inter', 'Noto Sans Arabic', -apple-system, BlinkMacSystemFont, 'Geeza Pro', 'Inter', 'Noto Sans Arabic', -apple-system, BlinkMacSystemFont, 'Geeza Pro',
Tahoma, sans-serif; Tahoma, sans-serif;
} }
/* Hebrew: JetBrains Mono and Inter also lack Hebrew glyphs. Noto Sans Hebrew
(loaded with the other Google fonts; its files download only when the page
shows Hebrew) leads the stack so the body text keeps one proportional
Hebrew face across platforms. */
html:lang(he) {
--font-mono:
'Noto Sans Hebrew', 'JetBrains Mono', 'SF Mono', 'Fira Code', Tahoma,
monospace;
--font-sans:
'Inter', 'Noto Sans Hebrew', -apple-system, BlinkMacSystemFont, Tahoma,
sans-serif;
}
/* The Global Context lines and their buttons set ui-monospace through the /* The Global Context lines and their buttons set ui-monospace through the
`font` shorthand; route them through the Arabic-aware stack too. */ `font` shorthand; in both RTL languages the words come from the page's
html:lang(ar) own font stack. */
html:is(:lang(ar), :lang(he))
:is( :is(
#military-awareness-panel, #military-awareness-panel,
.military-awareness-controls button, .military-awareness-controls button,
@@ -54,11 +69,18 @@ html:lang(ar)
) { ) {
font-family: var(--font-mono); font-family: var(--font-mono);
} }
/* The intel and cockpit HUDs keep JetBrains Mono leading for their Latin
telemetry and column-aligned digits, with the language's face behind it. */
html:lang(ar) :is(#intel-hud, #cockpit-hud) { html:lang(ar) :is(#intel-hud, #cockpit-hud) {
--font-mono: --font-mono:
'JetBrains Mono', 'SF Mono', 'Fira Code', 'Noto Sans Arabic', 'Geeza Pro', 'JetBrains Mono', 'SF Mono', 'Fira Code', 'Noto Sans Arabic', 'Geeza Pro',
Tahoma, monospace; Tahoma, monospace;
} }
html:lang(he) :is(#intel-hud, #cockpit-hud) {
--font-mono:
'JetBrains Mono', 'SF Mono', 'Fira Code', 'Noto Sans Hebrew', Tahoma,
monospace;
}
/* The Display/Context rail moves to the left edge. It keeps a physical /* The Display/Context rail moves to the left edge. It keeps a physical
`right` in layers.css because the attribution-clearance model `right` in layers.css because the attribution-clearance model