feat(i18n): Arabic, Hebrew, Persian and Urdu, and a layout that mirrors; release v6.4.0
CodeQL Analysis / Analyze CodeQL (push) Canceled after 0s
Deploy Application / deploy (push) Canceled after 0s
Mirror to Codeberg / mirror (push) Canceled after 0s
Mirror to PrivacyGuides / mirror (push) Canceled after 0s

Four right-to-left languages at /ar/, /he/, /fa/ and /ur/ — thirteen in all.

The layout no longer has a left and a right, it has a start and an end: the
margins, insets and corners are CSS logical properties now, so they follow
dir on <html>. Directional glyphs flip; keys, safety codes and session
descriptors are pinned left-to-right so bidi cannot reorder what two people
compare against each other's screens.

Also fixed: the Service Worker was registered as './sw.js', which 404s from
every locale subdirectory, so twelve of the thirteen pages had no worker at
all. And the bundler ran before the dictionaries were generated, so a newly
added language could reach the page ahead of the app that renders it.
This commit is contained in:
lockbitchat
2026-08-29 18:05:20 -04:00
parent 98d42ac2fb
commit 5e32f547b9
68 changed files with 15147 additions and 500 deletions
+41
View File
@@ -110,6 +110,44 @@ export function currentLocale() {
return resolvedLocale;
}
/**
* Writing direction of a locale. Arabic, Hebrew, Persian and Urdu read right to left,
* and the whole layout — not just the text — has to follow: an avatar that sits before
* a name in English sits after it in Arabic.
*
* The generated page already carries dir on <html>, so nothing here needs to apply it
* at load. This exists for the handful of decisions CSS cannot express on its own —
* which way an arrow points, which edge a drawer slides in from.
*/
export function localeDir(code = currentLocale()) {
return LOCALE_META[code]?.dir === 'rtl' ? 'rtl' : 'ltr';
}
export function isRTL(code = currentLocale()) {
return localeDir(code) === 'rtl';
}
/**
* +1 or -1, for arithmetic on a horizontal offset: a swipe threshold, a translate, the
* side a sheet enters from. `x * direction()` is the whole of what a mirrored gesture
* needs, and it reads better than an `isRTL ? -x : x` at every call site.
*/
export function direction(code = currentLocale()) {
return isRTL(code) ? -1 : 1;
}
/**
* Force a fragment to be laid out left to right inside right-to-left text.
*
* Bidi reordering is done by the browser on the rendered text, and it mangles exactly
* the strings this app is made of: a key fingerprint, a base64 session descriptor, a
* URL, a version number. In an RTL paragraph "a1b2:c3d4" can come out as "c3d4:a1b2" —
* the characters are all there, so nothing looks broken, and the reader compares the
* wrong thing against their peer's screen. Any element showing machine text gets these
* props.
*/
export const LTR_TEXT = { dir: 'ltr', style: { unicodeBidi: 'isolate', textAlign: 'start' } };
/**
* A locale the visitor would probably rather read, when it is not the one they are on.
* Used to offer a link, never to redirect: an automatic redirect sends Googlebot —
@@ -150,6 +188,9 @@ export function languageLinks({ pathname = '/', active = DEFAULT_LOCALE } = {})
label: LOCALE_META[code]?.nativeName || code,
// Short code for the collapsed switcher.
abbr: LOCALE_META[code]?.abbr || code.toUpperCase(),
// The row renders a name in its own script, so it needs its own direction:
// "العربية" laid out left-to-right is the word spelled backwards.
dir: LOCALE_META[code]?.dir || 'ltr',
isCurrent: code === active,
}));
}