Third pass: the strings libraries build and views render raw

A sweep of lib/ and store/ for user-facing English, after the views were done.
The pattern here is the one #248 found: a module returns an English sentence
and the view renders it without asking for a translation.

Scheduled send was entirely untranslated. The four presets -- Later today,
Tomorrow morning, Tomorrow afternoon, Monday morning -- were rendered raw, and
scheduleError() returned three English sentences straight to the picker.
describeSpan() built "30 days" by appending an "s" unless the count was one,
which is English grammar written into the code: it produces the right German
only by the two languages happening to agree, and Russian needs three forms.
It is plural() now. scheduleError translates in place rather than returning a
key, because it composes a sentence around that span.

Read receipts: refusalText() returns five explanations, all rendered raw in
MessageView, and the offered case was a template literal -- "Requested, to
x@y. Never sent automatically." -- with the address concatenated in. It takes
a placeholder now, so the sentence can be reordered.

Compose: the sending toast and its Undo, the Open draft action, the two
attachment failures, and three thrown errors that surface to the reader as
toasts.

Two swipe labels, Add star and Remove star, are rendered through
t(desc.label) and have never been in any catalogue -- the coverage scan that
found the other 176 only looked at t("literal") sites, so labels reaching t()
through a variable were invisible to it. Every such table is now enumerated
and all 61 values checked: these two were the only ones missing.

17 strings and 3 plural forms are new and land with each language.

Verified: typecheck clean, 1000 tests pass.
This commit is contained in:
2026-09-03 13:52:24 -07:00
parent 3ffee1224f
commit be088ed78d
4 changed files with 25 additions and 18 deletions
+1 -1
View File
@@ -18,7 +18,7 @@ export function ScheduleMenuItems({ maxMs, onPick, onCustom }: { maxMs: number;
<MenuSep />
<MenuTitle>{t("Schedule send")}</MenuTitle>
{presets.map((p) => (
<MenuItem key={p.id} icon={<Clock size={16} />} label={p.label} kbd={formatScheduleTime(p.at)} onClick={() => onPick(p.at)} />
<MenuItem key={p.id} icon={<Clock size={16} />} label={t(p.label)} kbd={formatScheduleTime(p.at)} onClick={() => onPick(p.at)} />
))}
<MenuItem icon={<Clock size={16} />} label={t("Pick date and time…")} onClick={onCustom} />
</>
+1 -1
View File
@@ -337,7 +337,7 @@ export const MessageView = memo(function MessageView({ email: e, expanded, wasUn
{e["header:List-Id:asText"] && <><dt>{translate("List")}</dt><dd>{e["header:List-Id:asText"]}</dd></>}
<dt>{translate("Size")}</dt><dd>{formatSize(e.size)}</dd>
{spam && <><dt>{translate("Spam filter")}</dt><dd><SpamSummary report={spam} /></dd></>}
{receiptRequested && <><dt>{translate("Receipt")}</dt><dd>{receipt.offer ? `Requested, to ${receipt.to!.email}. Never sent automatically.` : refusalText(receipt.refusal!)}</dd></>}
{receiptRequested && <><dt>{translate("Receipt")}</dt><dd>{receipt.offer ? translate("Requested, to {address}. Never sent automatically.", { address: receipt.to!.email }) : translate(refusalText(receipt.refusal!))}</dd></>}
</dl>
)}
{receipt.offer && settings.readReceiptPolicy !== "never" && receiptDone !== "dismissed" && (