mirror of
https://github.com/bilawalsidhu/gods-eye-view.git
synced 2026-11-05 10:53:59 +00:00
i18n(ar): apply review round 3
Co-Authored-By: Claude Opus 5.5 <[email protected]>
This commit is contained in:
1 parent
8540535817
commit
df866f33a5
13 files changed
+110
-38
No files matched your search
@@ -18,7 +18,11 @@ import {
|
||||
import { hudQuantity, LOCALIZED_LOCALITY } from '../hudLocalityFormat.js';
|
||||
import { formatCockpitDistance } from '../ui/cockpitPresentation.js';
|
||||
import { READOUT_WORDS } from '../layers/recentImagery/display.js';
|
||||
import { formatQuantity, formatScaleLabel } from '../ui/unitLabels.js';
|
||||
import {
|
||||
formatQuantity,
|
||||
formatScaleLabel,
|
||||
formatScaleRange,
|
||||
} from '../ui/unitLabels.js';
|
||||
|
||||
test('Arabic resolves from URL, saved preference and browser language', () => {
|
||||
assert.equal(
|
||||
@@ -133,6 +137,11 @@ test('Arabic uses all six plural forms and spaced HUD units', () => {
|
||||
'\u2068300+\u2069',
|
||||
'\u20680.1\u2069',
|
||||
]);
|
||||
// A Latin unit after the range keeps its order: °C, not C°.
|
||||
assert.equal(
|
||||
formatScaleRange('-40', '50+', '°C'),
|
||||
`${formatScaleLabel('-40')} — \u206850+\u2069\u00a0\u2068°C\u2069`,
|
||||
);
|
||||
// Counted minutes agree with the number (3–10 plural, 11+ singular).
|
||||
assert.deepEqual(
|
||||
[3, 15].map((n) =>
|
||||
|
||||
+18
-16
@@ -166,7 +166,7 @@
|
||||
"READY": "جاهز",
|
||||
"ACTIVE STYLE": "النمط النشط",
|
||||
"Return UI controls": "إعادة عناصر التحكم",
|
||||
"EXIT CLEAN VIEW": "الخروج من العرض النظيف",
|
||||
"EXIT CLEAN VIEW": "الخروج من الواجهة النظيفة",
|
||||
"Expand Visual Presets": "توسيع الأنماط البصرية",
|
||||
"VISUAL PRESETS": "الأنماط البصرية",
|
||||
"Pin visual presets": "تثبيت الأنماط البصرية",
|
||||
@@ -551,7 +551,7 @@
|
||||
"PROJECTION ON": "الإسقاط مفعّل",
|
||||
"CALIBRATION": "المعايرة",
|
||||
"ADJUST": "ضبط",
|
||||
"Drag the camera in the world: rings rotate, arrows move, handles set range/FOV": "اسحب الكاميرا في العالم: تدور الحلقات وتحرّك الأسهم الموضع وتضبط المقابض المدى وزاوية الرؤية",
|
||||
"Drag the camera in the world: rings rotate, arrows move, handles set range/FOV": "اسحب الكاميرا في العالم: الحلقات لتدويرها، والأسهم لتحريكها، والمقابض لضبط المدى وزاوية الرؤية",
|
||||
"Camera pose — click a value to type": "وضعية الكاميرا — انقر على قيمة لكتابتها",
|
||||
"Heading (compass °) — click to type": "الاتجاه (بوصلة °) — انقر للكتابة",
|
||||
"Pitch (° up/down) — click to type": "الميل (° أعلى/أسفل) — انقر للكتابة",
|
||||
@@ -814,7 +814,7 @@
|
||||
"VISUAL": "مرئية",
|
||||
"Brightest naked-eye objects — CelesTrak visual group": "ألمع الأجرام المرئية بالعين المجردة — مجموعة CelesTrak المرئية",
|
||||
"COMMS": "اتصالات",
|
||||
"Broadband constellation shell — shown only in DENSE mode": "غلاف كوكبة النطاق العريض — يظهر في وضع الكثيف فقط",
|
||||
"Broadband constellation shell — shown only in DENSE mode": "غلاف كوكبة النطاق العريض — يظهر في وضع «كثيف» فقط",
|
||||
"refreshing...": "جارٍ التحديث…",
|
||||
"KEY REQUIRED": "المفتاح مطلوب",
|
||||
"STALE · cached {age}": "متقادم · مخزّن {age}",
|
||||
@@ -899,6 +899,8 @@
|
||||
"awaiting usable AIS positions…": "بانتظار مواضع AIS صالحة…",
|
||||
"awaiting first AIS message…": "بانتظار أول رسالة AIS…",
|
||||
"EST. DOWNRANGE": "تقدير على امتداد المسار",
|
||||
"CONFIRMED": "مؤكَّد",
|
||||
"PAD / RTLS": "منصة الإطلاق / RTLS",
|
||||
"Pick a shape, then click the map.": "اختر شكلًا، ثم انقر على الخريطة.",
|
||||
"Enter to place the pin, Esc to cancel.": "Enter لوضع الدبوس، وEsc للإلغاء.",
|
||||
"Click where the pin goes.": "انقر حيث تريد وضع الدبوس.",
|
||||
@@ -938,7 +940,7 @@
|
||||
"General emergency": "طوارئ عامة",
|
||||
"Unlawful interference": "تدخل غير مشروع",
|
||||
"Radio failure": "عطل في الاتصال اللاسلكي",
|
||||
"Downed aircraft": "طائرة سقطت",
|
||||
"Downed aircraft": "طائرة ساقطة",
|
||||
"Minimum fuel": "الحد الأدنى من الوقود",
|
||||
"Lifeguard (medical priority)": "إسعاف جوي (أولوية طبية)",
|
||||
"Rasuwagadhi witness video": "فيديو شاهد عيان في Rasuwagadhi",
|
||||
@@ -1599,7 +1601,7 @@
|
||||
"WebUSB needs desktop Chrome or Edge": "يتطلب WebUSB متصفح Chrome أو Edge على سطح المكتب",
|
||||
"connect a receiver in Radio": "وصّل جهاز استقبال في «الراديو»",
|
||||
"receiver is in FM mode": "جهاز الاستقبال في وضع FM",
|
||||
"listening": "استماع",
|
||||
"listening": "جارٍ الاستماع",
|
||||
"USB error": "خطأ USB",
|
||||
"${amount}K": "{amount} ألف دولار",
|
||||
"${amount}M": "{amount} مليون دولار",
|
||||
@@ -1622,8 +1624,8 @@
|
||||
"many": "{acres} آكر",
|
||||
"other": "{acres} آكر"
|
||||
},
|
||||
"{percent}% contained": "تمت السيطرة على {percent}%",
|
||||
"containment unknown": "نسبة السيطرة غير معروفة",
|
||||
"{percent}% contained": "الاحتواء: {percent}%",
|
||||
"containment unknown": "نسبة الاحتواء غير معروفة",
|
||||
"{cause} cause": "السبب: {cause}",
|
||||
"{personnel} personnel": {
|
||||
"zero": "{personnel} فرد",
|
||||
@@ -1643,11 +1645,11 @@
|
||||
"Unnamed incident": "حادث بلا اسم",
|
||||
"Open {title} on InciWeb": "فتح {title} على InciWeb",
|
||||
"Perimeter source unavailable": "مصدر محيطات الحرائق غير متاح",
|
||||
"Not contained or unknown": "غير مسيطر عليه أو غير معروف",
|
||||
"Under 50% contained": "السيطرة أقل من 50%",
|
||||
"50–99% contained": "السيطرة 50–99%",
|
||||
"Fully contained": "مسيطر عليه بالكامل",
|
||||
"Colour shows reported containment. Perimeters are simplified to about 100 m.": "يبيّن اللون نسبة السيطرة المُبلَّغ عنها. المحيطات مبسّطة إلى نحو 100 م.",
|
||||
"Not contained or unknown": "بلا احتواء أو غير معروف",
|
||||
"Under 50% contained": "الاحتواء: أقل من 50%",
|
||||
"50–99% contained": "الاحتواء: 50–99%",
|
||||
"Fully contained": "احتواء كامل",
|
||||
"Colour shows reported containment. Perimeters are simplified to about 100 m.": "يبيّن اللون نسبة الاحتواء المُبلَّغ عنها. المحيطات مبسّطة إلى نحو 100 م.",
|
||||
"Malformed perimeter snapshot": "لقطة المحيطات مشوّهة",
|
||||
"radio tag|All": "الكل",
|
||||
"Directory refresh degraded; showing the previous station catalog.": "تحديث الدليل متدهور؛ يُعرض فهرس المحطات السابق.",
|
||||
@@ -2199,14 +2201,14 @@
|
||||
"Live stream connected": "البث المباشر متصل",
|
||||
"Snapshot feed connected": "مصدر اللقطات متصل",
|
||||
"Media fetch failed": "فشل جلب الوسائط",
|
||||
"Upstream snapshot active": "لقطة المصدر نشطة",
|
||||
"Upstream snapshot active": "لقطة المصدر الأصلي نشطة",
|
||||
"Fallback Street View frame": "إطار بديل من Street View",
|
||||
"Upstream unavailable": "المصدر غير متاح",
|
||||
"Upstream unavailable": "المصدر الأصلي غير متاح",
|
||||
"No source configured": "لم يُعَدّ أي مصدر",
|
||||
"Configured source": "المصدر المُعَدّ",
|
||||
"Configured Source": "المصدر المُعَدّ",
|
||||
"Synthetic fallback": "بديل محاكى",
|
||||
"Upstream HTTP {status}": "المصدر: HTTP {status}",
|
||||
"Upstream HTTP {status}": "المصدر الأصلي: HTTP {status}",
|
||||
"Unexpected media type {type}": "نوع وسائط غير متوقع: {type}",
|
||||
"{value} KM/H": "{value} كم/س",
|
||||
"{label} · {radius} AIR/SEA WINDOW": "{label} · نطاق جو/بحر {radius}",
|
||||
@@ -2294,7 +2296,7 @@
|
||||
"Data source unavailable": "مصدر البيانات غير متاح",
|
||||
"Local FM stopped: internet radio started": "توقف FM المحلي: بدأ راديو الإنترنت",
|
||||
"IDLE": "خامل",
|
||||
"TUNING": "ضبط",
|
||||
"TUNING": "جارٍ الضبط",
|
||||
"STREAMING": "استقبال",
|
||||
"UNSUPPORTED": "غير مدعوم",
|
||||
"idle": "خامل",
|
||||
|
||||
@@ -739,6 +739,8 @@
|
||||
"awaiting usable AIS positions…": "esperando posiciones AIS utilizables…",
|
||||
"awaiting first AIS message…": "esperando el primer mensaje AIS…",
|
||||
"EST. DOWNRANGE": "DIST. ESTIMADA",
|
||||
"CONFIRMED": "CONFIRMADA",
|
||||
"PAD / RTLS": "PLATAFORMA / RTLS",
|
||||
"MISSION CONTROL · FIRST LAUNCH": "CENTRO DE MANDO · PRIMER INICIO",
|
||||
"Choose your first view": "Elige tu primera vista",
|
||||
"LIVE CONTACTS": "CONTACTOS EN VIVO",
|
||||
|
||||
@@ -216,6 +216,8 @@
|
||||
"Pause replay": "Приостановить повтор",
|
||||
"Cancel replay": "Отменить повтор",
|
||||
"EST. DOWNRANGE": "РАСЧЁТ ПО ТРАССЕ",
|
||||
"CONFIRMED": "ПОДТВЕРЖДЕНО",
|
||||
"PAD / RTLS": "СТАРТОВАЯ ПЛОЩАДКА / RTLS",
|
||||
"PLANNED · ": "ПЛАН · ",
|
||||
"SUPPLIED TRAJECTORY POINTS": "ПРЕДОСТАВЛЕННЫЕ ТОЧКИ ТРАЕКТОРИИ",
|
||||
"RECONSTRUCTED ESTIMATE": "РЕКОНСТРУИРОВАННАЯ ОЦЕНКА",
|
||||
|
||||
@@ -811,6 +811,8 @@
|
||||
"Pause replay": "暂停回放",
|
||||
"Cancel replay": "取消回放",
|
||||
"EST. DOWNRANGE": "预估落点距离",
|
||||
"CONFIRMED": "已确认",
|
||||
"PAD / RTLS": "发射台 / RTLS",
|
||||
"PLANNED · ": "计划 · ",
|
||||
"SUPPLIED TRAJECTORY POINTS": "提供的轨迹点",
|
||||
"RECONSTRUCTED ESTIMATE": "重建估算",
|
||||
|
||||
@@ -806,6 +806,8 @@
|
||||
"Pause replay": "暫停回放",
|
||||
"Cancel replay": "取消回放",
|
||||
"EST. DOWNRANGE": "預估落點距離",
|
||||
"CONFIRMED": "已確認",
|
||||
"PAD / RTLS": "發射臺 / RTLS",
|
||||
"PLANNED · ": "計劃 · ",
|
||||
"SUPPLIED TRAJECTORY POINTS": "提供的軌跡點",
|
||||
"RECONSTRUCTED ESTIMATE": "重建估算",
|
||||
|
||||
@@ -6,6 +6,15 @@ import {
|
||||
} from './policy.js';
|
||||
import { UNKNOWN_LAUNCH_STATUS } from './model.js';
|
||||
|
||||
// Landing endpoints carry an English accuracy code (the panel and the map
|
||||
// entity compare it); only the text shown in the stage table is translated.
|
||||
// The downrange estimate arrives already translated.
|
||||
function accuracyLabel(accuracy) {
|
||||
if (accuracy === 'CONFIRMED') return t('CONFIRMED');
|
||||
if (accuracy === 'PAD / RTLS') return t('PAD / RTLS');
|
||||
return accuracy;
|
||||
}
|
||||
|
||||
export function createPanel({ state: layerState, services, parts, source }) {
|
||||
/**
|
||||
* Preserve the user's globe scale for roster previews while avoiding an
|
||||
@@ -251,7 +260,7 @@ export function createPanel({ state: layerState, services, parts, source }) {
|
||||
? launch.launchSite
|
||||
: t('UNAVAILABLE'));
|
||||
const position = endpoint
|
||||
? `${endpoint.lat.toFixed(2)}, ${endpoint.lon.toFixed(2)} · ${endpoint.accuracy}`
|
||||
? `${endpoint.lat.toFixed(2)}, ${endpoint.lon.toFixed(2)} · ${accuracyLabel(endpoint.accuracy)}`
|
||||
: stage.downrangeKm > 0
|
||||
? t('{value} KM DOWNRANGE', {
|
||||
value: stage.downrangeKm.toLocaleString(),
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
import { t } from '../i18n/index.js';
|
||||
import { syncChipGroup } from './chipGroup.js';
|
||||
import { syncRowList } from './rowList.js';
|
||||
import { formatScaleLabel, unitLabel } from './unitLabels.js';
|
||||
import { formatScaleRange } from './unitLabels.js';
|
||||
import { createSlider } from './slider.js';
|
||||
|
||||
const set = (node, key, value) => {
|
||||
@@ -16,13 +16,6 @@ const order = (parent, nodes) =>
|
||||
parent.insertBefore(node, parent.children[i] || null);
|
||||
});
|
||||
|
||||
// "960 — 1050+ гПа": the unit stays with the last number.
|
||||
const scaleText = (low, high, units) => {
|
||||
const unit = unitLabel(units);
|
||||
const range = `${formatScaleLabel(low)} — ${formatScaleLabel(high)}`;
|
||||
return unit ? `${range}\u00a0${unit}` : range;
|
||||
};
|
||||
|
||||
/** Optional, ordered blocks without disclosure state. All controls are keyed. */
|
||||
export function createRailCardBlocks({ container, cardId, onParams }) {
|
||||
const document = container.ownerDocument;
|
||||
@@ -309,7 +302,11 @@ export function createRailCardBlocks({ container, cardId, onParams }) {
|
||||
block.scale,
|
||||
'textContent',
|
||||
visible
|
||||
? scaleText(legend.labels[0], legend.labels.at(-1), legend.units)
|
||||
? formatScaleRange(
|
||||
legend.labels[0],
|
||||
legend.labels.at(-1),
|
||||
legend.units,
|
||||
)
|
||||
: '',
|
||||
);
|
||||
} else if (props.type === 'actions') {
|
||||
|
||||
@@ -1,4 +1,11 @@
|
||||
import { formatNumber, msg, t, tn, translateKnown } from '../i18n/index.js';
|
||||
import {
|
||||
formatNumber,
|
||||
isolate,
|
||||
msg,
|
||||
t,
|
||||
tn,
|
||||
translateKnown,
|
||||
} from '../i18n/index.js';
|
||||
import { keyGuidanceText, keyRequirementText } from './keyGuidance.js';
|
||||
|
||||
// Street Level UI state to panel strings and flags. Pure, so it is testable.
|
||||
@@ -174,7 +181,10 @@ function presentViewer(state) {
|
||||
if (street.capturedAt)
|
||||
right.push(formatDate(street.capturedAt, street.capturedAtPrecision));
|
||||
const left = [];
|
||||
if (street.title) left.push(street.title);
|
||||
// A provider address mixes Latin and the page language ("602 Congress Ave,
|
||||
// أوستن"); each part is isolated so a house number stays with its street
|
||||
// in right-to-left text. A no-op in left-to-right languages.
|
||||
if (street.title) left.push(street.title.split(', ').map(isolate).join(', '));
|
||||
if (street.creator)
|
||||
left.push(t('Image by {creator}', { creator: street.creator }));
|
||||
if (street.license) left.push(street.license);
|
||||
|
||||
@@ -5,6 +5,7 @@ import {
|
||||
sinceStopIndex,
|
||||
} from './streetLevelPresentation.js';
|
||||
import { providerSnapshot } from '../testSupport/streetLevelFakes.mjs';
|
||||
import { getLocale, setLocale } from '../i18n/index.js';
|
||||
|
||||
const provider = providerSnapshot;
|
||||
|
||||
@@ -321,6 +322,29 @@ test('a Street View caption names the place, dates it by month, and offers no FI
|
||||
assert.equal(view.viewer.renderModes, false);
|
||||
});
|
||||
|
||||
test('in Arabic each part of a provider address keeps its own order', () => {
|
||||
const previous = getLocale();
|
||||
setLocale('ar');
|
||||
try {
|
||||
const view = presentStreetLevelPanel(
|
||||
snapshot({
|
||||
enabled: true,
|
||||
street: {
|
||||
open: true,
|
||||
imageId: 'pano-1',
|
||||
title: '602 Congress Ave, أوستن, تكساس',
|
||||
},
|
||||
}),
|
||||
);
|
||||
assert.equal(
|
||||
view.viewer.captionLeft,
|
||||
'\u2068602 Congress Ave\u2069, \u2068أوستن\u2069, \u2068تكساس\u2069',
|
||||
);
|
||||
} finally {
|
||||
setLocale(previous);
|
||||
}
|
||||
});
|
||||
|
||||
test('the meta line keeps the sequence count and adds the other providers’ hints', () => {
|
||||
const view = presentStreetLevelPanel(
|
||||
snapshot({
|
||||
|
||||
@@ -26,7 +26,7 @@
|
||||
0 0 1.2rem rgba(0, 212, 255, 0.08);
|
||||
backdrop-filter: blur(24px) saturate(1.3);
|
||||
-webkit-backdrop-filter: blur(24px) saturate(1.3);
|
||||
text-align: left;
|
||||
text-align: start;
|
||||
transform: translateX(-50%);
|
||||
transform-origin: bottom center;
|
||||
animation: gev-voice-card-in 180ms cubic-bezier(0.2, 0.8, 0.2, 1);
|
||||
@@ -95,7 +95,7 @@
|
||||
}
|
||||
|
||||
.gev-voice-card-dismiss {
|
||||
margin-left: auto;
|
||||
margin-inline-start: auto;
|
||||
width: 1.1rem;
|
||||
height: 1.1rem;
|
||||
padding: 0;
|
||||
@@ -415,7 +415,7 @@ body.recording-mode .gev-pointer-reticle {
|
||||
-webkit-backdrop-filter: blur(24px) saturate(1.3);
|
||||
color-scheme: dark;
|
||||
font: 0.75rem/1.45 var(--font-sans);
|
||||
text-align: left;
|
||||
text-align: start;
|
||||
transform: translateX(-50%);
|
||||
}
|
||||
:root[data-ui-theme='cyber'] #gev-voice-control .gev-voice-panel {
|
||||
@@ -443,7 +443,7 @@ body.recording-mode .gev-pointer-reticle {
|
||||
letter-spacing: 0.14em;
|
||||
}
|
||||
.gev-voice-panel-tabs {
|
||||
margin-left: auto;
|
||||
margin-inline-start: auto;
|
||||
}
|
||||
#gev-voice-control .gev-voice-panel-header .gev-voice-panel-tab {
|
||||
display: inline-flex;
|
||||
|
||||
@@ -51,3 +51,14 @@ export function formatScaleLabel(label) {
|
||||
})}${match[2]}`,
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* A legend range with its unit: "960 — 1050+ hPa". The unit stays with the
|
||||
* last number. In right-to-left text the unit is isolated as well, so a Latin
|
||||
* unit keeps its own order ("°C", not "C°").
|
||||
*/
|
||||
export function formatScaleRange(low, high, units) {
|
||||
const unit = unitLabel(units);
|
||||
const range = `${formatScaleLabel(low)} — ${formatScaleLabel(high)}`;
|
||||
return unit ? `${range}\u00a0${isolate(unit)}` : range;
|
||||
}
|
||||
Reference in new issue
Block a user