Real plurals: the web's sync messages and Android's tag counts
Web: one counted(n, one, other) helper. The sync screen's three "(s)" messages (attachments that didn't download, changes refused, files that didn't upload) now say "1 file" / "2 files". The six hand-written ternaries (SyncView x2, ImportNotes, GroupList, ShareDialog, LabelsModal) use it too. Android: tags_count was two strings chosen in code, and is now <plurals>. The delete confirmation read "It is on 1 notes … removed from all of them" and now has a one form of its own. Operator decision on #5371, 2026-10-08. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
@@ -25,6 +25,7 @@ import {
|
||||
import { errorMessage } from "../api/errors";
|
||||
import { formatDateTime } from "../notes/datetime";
|
||||
import { toastOnFailure } from "../composables/useAction";
|
||||
import { counted } from "../counted";
|
||||
|
||||
// Opt-in server sync for the desktop app. Being UNLINKED is the normal resting
|
||||
// state, not an incomplete setup — the app is local-first and fully usable having
|
||||
@@ -313,14 +314,15 @@ function showOutcome(outcome: SyncOutcome) {
|
||||
const parts: string[] = [];
|
||||
if (sent > 0) parts.push(`sent ${sent}`);
|
||||
const up = outcome.push.uploaded;
|
||||
if (up > 0) parts.push(`uploaded ${up} file${up === 1 ? "" : "s"}`);
|
||||
if (up > 0) parts.push(`uploaded ${counted(up, "file", "files")}`);
|
||||
if (received > 0) parts.push(`received ${received}`);
|
||||
if (blobs > 0) parts.push(`${blobs} attachment${blobs === 1 ? "" : "s"}`);
|
||||
if (blobs > 0) parts.push(counted(blobs, "attachment", "attachments"));
|
||||
lastResult.value = parts.length ? `Synced — ${parts.join(", ")}.` : "Already up to date.";
|
||||
// Attachments that didn't arrive are retried next sync, so this is a note, not
|
||||
// an error — but saying nothing would leave a missing image unexplained.
|
||||
if (outcome.pull.blobs_failed > 0) {
|
||||
lastResult.value += ` ${outcome.pull.blobs_failed} attachment(s) didn't download — they'll retry on the next sync.`;
|
||||
const failed = counted(outcome.pull.blobs_failed, "attachment", "attachments");
|
||||
lastResult.value += ` ${failed} didn't download — they'll retry on the next sync.`;
|
||||
}
|
||||
// Rejections are the server refusing a specific change — surfaced, never
|
||||
// swallowed, because only the person can resolve them. The background cycle does
|
||||
@@ -328,8 +330,8 @@ function showOutcome(outcome: SyncOutcome) {
|
||||
// A file refused for good is listed once, here, and not retried; the editor says so
|
||||
// on the file itself from then on.
|
||||
const problems: string[] = [];
|
||||
if (outcome.push.rejected > 0) problems.push(`${outcome.push.rejected} change(s) the server wouldn't accept`);
|
||||
if (outcome.push.upload_failed > 0) problems.push(`${outcome.push.upload_failed} file(s) didn't upload`);
|
||||
if (outcome.push.rejected > 0) problems.push(`${counted(outcome.push.rejected, "change", "changes")} the server wouldn't accept`);
|
||||
if (outcome.push.upload_failed > 0) problems.push(`${counted(outcome.push.upload_failed, "file", "files")} didn't upload`);
|
||||
syncError.value = problems.length ? `${problems.join(" and ")}: ${outcome.push.errors.join("; ")}` : "";
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user