mirror of
https://github.com/bilawalsidhu/gods-eye-view.git
synced 2026-11-04 10:24:02 +00:00
i18n(ar): apply review round 1
Findings from the astra, sol and fable reviews of the Arabic screenshots: - Wording: forecast valid time is «عند», not an expiry; acres are آكر, not feddans; live layers are «تتبّع … المباشر» (not "direct flights"); the straight-ahead lane, turboprop, call sign, reconcile, proximity, retrying, count agreement (colon form, fixed counts, ي for compact day ages) and smaller fixes. Guide and glossary updated. - Right to left: the scene editor's JSON fields are LTR; canvas map cards draw with LTR geometry and an Arabic face; whiteboard callouts anchor inside their cards; HDG/FOV keep their values; the cyclone timestamp no longer splits its date; bare-number counters read 01 / 25, translated ones say «من». - Built-in shot titles of the Nepal shot pack's bootstrap shots localize (the applied pack vouches for them), in every language. Co-Authored-By: Claude Opus 5.5 <[email protected]>
This commit is contained in:
1 parent
91f1f80e60
commit
4f44a17b44
12 files changed
+226
-103
No files matched your search
@@ -19,10 +19,12 @@ Translation sources: abiyeenzo (#915).
|
||||
- **Western digits (0–9)**, as in the catalog. The HUD reads like an instrument panel, and mixing ٠–٩ with Latin codes and coordinates misreads. `intlLocale('ar')` in `src/i18n/locale.js` passes `ar-u-nu-latn` to every Intl formatter, so browsers that default to Arabic-Indic digits for an Arabic region still show 0–9.
|
||||
- **Right to left.** `ar` is in `RTL_LOCALES`, so the page gets `<html dir="rtl">`. The title, style indicator and both rails swap sides; the dock and the corner row reverse. The intel HUD (classification block, MGRS, coordinates, GSD) stays a left-to-right instrument overlay; its translated lines read right to left inside it. Media transport (previous / play / next) and the radio dial stay left to right, as on any player. Layout rules: `src/ui/styles/rtl.css`.
|
||||
- Latin runs inside Arabic sentences (product names, codes, `{placeholders}` filled with English data) are isolated by the bidi algorithm; write them as plain text, without manual direction marks.
|
||||
- Position counters: a translated counter says «من» («القناة {index} من {total}», «الإطار {index} من {total}»); a spaced slash between two isolated numbers swaps them in right-to-left text. Counters the code builds from bare numbers (`01 / 25`, `1 / 3`) are shown left to right by `rtl.css`.
|
||||
- Code stays left to right: the JSON fields of the scene editor are LTR in every language.
|
||||
|
||||
## Units
|
||||
|
||||
Distances and speeds take Arabic abbreviations after the number, with a space: كم، م، كم/س، كم/ث، م/ث، ميل/س، عقدة (knots)، قدم (feet)، مم. Time: ث (seconds)، د (minutes)، س (hours)، يوم. Scientific and radio units stay Latin: MHz، dB، dBFS، dBZ، hPa، MW، MiB. `°` and `%` stay. Hemisphere letters in coordinates: ش (N)، ج (S)، ق (E)، غ (W), as in `21.33ش`.
|
||||
Distances and speeds take Arabic abbreviations after the number, with a space: كم، م، كم/س، كم/ث، م/ث، ميل/س، عقدة (knots)، قدم (feet)، مم. Time: ث (seconds)، د (minutes)، س (hours)، يوم; compact age labels («3d», «5D AGO») use ي: «{count} ي»، «قبل {count} ي», since a plain string cannot make يوم agree with the count. Area: آكر (acres, the value is not converted; فدان is a different unit). Scientific and radio units stay Latin: MHz، dB، dBFS، dBZ، hPa، MW، MiB. `°` and `%` stay. Hemisphere letters in coordinates: ش (N)، ج (S)، ق (E)، غ (W), as in `21.33ش`.
|
||||
|
||||
## Plurals
|
||||
|
||||
@@ -55,6 +57,8 @@ Every form must keep `{count}`. For one and two, Arabic names the quantity with
|
||||
| globe / map / map source / basemap | الكرة الأرضية / الخريطة / مصدر الخريطة / الخريطة الأساسية |
|
||||
| tiles | بلاطات |
|
||||
| aircraft / flight / military flights | طائرة / رحلة / الرحلات العسكرية |
|
||||
| Live Flights / Live Vessels (layers) | تتبّع الرحلات / السفن المباشر (not الرحلات المباشرة) |
|
||||
| call sign | رمز النداء |
|
||||
| vessel, ship | سفينة |
|
||||
| satellite | قمر صناعي |
|
||||
| launch (rocket) / space mission | إطلاق / مهمة فضائية |
|
||||
@@ -78,12 +82,13 @@ Every form must keep `{count}`. For one and two, Arabic names the quantity with
|
||||
| transit / bike share | النقل العام / الدراجات المشتركة |
|
||||
| Previous / Next | السابق / التالي |
|
||||
| compass points (N, NNE, …) | شمال، شمال شمال شرق، … |
|
||||
| forecast / valid / issued | توقع / صالح حتى / صدر |
|
||||
| forecast / valid (time) / issued | توقع / عند، وقت التوقع (not صالح حتى) / صدر |
|
||||
| observation / observed | رصد / مرصود |
|
||||
| field (weather) / model | حقل / نموذج |
|
||||
| parameters / ellipsoid | المعلمات / المجسّم الإهليلجي |
|
||||
| scene cloud | غيوم المشهد |
|
||||
| box (Recent Imagery area) / pin | منطقة / تثبيت |
|
||||
| THIS (voice pointer) | المُشار إليه |
|
||||
| THIS (voice pointer) | هذا |
|
||||
| outline / mark (map annotation) | مخطط / علامة، تعليم |
|
||||
| share (a view or link) | مشاركة |
|
||||
| AI agent | وكيل ذكاء اصطناعي |
|
||||
|
||||
@@ -1,6 +1,14 @@
|
||||
import test from 'node:test';
|
||||
import assert from 'node:assert/strict';
|
||||
import { formatDate, formatNumber, getLocale, setLocale, t, tn } from './index.js';
|
||||
import {
|
||||
formatDate,
|
||||
formatNumber,
|
||||
getLocale,
|
||||
keepCodesWithValues,
|
||||
setLocale,
|
||||
t,
|
||||
tn,
|
||||
} from './index.js';
|
||||
import {
|
||||
applyDocumentLanguage,
|
||||
intlLocale,
|
||||
@@ -109,7 +117,19 @@ test('Arabic uses all six plural forms and spaced HUD units', () => {
|
||||
t('Issued: {time}', { time: '10/28 00:00 UTC' }),
|
||||
'صدر: \u206810/28 00:00 UTC\u2069',
|
||||
);
|
||||
// A Latin code stays beside its own value: HDG 68° · FOV 44°.
|
||||
assert.equal(
|
||||
keepCodesWithValues(
|
||||
t('{city} · HDG {heading} · FOV {fov}', {
|
||||
city: 'Austin',
|
||||
heading: '68°',
|
||||
fov: '44°',
|
||||
}),
|
||||
),
|
||||
'\u2068Austin\u2069 · \u2068HDG \u206868°\u2069\u2069 · \u2068FOV \u206844°\u2069\u2069',
|
||||
);
|
||||
setLocale('ru');
|
||||
assert.equal(keepCodesWithValues('HDG 68°'), 'HDG 68°');
|
||||
assert.equal(formatCockpitDistance(14000), '14 КМ');
|
||||
} finally {
|
||||
setLocale(previous);
|
||||
|
||||
@@ -53,6 +53,20 @@ export function isolate(text) {
|
||||
return isRtlLocale(currentLocale) ? `\u2068${text}\u2069` : String(text);
|
||||
}
|
||||
|
||||
const CODE_BEFORE_VALUE =
|
||||
/(?<![\p{L}\p{N}])([A-Z][A-Z0-9/]+) (\u2068[^\u2068\u2069]*\u2069)/gu;
|
||||
|
||||
/**
|
||||
* A Latin code and the isolated value after it ("HDG 68\u00b0") as one unit in
|
||||
* right-to-left text. Two codes in a row (HDG 68\u00b0 \u00b7 FOV 44\u00b0) otherwise form a
|
||||
* single left-to-right run and the second code ends up beside the first
|
||||
* value. Applies to t() output; a no-op outside RTL locales.
|
||||
*/
|
||||
export function keepCodesWithValues(text) {
|
||||
if (!isRtlLocale(currentLocale)) return text;
|
||||
return String(text).replace(CODE_BEFORE_VALUE, '\u2068$1 $2\u2069');
|
||||
}
|
||||
|
||||
/**
|
||||
* Switch the active locale. Pure state change: call applyDocumentLanguage /
|
||||
* applyDocumentTranslations to reflect it in the DOM.
|
||||
|
||||
+68
-68
@@ -27,21 +27,21 @@
|
||||
"Initializing systems...": "جارٍ تهيئة الأنظمة...",
|
||||
"LIVE DATA OFF": "البيانات المباشرة متوقفة",
|
||||
"MAPPED SITES LOADED": "تم تحميل المواقع المرسومة",
|
||||
"RETRYING MAPPED SITES": "إعادة محاولة المواقع المرسومة",
|
||||
"RETRYING MAPPED SITES": "جارٍ إعادة محاولة تحميل المواقع المرسومة",
|
||||
"FETCHING MAPPED SITES": "جارٍ جلب المواقع المرسومة",
|
||||
"TURNING OFF LIVE DATA": "جارٍ إيقاف البيانات المباشرة",
|
||||
"REFRESHING LIVE DATA": "جارٍ تحديث البيانات المباشرة",
|
||||
"Close data attribution": "إغلاق إسناد البيانات",
|
||||
"MISSION CONTROL · FIRST LAUNCH": "مركز التحكم · أول تشغيل",
|
||||
"Choose your first view": "اختر عرضك الأول",
|
||||
"LIVE CONTACTS": "الأهداف المباشرة",
|
||||
"LIVE CONTACTS": "تتبّع الأهداف المباشر",
|
||||
"Don't show this again": "لا تعرض هذا مرة أخرى",
|
||||
"POWER UP": "تشغيل",
|
||||
"GROUND STATION · PROVIDER SETTINGS": "المحطة الأرضية · إعدادات المزوّدين",
|
||||
"Power up the globe": "شغّل الكرة الأرضية",
|
||||
"SAVE KEYS": "حفظ المفاتيح",
|
||||
"Saving…": "جارٍ الحفظ…",
|
||||
"It feels like a forbidden cockpit—then you realize the sources are public and the data is real.": "يبدو الأمر كقمرة قيادة محظورة—ثم تدرك أن المصادر عامة والبيانات حقيقية.",
|
||||
"It feels like a forbidden cockpit—then you realize the sources are public and the data is real.": "يبدو الأمر كقمرة قيادة محظورة — ثم تدرك أن المصادر عامة والبيانات حقيقية.",
|
||||
"Aircraft, vessels and nearby intelligence": "طائرات وسفن ومعلومات استخباراتية قريبة",
|
||||
"SPACE MISSIONS": "المهام الفضائية",
|
||||
"Launches, spacecraft and orbital context": "عمليات إطلاق ومركبات وسياق مداري",
|
||||
@@ -66,7 +66,7 @@
|
||||
"IMPORT": "استيراد",
|
||||
"RUN LOG": "سجل التشغيل",
|
||||
"Ready": "جاهز",
|
||||
"Starting live contacts…": "جارٍ تشغيل الأهداف المباشرة…",
|
||||
"Starting live contacts…": "جارٍ بدء تتبّع الأهداف المباشر…",
|
||||
"Opening space missions…": "جارٍ فتح المهام الفضائية…",
|
||||
"Scanning active events…": "جارٍ مسح الأحداث النشطة…",
|
||||
"Working…": "جارٍ العمل…",
|
||||
@@ -105,7 +105,7 @@
|
||||
"CONNECTING": "جارٍ الاتصال",
|
||||
"ERROR": "خطأ",
|
||||
"Session ended — cost cap {cost}": "انتهت الجلسة — بلغت حد التكلفة {cost}",
|
||||
"VOICE STANDBY": "الصوت في وضع الاستعداد",
|
||||
"VOICE STANDBY": "الصوت: استعداد",
|
||||
"VOICE ACTIVE": "الصوت نشط",
|
||||
"VOICE UNAVAILABLE": "الصوت غير متاح",
|
||||
"WebRTC microphone support unavailable": "دعم ميكروفون WebRTC غير متاح",
|
||||
@@ -136,7 +136,7 @@
|
||||
"Loaded: {scene} / {shot}": "تم التحميل: {scene} / {shot}",
|
||||
"Camera unavailable — exit cockpit first": "الكاميرا غير متاحة — اخرج من قمرة القيادة أولًا",
|
||||
"No shots to run": "لا توجد لقطات للتشغيل",
|
||||
"Running {index}/{total}: {scene} / {shot}": "جارٍ التشغيل {index}/{total}: {scene} / {shot}",
|
||||
"Running {index}/{total}: {scene} / {shot}": "جارٍ التشغيل {index} من {total}: {scene} / {shot}",
|
||||
"Scene run complete": "اكتمل تشغيل المشهد",
|
||||
"Error: {message}": "خطأ: {message}",
|
||||
"Could not exit {mode} — scene layers may be refused": "تعذّر الخروج من {mode} — قد تُرفض طبقات المشهد",
|
||||
@@ -210,7 +210,7 @@
|
||||
"Outside": "الخارج",
|
||||
"Detection opacity outside the keyhole": "عتامة الكشف خارج الفتحة",
|
||||
"World-overlay label and card opacity beyond the fade distance": "عتامة التسميات والبطاقات في طبقة العالم بعد مسافة التلاشي",
|
||||
"PARAMETERS": "المعاملات",
|
||||
"PARAMETERS": "المعلمات",
|
||||
"3D aircraft — flat icons zoomed out, 3D models up close": "طائرات ثلاثية الأبعاد — أيقونات مسطحة عند التصغير ونماذج ثلاثية الأبعاد عن قرب",
|
||||
"Models": "النماذج",
|
||||
"3D model coverage": "تغطية النماذج ثلاثية الأبعاد",
|
||||
@@ -235,11 +235,11 @@
|
||||
"Landmark: --": "المَعلم: --",
|
||||
"Search any location": "البحث عن أي موقع",
|
||||
"Search any location...": "ابحث عن أي موقع...",
|
||||
"LEVEL": "المستوى",
|
||||
"LEVEL": "أفقي",
|
||||
"Estimated destination direction": "اتجاه الوجهة التقديري",
|
||||
"DEST ---°": "الوجهة ---°",
|
||||
"OPTICAL PLANE · 01": "المستوى البصري · 01",
|
||||
"VISOR LOCK · ACTIVE": "قفل الحاجب · نشط",
|
||||
"VISOR LOCK · ACTIVE": "قفل واقي الخوذة · نشط",
|
||||
"FIRST PERSON": "منظور الشخص الأول",
|
||||
"LIVE TRACK · COURSE ALIGNED": "تتبع مباشر · المسار متوافق",
|
||||
"Cockpit vision style": "نمط رؤية القمرة",
|
||||
@@ -249,8 +249,8 @@
|
||||
"PREV": "السابق",
|
||||
"Previous cockpit vision style": "نمط رؤية القمرة السابق",
|
||||
"Previous vision style": "نمط الرؤية السابق",
|
||||
"Current cockpit vision style: NORMAL. Activate for next style.": "نمط رؤية القمرة الحالي: NORMAL. فعّل للانتقال إلى النمط التالي.",
|
||||
"Current style: NORMAL — click for next": "النمط الحالي: NORMAL — انقر للتالي",
|
||||
"Current cockpit vision style: NORMAL. Activate for next style.": "نمط رؤية القمرة الحالي: عادي. فعّل للانتقال إلى النمط التالي.",
|
||||
"Current style: NORMAL — click for next": "النمط الحالي: عادي — انقر للتالي",
|
||||
"Next cockpit vision style": "نمط رؤية القمرة التالي",
|
||||
"Next vision style": "نمط الرؤية التالي",
|
||||
"View switcher": "مبدّل العرض",
|
||||
@@ -308,7 +308,7 @@
|
||||
"Reclassify tracked contact as TR-3B": "إعادة تصنيف الهدف المتتبَّع إلى TR-3B",
|
||||
"Reclassify as TR-3B": "إعادة التصنيف إلى TR-3B",
|
||||
"CONTACTS CONTEXT OFF": "سياق الأهداف متوقف",
|
||||
"SELECT CONTACTS TO LOAD OBSERVED / MAPPED PROXIMITY": "اختر الأهداف لتحميل القرب المرصود / المرسوم",
|
||||
"SELECT CONTACTS TO LOAD OBSERVED / MAPPED PROXIMITY": "اختر الأهداف لتحميل العناصر القريبة المرصودة / المرسومة",
|
||||
"Available Space Missions": "المهام الفضائية المتاحة",
|
||||
"AVAILABLE MISSIONS": "المهام المتاحة",
|
||||
"SELECT A MISSION TO INSPECT": "اختر مهمة لفحصها",
|
||||
@@ -356,7 +356,7 @@
|
||||
"SUBJECT LEFT ITS FEED · READOUT HOLDING LAST KNOWN": "غادر الهدف مصدر بياناته · القراءة تحتفظ بآخر قيمة معروفة",
|
||||
"MILITARY FLIGHT": "رحلة عسكرية",
|
||||
"COMMERCIAL FLIGHT": "رحلة تجارية",
|
||||
"{aircraftClass} · CURRENT": "{aircraftClass} · الحالي",
|
||||
"{aircraftClass} · CURRENT": "{aircraftClass} · الحالية",
|
||||
"{aircraftClass} · {distance}": "{aircraftClass} · {distance}",
|
||||
"DISTANCE UNKNOWN": "المسافة غير معروفة",
|
||||
"{count} INPUTS UNKNOWN": {
|
||||
@@ -401,7 +401,7 @@
|
||||
"Expand briefing panel": "توسيع لوحة الإحاطة",
|
||||
"CYCLE ON": "التدوير مفعّل",
|
||||
"CYCLE OFF": "التدوير متوقف",
|
||||
"Stop automatic page cycling. Previous, Next, and the SIG/NEWS/LOCAL tabs stay available.": "إيقاف التدوير التلقائي للصفحات. تبقى أزرار السابق والتالي وتبويبات SIG/NEWS/LOCAL متاحة.",
|
||||
"Stop automatic page cycling. Previous, Next, and the SIG/NEWS/LOCAL tabs stay available.": "إيقاف التدوير التلقائي للصفحات. تبقى أزرار السابق والتالي وتبويبات «إشارات» و«أخبار» و«محلي» متاحة.",
|
||||
"REGIONAL NEWS": "أخبار إقليمية",
|
||||
"LOCAL INFO": "معلومات محلية",
|
||||
"LATEST LOCATION-MATCHED REPORTING": "أحدث التقارير المطابقة للموقع",
|
||||
@@ -416,7 +416,7 @@
|
||||
"TIME UNKNOWN": "الوقت غير معروف",
|
||||
"{count}M AGO": "قبل {count} د",
|
||||
"{count}H AGO": "قبل {count} س",
|
||||
"{count}D AGO": "قبل {count} يوم",
|
||||
"{count}D AGO": "قبل {count} ي",
|
||||
"DIR UNKNOWN": "الاتجاه غير معروف",
|
||||
"CLOUD {pct}%": "الغيوم {pct}%",
|
||||
"CLOUD UNKNOWN": "الغيوم غير معروفة",
|
||||
@@ -432,7 +432,7 @@
|
||||
"entity": "عنصر",
|
||||
"SHARED {subject}": "مشاركة: {subject}",
|
||||
"Shared {subject} follow expired": "انتهت متابعة المشاركة: {subject}",
|
||||
"Shared {subject} could not be restored — feed unavailable": "تعذّرت استعادة المشاركة — المصدر غير متاح: {subject}",
|
||||
"Shared {subject} could not be restored — feed unavailable": "تعذّرت استعادة المشاركة ({subject}) — المصدر غير متاح",
|
||||
"Shared {subject} is unavailable": "المشاركة غير متاحة: {subject}",
|
||||
"Context": "السياق",
|
||||
"Space Missions": "المهام الفضائية",
|
||||
@@ -469,7 +469,7 @@
|
||||
"Resume": "استئناف",
|
||||
"selected": "المحددة",
|
||||
"nearest": "الأقرب",
|
||||
"{action} {target} radio station": "{action} محطة الراديو {target}",
|
||||
"{action} {target} radio station": "{action}: محطة الراديو {target}",
|
||||
"RADIO STATE UNCERTAIN": "حالة الراديو غير مؤكدة",
|
||||
"SYNCING DIRECTORY": "جارٍ مزامنة الدليل",
|
||||
"SYNCING": "جارٍ المزامنة",
|
||||
@@ -519,7 +519,7 @@
|
||||
"Search location by name or coordinates": "البحث عن موقع بالاسم أو الإحداثيات",
|
||||
"Expand LOCATION": "توسيع الموقع",
|
||||
"UNAVAILABLE": "غير متاح",
|
||||
"Live Flights": "الرحلات المباشرة",
|
||||
"Live Flights": "تتبّع الرحلات المباشر",
|
||||
"Earthquakes (24h)": "الزلازل (24 ساعة)",
|
||||
"No selected data layers": "لا توجد طبقات بيانات محددة",
|
||||
"Cleared {count} data layers": {
|
||||
@@ -552,7 +552,7 @@
|
||||
"CALIBRATION": "المعايرة",
|
||||
"ADJUST": "ضبط",
|
||||
"Drag the camera in the world: rings rotate, arrows move, handles set range/FOV": "اسحب الكاميرا في العالم: تدور الحلقات وتحرّك الأسهم الموضع وتضبط المقابض المدى وزاوية الرؤية",
|
||||
"Camera pose — click a value to type": "وضع الكاميرا — انقر على قيمة لكتابتها",
|
||||
"Camera pose — click a value to type": "وضعية الكاميرا — انقر على قيمة لكتابتها",
|
||||
"Heading (compass °) — click to type": "الاتجاه (بوصلة °) — انقر للكتابة",
|
||||
"Pitch (° up/down) — click to type": "الميل (° أعلى/أسفل) — انقر للكتابة",
|
||||
"Horizontal FOV (°) — click to type": "زاوية الرؤية الأفقية (°) — انقر للكتابة",
|
||||
@@ -599,7 +599,7 @@
|
||||
"Street Traffic": "حركة مرور الشوارع",
|
||||
"Radio": "الراديو",
|
||||
"Bikeshare": "الدراجات المشتركة",
|
||||
"Live AIS Vessels": "سفن AIS المباشرة",
|
||||
"Live AIS Vessels": "تتبّع سفن AIS المباشر",
|
||||
"Mapped Installations": "المنشآت المرسومة",
|
||||
"Global Context": "السياق العالمي",
|
||||
"Datacenters": "مراكز البيانات",
|
||||
@@ -612,7 +612,7 @@
|
||||
"{count}h ago": "قبل {count} س",
|
||||
"never": "لم يُحدَّث",
|
||||
"loading...": "جارٍ التحميل...",
|
||||
"UNCERTAIN · {source} · lifecycle state requires reconciliation": "غير مؤكد · {source} · حالة دورة التشغيل تحتاج إلى مطابقة",
|
||||
"UNCERTAIN · {source} · lifecycle state requires reconciliation": "غير مؤكد · {source} · حالة التشغيل تحتاج إلى تصحيح",
|
||||
"{state} · {source}": "{state} · {source}",
|
||||
"{state} · {source} · {detail} · retry {seconds}s": "{state} · {source} · {detail} · إعادة المحاولة {seconds} ث",
|
||||
"{state} · {source} · {detail}": "{state} · {source} · {detail}",
|
||||
@@ -653,19 +653,19 @@
|
||||
"AIS: {name}": "AIS: {name}",
|
||||
"{type} SPD: {speed} HDG: {heading}": "{type} السرعة: {speed} HDG: {heading}",
|
||||
"awaiting first AIS position…": "بانتظار أول موضع AIS…",
|
||||
"CCTV ON": "CCTV يعمل",
|
||||
"CCTV ON": "CCTV مفعّل",
|
||||
"COVERAGE ON": "التغطية مفعّلة",
|
||||
"AUTO HOP ON": "التنقل التلقائي مفعّل",
|
||||
"PROJECTION OFF": "الإسقاط متوقف",
|
||||
"ADJUST ON": "الضبط مفعّل",
|
||||
"{count} CAMERAS STANDING BY · NO CAMERA SELECTED · CLICK A CAMERA TO ACTIVATE": "{count} كاميرا في وضع الاستعداد · لم تُحدَّد كاميرا · انقر على كاميرا لتفعيلها",
|
||||
"{count} CAMERAS STANDING BY · NO CAMERA SELECTED · CLICK A CAMERA TO ACTIVATE": "الكاميرات في وضع الاستعداد: {count} · لم تُحدَّد كاميرا · انقر على كاميرا لتفعيلها",
|
||||
"No cameras available in catalog.": "لا توجد كاميرات متاحة في الفهرس.",
|
||||
"{city} CCTV": "CCTV {city}",
|
||||
"HDG {value}°": "HDG {value}°",
|
||||
"HDG {value}° (ESTIMATED)": "HDG {value}° (تقديري)",
|
||||
"FOV {value}°": "FOV {value}°",
|
||||
"COVERAGE {value}km²": "التغطية {value} كم²",
|
||||
"OVERLAP {count} cams": "تداخل {count} كاميرا",
|
||||
"OVERLAP {count} cams": "الكاميرات المتداخلة: {count}",
|
||||
"ISOLATED VIEW": "منظور معزول",
|
||||
"PROJ MONITOR": "إسقاط على شاشة",
|
||||
"PROJ OFF": "إسقاط متوقف",
|
||||
@@ -681,7 +681,7 @@
|
||||
"Traffic / Transit": "المرور / النقل",
|
||||
"Music": "موسيقى",
|
||||
"Other": "أخرى",
|
||||
"Alternative": "بديل",
|
||||
"Alternative": "بديلة",
|
||||
"Ambient": "أمبيانت",
|
||||
"Blues": "بلوز",
|
||||
"Classical": "كلاسيكية",
|
||||
@@ -710,10 +710,10 @@
|
||||
"WEATHER": "طقس",
|
||||
"NO STATION AVAILABLE": "لا توجد محطة متاحة",
|
||||
"DISABLE": "إيقاف",
|
||||
"RECONCILE": "مطابقة",
|
||||
"RECONCILE": "تصحيح الحالة",
|
||||
"Enable Radio": "تفعيل الراديو",
|
||||
"Disable Radio": "إيقاف الراديو",
|
||||
"Reconcile Radio — lifecycle uncertain": "مطابقة الراديو — حالة دورة التشغيل غير مؤكدة",
|
||||
"Reconcile Radio — lifecycle uncertain": "تصحيح حالة الراديو — حالة التشغيل غير مؤكدة",
|
||||
"Loading station directory…": "جارٍ تحميل دليل المحطات…",
|
||||
"Directory metadata only": "بيانات وصفية للدليل فقط",
|
||||
"Choose a globe marker or use next.": "اختر علامة على الكرة الأرضية أو استخدم التالي.",
|
||||
@@ -735,7 +735,7 @@
|
||||
"Station unavailable after directory refresh — choose another channel": "المحطة غير متاحة بعد تحديث الدليل — اختر قناة أخرى",
|
||||
"Radio is enabling…": "جارٍ تفعيل الراديو…",
|
||||
"Radio is disabling…": "جارٍ إيقاف الراديو…",
|
||||
"Radio lifecycle is uncertain — use Enable or Disable to reconcile": "حالة دورة تشغيل الراديو غير مؤكدة — استخدم تفعيل أو إيقاف للمطابقة",
|
||||
"Radio lifecycle is uncertain — use Enable or Disable to reconcile": "حالة تشغيل الراديو غير مؤكدة — استخدم «تفعيل» أو «إيقاف» لتصحيحها",
|
||||
"LAUNCH SITE": "موقع الإطلاق",
|
||||
"LAUNCH SITE · {site}": "موقع الإطلاق · {site}",
|
||||
"SELECTED SPACE MISSION": "المهمة الفضائية المحددة",
|
||||
@@ -856,7 +856,7 @@
|
||||
"Overpass timed out": "انتهت مهلة Overpass",
|
||||
"Overpass could not complete the query": "تعذّر على Overpass إكمال الاستعلام",
|
||||
"Overpass temporarily unavailable": "Overpass غير متاح مؤقتًا",
|
||||
"Retrying mapped sites…": "إعادة محاولة المواقع المرسومة…",
|
||||
"Retrying mapped sites…": "جارٍ إعادة محاولة تحميل المواقع المرسومة…",
|
||||
"Fetching mapped sites…": "جارٍ جلب المواقع المرسومة…",
|
||||
"{reason} — retrying in {seconds}s": "{reason} — إعادة المحاولة خلال {seconds} ث",
|
||||
"{reason} — retry pending": "{reason} — إعادة المحاولة معلّقة",
|
||||
@@ -866,7 +866,7 @@
|
||||
"CONTEXT READY": "السياق جاهز",
|
||||
"GLOBAL CONTEXT OFF": "السياق العالمي متوقف",
|
||||
"SELECT A FLIGHT, VESSEL, OR MAPPED INSTALLATION": "اختر رحلة أو سفينة أو منشأة مرسومة",
|
||||
"ENABLE TO LOAD OBSERVED / MAPPED PROXIMITY": "فعّل لتحميل القرب المرصود / المرسوم",
|
||||
"ENABLE TO LOAD OBSERVED / MAPPED PROXIMITY": "فعّل لتحميل العناصر القريبة المرصودة / المرسومة",
|
||||
"Global Context navigation": "التنقل في السياق العالمي",
|
||||
"PREVIOUS": "السابق",
|
||||
"Open-source mapped/observed context. Missing broadcasts, unloaded map areas, or unmapped sites are not evidence of absence.": "سياق مرسوم/مرصود من مصادر مفتوحة. غياب البث أو عدم تحميل مناطق الخريطة أو وجود مواقع غير مرسومة لا يدل على عدم الوجود.",
|
||||
@@ -928,7 +928,7 @@
|
||||
"Data provided by:": "البيانات مقدَّمة من:",
|
||||
"Light aircraft": "طائرة خفيفة",
|
||||
"Glider": "طائرة شراعية",
|
||||
"Turboprop": "طائرة مروحية توربينية",
|
||||
"Turboprop": "طائرة بمحرك توربيني مروحي",
|
||||
"Airliner": "طائرة ركاب",
|
||||
"Widebody": "طائرة عريضة البدن",
|
||||
"Four-engine jet": "نفاثة رباعية المحركات",
|
||||
@@ -1110,7 +1110,7 @@
|
||||
"That file creates more than {limit} map objects (the limit).": "ينشئ هذا الملف عناصر على الخريطة تتجاوز الحد المسموح ({limit}).",
|
||||
"That file has more than {limit} style definitions and image links after cleanup (the limit).": "بعد التنظيف، يحتوي هذا الملف على تعريفات أنماط وروابط صور تتجاوز الحد المسموح ({limit}).",
|
||||
"That file has more than {limit} features (the limit).": "يحتوي هذا الملف على عناصر تتجاوز الحد المسموح ({limit}).",
|
||||
"That file has more than {limit} points (the limit) across {features} features.": "يحتوي هذا الملف على نقاط تتجاوز الحد المسموح ({limit}) موزّعة على {features} عنصر.",
|
||||
"That file has more than {limit} points (the limit) across {features} features.": "يحتوي هذا الملف على نقاط تتجاوز الحد المسموح ({limit})؛ عدد العناصر: {features}.",
|
||||
"That file has more than {limit} points (the limit).": "يحتوي هذا الملف على نقاط تتجاوز الحد المسموح ({limit}).",
|
||||
"That KMZ unpacks to more than the import limit.": "حجم ملف KMZ هذا بعد فك ضغطه يتجاوز حد الاستيراد.",
|
||||
"That KMZ is damaged and could not be unpacked.": "ملف KMZ هذا تالف وتعذّر فك ضغطه.",
|
||||
@@ -1200,7 +1200,7 @@
|
||||
"{name} is empty": "{name}: القيمة فارغة",
|
||||
"{name} is longer than any real key ({limit} max)": "{name}: القيمة أطول من أي مفتاح حقيقي (الحد الأقصى {limit})",
|
||||
"{name} may only contain printable characters with no spaces": "{name}: يُسمح فقط بالأحرف القابلة للطباعة دون مسافات",
|
||||
"{name} contains a character that is not valid in a key (#, quotes, $, \\, or backtick)": "{name}: يحتوي على حرف غير صالح في المفتاح (#، أو علامات الاقتباس، أو $، أو \\، أو العلامة المائلة الخلفية `)",
|
||||
"{name} contains a character that is not valid in a key (#, quotes, $, \\, or backtick)": "{name}: يحتوي على حرف غير صالح في المفتاح (#، أو علامات الاقتباس، أو $، أو \\، أو علامة الاقتباس الخلفية `)",
|
||||
"{name} is configured outside Provider Settings and can only be changed where it was set": "{name}: مُعَدّ خارج «إعدادات المزوّدين» ولا يمكن تغييره إلا حيث ضُبط",
|
||||
"The key was not saved: could not restrict the credential file to your account; nothing was saved": "لم يُحفظ المفتاح: تعذّر قصر الوصول إلى ملف بيانات الاعتماد على حسابك؛ لم يُحفظ شيء",
|
||||
"The key was not saved: the existing configuration could not be read, so nothing was changed": "لم يُحفظ المفتاح: تعذّرت قراءة الإعدادات الحالية، لذا لم يتغيّر شيء",
|
||||
@@ -1290,7 +1290,7 @@
|
||||
"many": "القريبة: {count}",
|
||||
"other": "القريبة: {count}"
|
||||
},
|
||||
"retrying mapped ALPR cameras": "إعادة محاولة تحميل كاميرات ALPR المرسومة",
|
||||
"retrying mapped ALPR cameras": "جارٍ إعادة محاولة تحميل كاميرات ALPR المرسومة",
|
||||
"loading mapped ALPR cameras": "جارٍ تحميل كاميرات ALPR المرسومة",
|
||||
"No ALPR data for this area — US and Canada only": "لا توجد بيانات ALPR لهذه المنطقة — الولايات المتحدة وكندا فقط",
|
||||
"Zoom in to load mapped cameras": "قرّب الخريطة لتحميل الكاميرات المرسومة",
|
||||
@@ -1336,7 +1336,7 @@
|
||||
"many": "{count} عاصفة نشطة",
|
||||
"other": "{count} عاصفة نشطة"
|
||||
},
|
||||
"Position as of {time}": "الموضع حتى {time}",
|
||||
"Position as of {time}": "الموضع عند {time}",
|
||||
"Maximum sustained wind: {wind} · Pressure: {pressure}": "أقصى رياح مستمرة: {wind} · الضغط: {pressure}",
|
||||
"{name} · {classification} · Advisory {number} · issued {time}": "{name} · {classification} · النشرة رقم {number} · صدرت {time}",
|
||||
"No active NHC/CPHC systems": "لا توجد أنظمة NHC/CPHC نشطة",
|
||||
@@ -1385,7 +1385,7 @@
|
||||
"lane direction|slightly right": "اليمنى",
|
||||
"lane direction|sharply left": "الأقصى يسارًا",
|
||||
"lane direction|sharply right": "الأقصى يمينًا",
|
||||
"lane direction|straight": "الوسطى",
|
||||
"lane direction|straight": "المستقيمة",
|
||||
"ordinal|{number}st": "رقم {number}",
|
||||
"ordinal|{number}nd": "رقم {number}",
|
||||
"ordinal|{number}rd": "رقم {number}",
|
||||
@@ -1453,7 +1453,7 @@
|
||||
"Then click the globe to place the destination": "ثم انقر على الكرة الأرضية لوضع الوجهة",
|
||||
"Swap A and B": "تبديل A وB",
|
||||
"FLY ···": "تحليق ···",
|
||||
"FLYING": "يحلّق",
|
||||
"FLYING": "جارٍ التحليق",
|
||||
"FLY": "تحليق",
|
||||
"Fly the camera along the route": "تحليق الكاميرا على امتداد المسار",
|
||||
"Place A and B first": "ضع A وB أولًا",
|
||||
@@ -1462,12 +1462,12 @@
|
||||
"press Escape twice to leave Draw": "اضغط Esc مرتين للخروج من «الرسم»",
|
||||
"{name} is active — turn {name} off, then set {endpoint}": "«{name}» نشط — أوقف «{name}»، ثم عيّن {endpoint}",
|
||||
"{name} is active — {leave}, then set {endpoint}": "«{name}» نشط — {leave}، ثم عيّن {endpoint}",
|
||||
"Only the first {count} turns of this route are shown": "لا يُعرض من هذا المسار سوى أول {count} انعطافات",
|
||||
"Only the first {count} turns of this route are shown": "لا يُعرض من هذا المسار سوى أول {count} من الانعطافات",
|
||||
"Turn-by-turn directions": "إرشادات خطوة بخطوة",
|
||||
"Another map tool is using clicks — close it, then SET A again": "أداة خريطة أخرى تستخدم النقرات — أغلقها، ثم اضغط «تعيين A» مجددًا",
|
||||
"Routing…": "جارٍ حساب المسار…",
|
||||
"No route found": "لم يُعثر على مسار",
|
||||
"{distance} · {duration} · {mode} · first {count} turns": "{distance} · {duration} · {mode} · أول {count} انعطافات",
|
||||
"{distance} · {duration} · {mode} · first {count} turns": "{distance} · {duration} · {mode} · أول {count} من الانعطافات",
|
||||
"{distance} · {duration} · {mode}": "{distance} · {duration} · {mode}",
|
||||
"Click the globe to place {endpoint}": "انقر على الكرة الأرضية لوضع {endpoint}",
|
||||
"SET B, then click the globe": "اضغط «تعيين B»، ثم انقر على الكرة الأرضية",
|
||||
@@ -1480,7 +1480,7 @@
|
||||
"Routing timed out": "انتهت مهلة حساب المسار",
|
||||
"Routing unavailable — no response from the routing service": "التوجيه غير متاح — لا استجابة من خدمة التوجيه",
|
||||
"Earthquake source unavailable": "مصدر بيانات الزلازل غير متاح",
|
||||
"live feed unavailable": "البث المباشر للبيانات غير متاح",
|
||||
"live feed unavailable": "المصدر المباشر غير متاح",
|
||||
"{value}°N": "{value}° ش",
|
||||
"{value}°S": "{value}° ج",
|
||||
"{value}°E": "{value}° ق",
|
||||
@@ -1490,7 +1490,7 @@
|
||||
"fire confidence|low": "منخفضة",
|
||||
"age|<1h": "<1 س",
|
||||
"age|{count}h": "{count} س",
|
||||
"age|{count}d": "{count} يوم",
|
||||
"age|{count}d": "{count} ي",
|
||||
"Source snapshot time unavailable": "وقت لقطة المصدر غير متاح",
|
||||
"Live data unavailable": "البيانات المباشرة غير متاحة",
|
||||
"Intel Picture": "الصورة الاستخباراتية",
|
||||
@@ -1534,7 +1534,7 @@
|
||||
"Retrying automatically at {time} UTC": "إعادة المحاولة تلقائيًا عند {time} UTC",
|
||||
"Retrying automatically after provider cooldown": "إعادة المحاولة تلقائيًا بعد انتهاء مهلة المزوّد",
|
||||
"Updates automatically after pan or zoom and once a minute. The public feed covers a 250-nautical-mile circle around the view centre; zooming out does not make it a worldwide snapshot. Blank cells are outside sampled coverage or have insufficient observations.": "يتحدّث تلقائيًا بعد التحريك أو التكبير/التصغير ومرة كل دقيقة. يغطي المصدر العام دائرة نصف قطرها 250 ميلًا بحريًا حول مركز العرض؛ والتصغير لا يجعله لقطة عالمية. الخلايا الفارغة خارج نطاق العيّنة أو أرصادها غير كافية.",
|
||||
"Low-accuracy score = 100 × max(0, bad − 1) / total: one aircraft is discounted, so this is not the raw share. Cells need {aircraft} aircraft over {minutes} minutes. Navigation-integrity anomalies are not a detection of jamming or spoofing.": "مؤشر ضعف الدقة = 100 × max(0, الضعيفة − 1) / الإجمالي: تُستبعد طائرة واحدة، لذا فهو ليس النسبة الخام. تحتاج كل خلية إلى {aircraft} طائرة خلال {minutes} دقيقة. شذوذ سلامة الملاحة ليس كشفًا للتشويش أو الانتحال.",
|
||||
"Low-accuracy score = 100 × max(0, bad − 1) / total: one aircraft is discounted, so this is not the raw share. Cells need {aircraft} aircraft over {minutes} minutes. Navigation-integrity anomalies are not a detection of jamming or spoofing.": "مؤشر ضعف الدقة = 100 × max(0, الضعيفة − 1) / الإجمالي: تُستبعد طائرة واحدة، لذا فهو ليس النسبة الخام. تحتاج كل خلية إلى {aircraft} طائرات خلال {minutes} دقيقة. شذوذ سلامة الملاحة ليس كشفًا للتشويش أو الانتحال.",
|
||||
"GNSS snapshot needs a view anchor": "لقطة GNSS تحتاج إلى نقطة ارتكاز للعرض",
|
||||
"GNSS feed rate limited (HTTP 429)": "مصدر GNSS تجاوز حد الطلبات (HTTP 429)",
|
||||
"Malformed GNSS integrity snapshot": "لقطة سلامة GNSS مشوّهة",
|
||||
@@ -1579,7 +1579,7 @@
|
||||
"Your RTL-SDR receiver and decoder feed": "جهاز استقبال RTL-SDR لديك ومصدر المفكّك",
|
||||
"Your decoder feed": "مصدر المفكّك لديك",
|
||||
"Your RTL-SDR receiver": "جهاز استقبال RTL-SDR لديك",
|
||||
"NO CALLSIGN": "بلا نداء",
|
||||
"NO CALLSIGN": "بلا رمز نداء",
|
||||
"ALT {altitude} FT · GS {speed} KT · TRK {track}": "الارتفاع {altitude} قدم · GS {speed} عقدة · TRK {track}",
|
||||
"V/S {rate}": "V/S {rate}",
|
||||
"V/S {rate} FPM": "V/S {rate} قدم/د",
|
||||
@@ -1616,20 +1616,20 @@
|
||||
"Type {level} Incident": "حادث من النوع {level}",
|
||||
"age|{count}m": "{count} د",
|
||||
"{acres} ac": {
|
||||
"zero": "{acres} فدان",
|
||||
"one": "{acres} فدان",
|
||||
"two": "{acres} فدان",
|
||||
"few": "{acres} فدادين",
|
||||
"many": "{acres} فدانًا",
|
||||
"other": "{acres} فدان"
|
||||
"zero": "{acres} آكر",
|
||||
"one": "{acres} آكر",
|
||||
"two": "{acres} آكر",
|
||||
"few": "{acres} آكر",
|
||||
"many": "{acres} آكر",
|
||||
"other": "{acres} آكر"
|
||||
},
|
||||
"{percent}% contained": "تمت السيطرة على {percent}%",
|
||||
"containment unknown": "نسبة السيطرة غير معروفة",
|
||||
"{cause} cause": "السبب: {cause}",
|
||||
"{personnel} personnel": {
|
||||
"zero": "{personnel} فرد",
|
||||
"one": "{personnel} فرد",
|
||||
"two": "{personnel} فرد",
|
||||
"one": "فرد واحد ({personnel})",
|
||||
"two": "فردان ({personnel})",
|
||||
"few": "{personnel} أفراد",
|
||||
"many": "{personnel} فردًا",
|
||||
"other": "{personnel} فرد"
|
||||
@@ -1813,7 +1813,7 @@
|
||||
"{heading}DEG": "{heading}°",
|
||||
"AIS live load failed": "فشل تحميل AIS المباشر",
|
||||
"AISSTREAM_API_KEY not set": "AISSTREAM_API_KEY غير مضبوط",
|
||||
"live feed unsupported": "البث المباشر للبيانات غير مدعوم",
|
||||
"live feed unsupported": "المصدر المباشر غير مدعوم",
|
||||
"connecting to feed…": "جارٍ الاتصال بالمصدر…",
|
||||
"feed disconnected": "المصدر منقطع",
|
||||
"feed down": "المصدر متعطل",
|
||||
@@ -1850,7 +1850,7 @@
|
||||
"GOES INFRARED · ~5 min": "الأشعة تحت الحمراء GOES · ~5 د",
|
||||
"{state}: {time}": "{state}: {time}",
|
||||
"Latest observation": "أحدث رصد",
|
||||
"frame {index}/{total}": "الإطار {index}/{total}",
|
||||
"frame {index}/{total}": "الإطار {index} من {total}",
|
||||
"loading": "جارٍ التحميل",
|
||||
"Observation: unavailable": "الرصد: غير متاح",
|
||||
"Contiguous US · gaps ≠ no rain": "الولايات المتحدة المتجاورة · الفجوات ≠ انعدام المطر",
|
||||
@@ -1876,7 +1876,7 @@
|
||||
"Hourly global mosaic; usually 2–3 hours delayed": "فسيفساء عالمية كل ساعة؛ بتأخير 2–3 ساعات عادةً",
|
||||
"GOES regional clouds; approximately 5-minute updates": "غيوم إقليمية من GOES؛ تحديث كل 5 دقائق تقريبًا",
|
||||
"Clouds only": "الغيوم فقط",
|
||||
"Apply the full cloud brightness key; 100% filter strength": "تطبيق مفتاح سطوع الغيوم كاملًا؛ قوة المرشح 100%",
|
||||
"Apply the full cloud brightness key; 100% filter strength": "تطبيق مرشح سطوع الغيوم بكامل شدته؛ قوة المرشح 100%",
|
||||
"Full": "كامل",
|
||||
"Show the complete infrared image; 0% filter strength": "عرض صورة الأشعة تحت الحمراء كاملة؛ قوة المرشح 0%",
|
||||
"Soft": "ناعم",
|
||||
@@ -1936,7 +1936,7 @@
|
||||
"Read wind at map center": "قراءة الرياح في مركز الخريطة",
|
||||
"Read the forecast at the center of the map without changing selection": "قراءة التوقع في مركز الخريطة دون تغيير التحديد",
|
||||
"{model} forecast · {field} ({unit})": "توقع {model} · {field} ({unit})",
|
||||
"Valid: {time}": "صالح حتى: {time}",
|
||||
"Valid: {time}": "وقت التوقع: {time}",
|
||||
"preparing": "جارٍ التجهيز",
|
||||
"Issued: {time}": "صدر: {time}",
|
||||
"Selected field unavailable · wind remains visible": "الحقل المحدد غير متاح · تبقى الرياح ظاهرة",
|
||||
@@ -1966,11 +1966,11 @@
|
||||
"No surface reading": "لا قراءة سطحية",
|
||||
"Aim the map center at Earth": "وجّه مركز الخريطة نحو الأرض",
|
||||
"A location reading needs a loaded forecast and the Earth at the center of the view.": "تتطلب قراءة الموقع توقعًا محمّلًا ووجود الأرض في مركز العرض.",
|
||||
"Interpolated model forecast on an approximately 1° grid. Broad weather patterns, not a street-level measurement.": "توقع نموذجي مستكمَل على شبكة بدقة 1° تقريبًا. أنماط طقس عامة، لا قياس على مستوى الشارع.",
|
||||
"Interpolated model forecast on an approximately 1° grid. Broad weather patterns, not a street-level measurement.": "توقع نموذج عددي محسوب بالاستيفاء على شبكة بدقة 1° تقريبًا. أنماط طقس عامة، لا قياس على مستوى الشارع.",
|
||||
" · calm": " · سكون",
|
||||
" from {direction}": " من {direction}",
|
||||
"WIND AT {coordinates}": "الرياح عند {coordinates}",
|
||||
"{model} · valid {time}": "{model} · صالح حتى {time}",
|
||||
"{model} · valid {time}": "{model} · التوقع عند {time}",
|
||||
"Wind speed field unavailable": "حقل سرعة الرياح غير متاح",
|
||||
"Globe imagery unavailable": "صور الكرة الأرضية غير متاحة",
|
||||
"Globe field image unavailable": "صورة الحقل على الكرة الأرضية غير متاحة",
|
||||
@@ -2096,12 +2096,12 @@
|
||||
"Review scene import": "مراجعة استيراد المشهد",
|
||||
"Nothing is loaded or changed until you apply this file.": "لا يُحمَّل أو يتغيّر شيء حتى تطبّق هذا الملف.",
|
||||
"Ready to import": "جاهز للاستيراد",
|
||||
"Apply replaces the current project. Export your current project first if you want to keep both.": "التطبيق يستبدل المشروع الحالي. صدّر مشروعك الحالي أولًا إن أردت الاحتفاظ بكليهما.",
|
||||
"Apply replaces the current project. Export your current project first if you want to keep both.": "يؤدي تطبيق الاستيراد إلى استبدال المشروع الحالي. صدّر مشروعك الحالي أولًا إن أردت الاحتفاظ بكليهما.",
|
||||
"Apply import": "تطبيق الاستيراد",
|
||||
"Import was not applied. The current project may have changed.": "لم يُطبَّق الاستيراد. ربما تغيّر المشروع الحالي.",
|
||||
"Select a scene and shot first": "اختر مشهدًا ولقطة أولًا",
|
||||
"Edit scene details": "تعديل تفاصيل المشهد",
|
||||
"Camera positions use degrees and meters above the ellipsoid. Drafts are validated before they replace your saved scene.": "تُحدَّد مواضع الكاميرا بالدرجات وبالأمتار فوق الإهليلج. يُتحقّق من المسودات قبل أن تحل محل مشهدك المحفوظ.",
|
||||
"Camera positions use degrees and meters above the ellipsoid. Drafts are validated before they replace your saved scene.": "تُحدَّد مواضع الكاميرا بالدرجات وبالأمتار فوق المجسّم الإهليلجي. يُتحقّق من المسودات قبل أن تحل محل مشهدك المحفوظ.",
|
||||
"Anchors and data packs": "نقاط الارتكاز وحزم البيانات",
|
||||
"Shot camera, timing, packs and actions": "كاميرا اللقطة والتوقيت والحزم والإجراءات",
|
||||
"New anchor ID": "معرّف نقطة الارتكاز الجديدة",
|
||||
@@ -2233,7 +2233,7 @@
|
||||
"Show {name}": "إظهار {name}",
|
||||
"Fly to {name}": "الانتقال إلى {name}",
|
||||
"Remove {name}": "إزالة {name}",
|
||||
"At most {count} files at once — remove one first.": "الحد الأقصى {count} ملفات في آن واحد — أزل ملفًا أولًا.",
|
||||
"At most {count} files at once — remove one first.": "الحد الأقصى {count} ملفًا في آن واحد — أزل ملفًا أولًا.",
|
||||
"Reading {name}…": "جارٍ قراءة {name}…",
|
||||
"That file could not be read.": "تعذّرت قراءة هذا الملف.",
|
||||
"Added {count} files.": {
|
||||
@@ -2269,7 +2269,7 @@
|
||||
"Lightning density": "كثافة البرق",
|
||||
"Cyclone advisories": "نشرات الأعاصير",
|
||||
"ALPR Cameras": "كاميرات ALPR",
|
||||
"Live Vessels": "السفن المباشرة",
|
||||
"Live Vessels": "تتبّع السفن المباشر",
|
||||
"Bike Share": "الدراجات المشتركة",
|
||||
"Cameras": "الكاميرات",
|
||||
"Street Level": "مستوى الشارع",
|
||||
@@ -2364,7 +2364,7 @@
|
||||
"Washington DC": "واشنطن العاصمة",
|
||||
"Tallinn": "تالين",
|
||||
"Los Angeles": "لوس أنجلوس",
|
||||
"CH {index} / {total}": "القناة {index} / {total}",
|
||||
"CH {index} / {total}": "القناة {index} من {total}",
|
||||
"Station unavailable after directory refresh": "المحطة غير متاحة بعد تحديث الدليل",
|
||||
"Go to expanded Radio section": "الانتقال إلى قسم الراديو الموسَّع",
|
||||
"Expand Radio section in Context": "توسيع قسم الراديو في «السياق»",
|
||||
@@ -2514,7 +2514,7 @@
|
||||
"This share link can't be opened": "لا يمكن فتح رابط المشاركة هذا",
|
||||
"Shared vessel is unavailable": "السفينة المشتركة غير متاحة",
|
||||
"subject": "الهدف",
|
||||
"RETRYING ALPR CAMERAS": "إعادة محاولة كاميرات ALPR",
|
||||
"RETRYING ALPR CAMERAS": "جارٍ إعادة محاولة تحميل كاميرات ALPR",
|
||||
"FETCHING ALPR CAMERAS": "جارٍ جلب كاميرات ALPR",
|
||||
"OVERPASS TEMPORARILY UNAVAILABLE": "OVERPASS غير متاح مؤقتًا",
|
||||
"OVERPASS RATE-LIMITED": "OVERPASS قيّد معدل الطلبات",
|
||||
@@ -2681,13 +2681,13 @@
|
||||
"{time} · {minutes} min ago": "{time} · قبل {minutes} د",
|
||||
"Active weather": "الطقس النشط",
|
||||
"LATEST · newest per product": "الأحدث · آخر إطار لكل منتج",
|
||||
"Forecast · valid {valid} · issued {issued}": "توقع · صالح حتى {valid} · صدر {issued}",
|
||||
"Forecast · valid {valid} · issued {issued}": "توقع عند {valid} · صدر {issued}",
|
||||
"{detail} · Does not follow history": "{detail} · لا يتبع السجل",
|
||||
"Forecast · valid {valid}": "توقع · صالح حتى {valid}",
|
||||
"Forecast · valid {valid}": "توقع عند {valid}",
|
||||
"voice|OFF": "متوقف",
|
||||
"voice|CONNECTING": "جارٍ الاتصال",
|
||||
"voice|LISTENING": "يستمع",
|
||||
"voice|EXECUTING": "ينفّذ",
|
||||
"voice|LISTENING": "جارٍ الاستماع",
|
||||
"voice|EXECUTING": "جارٍ التنفيذ",
|
||||
"voice|ERROR": "خطأ",
|
||||
"Voice provider": "مزوّد الصوت",
|
||||
"Changing provider reconnects voice if it is on.": "تغيير المزوّد يعيد اتصال الصوت إن كان مفعّلًا.",
|
||||
@@ -2956,7 +2956,7 @@
|
||||
"The flights layer is not enabled": "طبقة الرحلات غير مفعّلة",
|
||||
"The military layer is not enabled": "طبقة الرحلات العسكرية غير مفعّلة",
|
||||
"The satellites layer is not enabled": "طبقة الأقمار الصناعية غير مفعّلة",
|
||||
"The ais-live-vessels layer is not enabled": "طبقة سفن AIS المباشرة غير مفعّلة",
|
||||
"The ais-live-vessels layer is not enabled": "طبقة تتبّع سفن AIS المباشر غير مفعّلة",
|
||||
"Could not enable the requested layer": "تعذّر تفعيل الطبقة المطلوبة",
|
||||
"Could not disable the requested layer": "تعذّر إيقاف الطبقة المطلوبة",
|
||||
"Radio layer unavailable": "طبقة الراديو غير متاحة",
|
||||
|
||||
@@ -16,6 +16,7 @@ import {
|
||||
} from './labels.js';
|
||||
import {
|
||||
formatUtcDayMonth,
|
||||
isolate,
|
||||
t,
|
||||
tn,
|
||||
msg,
|
||||
@@ -25,9 +26,11 @@ import { formatQuantity } from '../../ui/unitLabels.js';
|
||||
|
||||
// "10-12 15:00 UTC" never breaks across lines: no-break spaces, and a word
|
||||
// joiner after the hyphen (JetBrains Mono has no U+2011 non-breaking hyphen).
|
||||
// The clock is isolated (a no-op outside RTL): a no-break space between two
|
||||
// numbers joins them into one run, which in Arabic split the date around it.
|
||||
const utc = (value) =>
|
||||
value
|
||||
? `${formatUtcDayMonth(value).replace('-', '-\u2060')}\u00a0${value.slice(11, 16)}\u00a0UTC`
|
||||
? `${formatUtcDayMonth(value).replace('-', '-\u2060')}\u00a0${isolate(value.slice(11, 16))}\u00a0UTC`
|
||||
: t('Unavailable');
|
||||
const COVERAGE = msg(
|
||||
'Atlantic and eastern/central North Pacific; not worldwide cyclone coverage.',
|
||||
|
||||
@@ -365,8 +365,9 @@ export function createStreetLevelLayer({
|
||||
/** The hint a ground-click provider shows, or the provider's own. */
|
||||
function hintFor(entry, stats) {
|
||||
if (entry.def.groundClick && stats.hint && !state.groundClickReady)
|
||||
// No-break spaces keep a provider name (Google Street View) on one line.
|
||||
return t('zoom in to a street to open {name}', {
|
||||
name: translateKnown(entry.def.name),
|
||||
name: translateKnown(entry.def.name).replace(/ /g, '\u00a0'),
|
||||
});
|
||||
return stats.hint || '';
|
||||
}
|
||||
|
||||
@@ -7,6 +7,9 @@
|
||||
*/
|
||||
|
||||
/** Shared visual tokens used by every world-overlay source. */
|
||||
// Map-card fonts: JetBrains Mono has no Arabic glyphs and the generic
|
||||
// monospace fallback is a fixed-pitch Arabic face that stretches every word;
|
||||
// Arabic text falls through to Noto Sans Arabic (loaded with the page fonts).
|
||||
export const WORLD_OVERLAY_STYLE = Object.freeze({
|
||||
background: 'rgba(4, 12, 16, 0.82)',
|
||||
selectedBackground: 'rgba(5, 18, 24, 0.94)',
|
||||
@@ -16,13 +19,13 @@ export const WORLD_OVERLAY_STYLE = Object.freeze({
|
||||
detail: 'rgba(147, 161, 173, 0.92)',
|
||||
leader: 'rgba(147, 213, 228, 0.58)',
|
||||
accent: '#6be8ff',
|
||||
fontLabel: '500 10px "JetBrains Mono", monospace',
|
||||
fontTrack: '600 10px "JetBrains Mono", monospace',
|
||||
fontTitle: '600 12px "JetBrains Mono", monospace',
|
||||
fontDetail: '500 10.5px "JetBrains Mono", monospace',
|
||||
fontSelected: '600 13px "JetBrains Mono", monospace',
|
||||
fontTrackedTitle: '600 13px "JetBrains Mono", monospace',
|
||||
fontTrackedDetail: '500 11px "JetBrains Mono", monospace',
|
||||
fontLabel: '500 10px "JetBrains Mono", "Noto Sans Arabic", monospace',
|
||||
fontTrack: '600 10px "JetBrains Mono", "Noto Sans Arabic", monospace',
|
||||
fontTitle: '600 12px "JetBrains Mono", "Noto Sans Arabic", monospace',
|
||||
fontDetail: '500 10.5px "JetBrains Mono", "Noto Sans Arabic", monospace',
|
||||
fontSelected: '600 13px "JetBrains Mono", "Noto Sans Arabic", monospace',
|
||||
fontTrackedTitle: '600 13px "JetBrains Mono", "Noto Sans Arabic", monospace',
|
||||
fontTrackedDetail: '500 11px "JetBrains Mono", "Noto Sans Arabic", monospace',
|
||||
radius: 4,
|
||||
anchorDotRadius: 3.2,
|
||||
anchorDotStrokeWidth: 1,
|
||||
|
||||
+19
-6
@@ -1069,17 +1069,30 @@ export function displaySceneTitle(scene) {
|
||||
export function displayShotTitle(title, shot = undefined, scene = undefined) {
|
||||
const text = typeof title === 'string' ? title : '';
|
||||
const fromRecipe = shot
|
||||
? Boolean(shot.sourcePackId) || recipeDeclaresShotTitle(scene?.id, text)
|
||||
? Boolean(shot.sourcePackId) || recipeDeclaresShotTitle(scene, text)
|
||||
: true;
|
||||
if (fromRecipe && BUILTIN_SHOT_TITLES.has(text)) return t(text);
|
||||
const numbered = /^Shot ([1-9]\d*)$/.exec(text);
|
||||
return numbered ? t('Shot {n}', { n: numbered[1] }) : text;
|
||||
}
|
||||
|
||||
/** Whether the built-in recipe behind a scene declares a shot with this title. */
|
||||
function recipeDeclaresShotTitle(sceneId, title) {
|
||||
const recipe = sceneId ? getSceneRecipeById(sceneId) : null;
|
||||
return Boolean(
|
||||
recipe?.cameraPath?.some((keyframe) => keyframe?.title === title),
|
||||
/**
|
||||
* Whether the built-in recipe behind a scene declares a shot with this title:
|
||||
* its own keyframes, or the bootstrap shots of a shot pack applied to the
|
||||
* scene (those shots carry no sourcePackId; the scene's pack marker vouches
|
||||
* for them).
|
||||
*/
|
||||
function recipeDeclaresShotTitle(scene, title) {
|
||||
const declares = (cameraPath) =>
|
||||
Boolean(cameraPath?.some((keyframe) => keyframe?.title === title));
|
||||
const recipe = scene?.id ? getSceneRecipeById(scene.id) : null;
|
||||
if (declares(recipe?.cameraPath)) return true;
|
||||
const packs = Array.isArray(scene?.appliedShotPacks)
|
||||
? scene.appliedShotPacks
|
||||
: [];
|
||||
return packs.some((marker) =>
|
||||
declares(
|
||||
getSceneAppendRecipeById(marker?.id)?.legacySceneBootstrap?.cameraPath,
|
||||
),
|
||||
);
|
||||
}
|
||||
@@ -87,6 +87,25 @@ test('a built-in shot title localizes only for a recipe-sourced shot', () => {
|
||||
}
|
||||
});
|
||||
|
||||
test('bootstrap shots of an applied shot pack localize their built-in titles', () => {
|
||||
setLocale('ru');
|
||||
const title = 'Global Incident Context';
|
||||
const shot = { id: 'shot-1', title };
|
||||
// A legacy scene upgraded in place: its bootstrap shots have no
|
||||
// sourcePackId, but the scene carries the pack marker.
|
||||
const upgraded = {
|
||||
id: 'scene-legacy',
|
||||
appliedShotPacks: [{ id: 'bhote-koshi-nepal-evidence-pack', version: 12 }],
|
||||
};
|
||||
assert.equal(
|
||||
displayShotTitle(title, shot, upgraded),
|
||||
displayShotTitle(title),
|
||||
);
|
||||
assert.notEqual(displayShotTitle(title, shot, upgraded), title);
|
||||
// The same typed title in a scene without the pack stays as typed.
|
||||
assert.equal(displayShotTitle(title, shot, { id: 'scene-user' }), title);
|
||||
});
|
||||
|
||||
test('the Scene status line shows an operator-typed shot title as typed', async () => {
|
||||
const { createScenePlaybackAdapter } = await import('./playbackAdapter.js');
|
||||
const statuses = [];
|
||||
|
||||
+15
-13
@@ -1,5 +1,5 @@
|
||||
import { updateStatusBadge } from './statusBadge.js';
|
||||
import { t, tn } from '../i18n/index.js';
|
||||
import { keepCodesWithValues, t, tn } from '../i18n/index.js';
|
||||
import { cctvSourceLabelText, cctvSourceMessageText } from './cctvFrames.js';
|
||||
|
||||
import { createCctvVideoSurface } from './cctvVideo.js';
|
||||
@@ -167,18 +167,20 @@ export function _renderCctvState(state) {
|
||||
: '';
|
||||
const projLabel =
|
||||
state?.showProjection !== false ? t('MONITOR') : t('OFF');
|
||||
this._cctvMeta.textContent = t(
|
||||
'{city} · HDG {heading} · FOV {fov} · RANGE {range}m · {projection}{calBadge} · {provider}{status}',
|
||||
{
|
||||
city: activeCamera.city,
|
||||
heading: `${Math.round(activeCamera.headingDeg)}°`,
|
||||
fov: `${Math.round(activeCamera.fovDeg)}°`,
|
||||
range: Math.round(activeCamera.rangeM),
|
||||
projection: projLabel,
|
||||
calBadge: calBadge ? ` · ${calBadge}` : '',
|
||||
provider,
|
||||
status: `${credit}${statusMsg}`,
|
||||
},
|
||||
this._cctvMeta.textContent = keepCodesWithValues(
|
||||
t(
|
||||
'{city} · HDG {heading} · FOV {fov} · RANGE {range}m · {projection}{calBadge} · {provider}{status}',
|
||||
{
|
||||
city: activeCamera.city,
|
||||
heading: `${Math.round(activeCamera.headingDeg)}°`,
|
||||
fov: `${Math.round(activeCamera.fovDeg)}°`,
|
||||
range: Math.round(activeCamera.rangeM),
|
||||
projection: projLabel,
|
||||
calBadge: calBadge ? ` · ${calBadge}` : '',
|
||||
provider,
|
||||
status: `${credit}${statusMsg}`,
|
||||
},
|
||||
),
|
||||
);
|
||||
} else if (cameras.length > 0) {
|
||||
this._cctvMeta.textContent = enabled
|
||||
|
||||
@@ -80,7 +80,13 @@ export function createSceneDialog(title, onClose) {
|
||||
border: '1px solid #397080',
|
||||
font: multiline ? '12px monospace' : 'inherit',
|
||||
});
|
||||
if (multiline) node.rows = 9;
|
||||
if (multiline) {
|
||||
node.rows = 9;
|
||||
// The multiline fields hold JSON: code reads left to right in every
|
||||
// language, or braces, quotes and minus signs move in Arabic.
|
||||
node.dir = 'ltr';
|
||||
node.spellcheck = false;
|
||||
}
|
||||
wrapper.append(node);
|
||||
if (type === 'file') fileChooser(wrapper, node);
|
||||
body.append(wrapper);
|
||||
|
||||
+39
-2
@@ -37,8 +37,8 @@ html:lang(ar) {
|
||||
'Noto Sans Arabic', 'JetBrains Mono', 'SF Mono', 'Fira Code', 'Geeza Pro',
|
||||
Tahoma, monospace;
|
||||
--font-sans:
|
||||
'Inter', 'Noto Sans Arabic', -apple-system, BlinkMacSystemFont,
|
||||
'Geeza Pro', Tahoma, sans-serif;
|
||||
'Inter', 'Noto Sans Arabic', -apple-system, BlinkMacSystemFont, 'Geeza Pro',
|
||||
Tahoma, sans-serif;
|
||||
}
|
||||
/* The Global Context lines and their buttons set ui-monospace through the
|
||||
`font` shorthand; route them through the Arabic-aware stack too. */
|
||||
@@ -139,3 +139,40 @@ html[dir='rtl']
|
||||
:is(.context-radio-mini-transport, .sdr-seek-row, .radio-tuner) {
|
||||
direction: ltr;
|
||||
}
|
||||
|
||||
/* Canvas labels (installations, map cards, detections) are measured and drawn
|
||||
with left-to-right geometry: a card's text starts at its left padding. A
|
||||
canvas inherits the page direction, which turned that start into the right
|
||||
edge and pushed every title out of its card. */
|
||||
html[dir='rtl'] canvas {
|
||||
direction: ltr;
|
||||
}
|
||||
|
||||
/* Scene callouts on the screen whiteboard: the card grows to the right of the
|
||||
text's x, so right-to-left text ends there instead of starting there. The
|
||||
injected stack (JetBrains Mono) has no Arabic glyphs; use the Arabic-aware
|
||||
one. */
|
||||
html[dir='rtl'] .gev-anno-text {
|
||||
text-anchor: end;
|
||||
font-family: var(--font-mono);
|
||||
letter-spacing: 0;
|
||||
}
|
||||
|
||||
/* Position counters built from bare numbers (01 / 25, 1 / 3) read as a number
|
||||
pair, left to right, as an unspaced 1/3 does; otherwise the slash swaps
|
||||
them and the current item looks like the total. Translated counters say
|
||||
«من» instead. */
|
||||
html[dir='rtl']
|
||||
:is(
|
||||
#bhote-koshi-event-panel [data-role='beat-index'],
|
||||
#cockpit-brief-position
|
||||
) {
|
||||
direction: ltr;
|
||||
unicode-bidi: isolate;
|
||||
}
|
||||
|
||||
/* The radio tuner header is a left-to-right instrument row; its channel
|
||||
readout («القناة 01 من 750») still reads in its own language. */
|
||||
html[dir='rtl'] #radio-tuner-value {
|
||||
unicode-bidi: plaintext;
|
||||
}
|
||||
Reference in new issue
Block a user