Merge main: the list subject stays notranslate and gains its translated fallback

Both sides of the conflict belong. The span holds a subject, which is the
sender's words and not ours to machine-translate; the text shown when
there is no subject is ours, and should follow the interface language.
This commit is contained in:
2026-08-31 14:44:30 -07:00
7 changed files with 117 additions and 13 deletions
+6 -6
View File
@@ -648,7 +648,7 @@ const Row = memo(function Row({ email: e, threadEmails, top, height, selected, f
{twoLine ? (
<div className="msg-body">
<div className="msg-line1">
<span className="msg-from truncate">
<span className="msg-from truncate notranslate" translate="no">
<span className="truncate">{who}</span>
{count > 1 && <span className="thread-count"> {count}</span>}
</span>
@@ -659,8 +659,8 @@ const Row = memo(function Row({ email: e, threadEmails, top, height, selected, f
</div>
<div className="msg-main">
{isDrafts && <span style={{ color: "var(--danger)" }}>{t("Draft")}</span>}
<span className="msg-subject">{e.subject || t("(no subject)")}</span>
{showPreview && <span className="msg-preview">{latest.preview}</span>}
<span className="msg-subject notranslate" translate="no">{e.subject || t("(no subject)")}</span>
{showPreview && <span className="msg-preview notranslate" translate="no">{latest.preview}</span>}
<button className={`msg-star ${starred ? "on" : ""}`} style={{ marginLeft: "auto" }} onClick={(ev) => { ev.stopPropagation(); onStar(e.id, !starred); }} aria-label={t("Star")}>
<Star size={16} fill={starred ? "currentColor" : "none"} />
</button>
@@ -669,15 +669,15 @@ const Row = memo(function Row({ email: e, threadEmails, top, height, selected, f
</div>
) : (
<>
<span className="msg-from" title={who}>
<span className="msg-from notranslate" translate="no" title={who}>
<span className="truncate">{who}</span>
{count > 1 && <span className="thread-count">{count}</span>}
</span>
<span className="msg-main">
{isDrafts && <span style={{ color: "var(--danger)", flex: "0 0 auto" }}>{t("Draft")}</span>}
{rowLabels.length > 0 && <span className="msg-labels">{rowLabels.map((l) => <span key={l.keyword} className="tag" style={{ background: l.color }}>{l.name}</span>)}</span>}
<span className="msg-subject">{e.subject || t("(no subject)")}</span>
{showPreview && <span className="msg-preview">{latest.preview}</span>}
<span className="msg-subject notranslate" translate="no">{e.subject || t("(no subject)")}</span>
{showPreview && <span className="msg-preview notranslate" translate="no">{latest.preview}</span>}
</span>
<span className="msg-meta">
{(answered || forwarded) && <span className="msg-answered" title={answered ? t("Replied") : t("Forwarded")}>{answered ? <Reply size={14} /> : <Forward size={14} />}</span>}
+1 -1
View File
@@ -149,7 +149,7 @@ export const MessageView = memo(function MessageView({ email: e, expanded, wasUn
<Avatar who={from ?? null} />
<div className="who">
<div className="from" onContextMenu={(ev) => from && addrMenu.open(ev, from)}>
<span className="addr">{displayName(from)}</span>
<span className="addr notranslate" translate="no">{displayName(from)}</span>
{/* An address, not a sentence. */}
{expanded && from && <span className="email addr notranslate" translate="no">&lt;{from.email}&gt;</span>}
{isHighPriority && <span className="tag" style={{ background: "var(--danger)" }}>{translate("Important")}</span>}
+1 -1
View File
@@ -260,7 +260,7 @@ export function ThreadView({ threadId, mailboxId, onBack, actions, onNavigate, h
<div className="thread-scroll" ref={scrollRef}>
<div className="thread-subject">
<div className="grow">
<h1>{subject}</h1>
<h1 className="notranslate" translate="no">{subject}</h1>
{(threadLabels.length > 0 || threadMailboxes.length > 0) && (
<div className="labels">
{threadMailboxes.map((n) => <span key={n} className="chip">{n}</span>)}