i18n(ar): mirror the layout right to left

- The title, style indicator and both rails swap sides in RTL through logical
  properties (inset-inline-*, margin-inline-*, border-inline-start); LTR
  computes the same values. The Display/Context rail keeps its physical
  `right` for the credit-clearance model and swaps in rtl.css above 720px.
- rtl.css: no letter-spacing on Arabic (it tears joined letters apart);
  Noto Sans Arabic leads the Arabic font stacks so words are not set in a
  fixed-pitch fallback; the intel HUD and cockpit instruments stay a
  left-to-right instrument overlay; media transport and the radio dial stay
  left to right; feed headlines and station names take their own direction;
  the moved rail's arrows flip.
- isolate(): a quantity in RTL text (2.7 كم, -18.0°) is wrapped in
  first-strong isolates so its digits do not join a neighbouring Latin run.

Co-Authored-By: Claude Opus 5.5 <[email protected]>
This commit is contained in:
German KhokhlovandClaude Opus 5.5 committed 2026-10-28 13:28:23 +04:00
1 parent 0b928b6810
commit 06370d8676
15 files changed
+183 -39

No files matched your search

+9 -1
View File
@@ -10,6 +10,7 @@ import {
import { hudQuantity, LOCALIZED_LOCALITY } from '../hudLocalityFormat.js';
import { formatCockpitDistance } from '../ui/cockpitPresentation.js';
import { READOUT_WORDS } from '../layers/recentImagery/display.js';
import { formatQuantity } from '../ui/unitLabels.js';
test('Arabic resolves from URL, saved preference and browser language', () => {
assert.equal(
@@ -88,13 +89,20 @@ test('Arabic uses all six plural forms and spaced HUD units', () => {
],
);
assert.equal(hudQuantity(2069, 'km', 1), '2069.0 كم');
assert.equal(formatCockpitDistance(14000), '14 كم');
// Isolated, so the number keeps its unit next to Latin words.
assert.equal(formatCockpitDistance(14000), '\u206814\u00a0كم\u2069');
assert.equal(
`Sentinel-2 · ${formatQuantity(30, 'm')}`,
'Sentinel-2 · \u206830\u00a0م\u2069',
);
assert.equal(
LOCALIZED_LOCALITY.sector({ latDeg: 21.33, lonDeg: -77.5 }),
'القطاع 21.33ش 77.50غ',
);
assert.equal(READOUT_WORDS.daysAgo(9), 'قبل 9 أيام');
assert.equal(t('SAVE KEYS'), 'حفظ المفاتيح');
setLocale('ru');
assert.equal(formatCockpitDistance(14000), '14 КМ');
} finally {
setLocale(previous);
}
+10
View File
@@ -14,6 +14,7 @@
import {
DEFAULT_LOCALE,
intlLocale,
isRtlLocale,
normalizeLocale,
writeStoredLocale,
} from './locale.js';
@@ -43,6 +44,15 @@ export function getLocale() {
return currentLocale;
}
/**
* A value inserted into right-to-left text as one unit: `30 م` next to
* `Sentinel-2` would otherwise lend its digits to the Latin run and leave the
* unit stranded. First-strong isolates (U+2068…U+2069) in RTL locales only.
*/
export function isolate(text) {
return isRtlLocale(currentLocale) ? `\u2068${text}\u2069` : String(text);
}
/**
* Switch the active locale. Pure state change: call applyDocumentLanguage /
* applyDocumentTranslations to reflect it in the DOM.
+5
View File
@@ -30,6 +30,11 @@ export const LOCALE_NAMES = Object.freeze({
/** Right-to-left locales. */
const RTL_LOCALES = Object.freeze(['ar']);
/** Whether a shipped locale reads right to left. */
export function isRtlLocale(locale) {
return RTL_LOCALES.includes(normalizeLocale(locale));
}
/**
* Tags for Intl formatters where the locale code alone is not enough. Arabic
* keeps Western digits (0–9) like its catalog: ICU picks Arabic-Indic digits