fix(mobile): Arabic reads in its direction: arrows turn round, Latin names keep their ends, figures stay whole

In the touch shell the back, forward, disclosure, date-range and flow
arrows kept pointing right in Arabic, so Settle up showed the debt the
wrong way round and Vacay's year arrows pointed inward. The shell now
mirrors lucide's direction arrows in right-to-left languages; an icon
that names its own treatment keeps it, and the lightbox arrows stay on
their physical sides.

Latin user text in Arabic rows lost its beginning when cut and moved its
punctuation to the wrong end. Names, titles, addresses and file names take
their direction from their own text (dir=auto) and still line up with the
row; markdown paragraphs, chat messages, poll options and journal stories
are isolated; signed balances, amounts, sizes, rates and leg figures read
as one left-to-right run; URL fields read left to right. Name pairs joined
by an arrow isolate each name and point from the first to the second.

The hero progress and poll result bars fill from the start edge. Arabic
and Hebrew uppercase caption labels grow a step and take the muted ink,
and a todo's due date in a notification is written in the reader's
language instead of as YYYY-MM-DD.
This commit is contained in:
Maurice
2026-11-07 23:21:33 +01:00
parent 2a6c771ad9
commit bf00412863
61 changed files with 496 additions and 128 deletions
@@ -80,6 +80,34 @@ export function createdAt(value: string): Date {
const dayStart = (d: Date) => new Date(d.getFullYear(), d.getMonth(), d.getDate()).getTime();
const BARE_DAY = /^(\d{4})-(\d{2})-(\d{2})$/;
/**
* The parameters of a notification's text as the reader's language writes
* them. A day the server sends (a todo's due date) arrives as YYYY-MM-DD, which
* would stand as "2026-11-05" in a German or Arabic sentence; it becomes the
* short date of the language ("5. Nov.", "٥ نوفمبر"), with the year only when
* it is not this one. Everything else passes through as it came.
*/
export function localizedParams(
params: Record<string, string> | null | undefined,
locale: string,
now: Date = new Date()
): Record<string, string> {
const out: Record<string, string> = { ...params };
for (const [key, value] of Object.entries(out)) {
const day = BARE_DAY.exec(value);
if (!day) continue;
const date = new Date(Number(day[1]), Number(day[2]) - 1, Number(day[3]));
out[key] = new Intl.DateTimeFormat(locale, {
day: 'numeric',
month: 'short',
...(date.getFullYear() === now.getFullYear() ? {} : { year: 'numeric' }),
}).format(date);
}
return out;
}
/**
* The heading of the day a notification belongs to, in the reader's language:
* today and yesterday by name, the rest of the week by weekday, anything older