Second pass: four dialogs that were never wrapped

A sweep for UI text still rendering in English, after the nine catalogues were
brought up to date. Four dialogs were building their own English:

  - The delete confirmation in MailView, entirely: both titles, both messages
    and the confirm label. Its counts read "message(s)", which is a
    parenthesis standing in for agreement -- every language that inflects got
    the wrong form. They are plural() calls now.
  - Rename, in the Files tree.
  - New address book, and its Name placeholder.
  - New category, and its Name placeholder. The button opening that dialog was
    already translated, which is how it went unnoticed: the label read right
    and the dialog it opened did not.

Rename, New address book, New category, Name and Delete are already in all
nine catalogues. Delete?, Delete forever? and the two plural forms are new and
land with each language.

Verified: typecheck clean, 1000 tests pass.
This commit is contained in:
2026-09-03 11:58:17 -07:00
parent 3ffee1224f
commit 20b6475f18
4 changed files with 13 additions and 4 deletions
+1 -1
View File
@@ -114,7 +114,7 @@ export function ContactsSidebar() {
title={t("New address book")} title={t("New address book")}
aria-label={t("New address book")} aria-label={t("New address book")}
onClick={async () => { onClick={async () => {
const name = await promptDialog({ title: "New address book", placeholder: "Name" }); const name = await promptDialog({ title: t("New address book"), placeholder: t("Name") });
if (!name?.trim()) return; if (!name?.trim()) return;
try { try {
await contacts.createBook(name.trim()); await contacts.createBook(name.trim());
+1 -1
View File
@@ -257,7 +257,7 @@ export function FilesTree() {
label={t("Rename")} label={t("Rename")}
disabled={!menuNode.myRights?.mayRename} disabled={!menuNode.myRights?.mayRename}
onClick={async () => { onClick={async () => {
const name = await promptDialog({ title: "Rename", defaultValue: menuNode.name }); const name = await promptDialog({ title: t("Rename"), defaultValue: menuNode.name });
if (!name?.trim() || name === menuNode.name) return; if (!name?.trim() || name === menuNode.name) return;
try { try {
await useFiles.getState().rename(menuNode.id, name.trim()); await useFiles.getState().rename(menuNode.id, name.trim());
+10 -1
View File
@@ -228,7 +228,16 @@ export function MailView({ mailboxId, threadId, search }: { mailboxId?: string;
const trashId = mail.roleId("trash"); const trashId = mail.roleId("trash");
const permanent = t.every((id) => trashId && mail.emails[id]?.mailboxIds[trashId]); const permanent = t.every((id) => trashId && mail.emails[id]?.mailboxIds[trashId]);
if (permanent || settings.confirmDelete) { if (permanent || settings.confirmDelete) {
const ok = await confirmDialog({ title: permanent ? "Delete forever?" : "Delete?", message: permanent ? `${t.length} message(s) will be permanently deleted.` : `Move ${t.length} message(s) to Trash?`, confirmLabel: "Delete", danger: permanent }); // "message(s)" was doing the work a plural form should: every
// language that inflects got a parenthesis instead of agreement.
const ok = await confirmDialog({
title: permanent ? translate("Delete forever?") : translate("Delete?"),
message: permanent
? plural(t.length, { one: "{n} message will be permanently deleted.", other: "{n} messages will be permanently deleted." })
: plural(t.length, { one: "Move {n} message to Trash?", other: "Move {n} messages to Trash?" }),
confirmLabel: translate("Delete"),
danger: permanent,
});
if (!ok) return; if (!ok) return;
} }
await mail.trash(t); await mail.trash(t);
+1 -1
View File
@@ -60,7 +60,7 @@ export function CalendarSettings() {
<div style={{ marginTop: 8 }}><ColorSwatches value={c.color} onChange={(col) => update({ eventCategories: s.eventCategories.map((x, j) => (j === i ? { ...x, color: col } : x)) })} /></div> <div style={{ marginTop: 8 }}><ColorSwatches value={c.color} onChange={(col) => update({ eventCategories: s.eventCategories.map((x, j) => (j === i ? { ...x, color: col } : x)) })} /></div>
</div> </div>
))} ))}
<button className="btn mb-16" onClick={async () => { const n = await promptDialog({ title: "New category", placeholder: "Name" }); if (n?.trim() && !s.eventCategories.some((c) => c.name.toLowerCase() === n.trim().toLowerCase())) update({ eventCategories: [...s.eventCategories, { name: n.trim(), color: CALENDAR_COLORS[s.eventCategories.length % CALENDAR_COLORS.length]! }] }); }}><Plus size={16} /> {t("New category")}</button> <button className="btn mb-16" onClick={async () => { const n = await promptDialog({ title: t("New category"), placeholder: t("Name") }); if (n?.trim() && !s.eventCategories.some((c) => c.name.toLowerCase() === n.trim().toLowerCase())) update({ eventCategories: [...s.eventCategories, { name: n.trim(), color: CALENDAR_COLORS[s.eventCategories.length % CALENDAR_COLORS.length]! }] }); }}><Plus size={16} /> {t("New category")}</button>
<h2>{t("Subscribed calendars")}</h2> <h2>{t("Subscribed calendars")}</h2>
<p className="hint" style={{ marginTop: -8 }}> <p className="hint" style={{ marginTop: -8 }}>