Check component props for untranslated literals, and fail on a finding

i18n-literals checked title, aria-label, placeholder and alt on elements,
but not the same English passed to a component, so a MenuItem label or a
Popover ariaLabel written as a literal went through. It also excused a
literal that happened to be a catalogue key. That exemption is meant for
English held in a constant and translated where it renders, and a literal
written straight into a JSX attribute has no such render site. No
component passes its props through t(). And neither half of i18n:check
was run with --check, so a finding printed and the script still exited 0.

Component props are checked now, a key no longer excuses a literal in an
attribute, and both scripts run with --check. That found 28 strings
rendering English in every language: 19 already had keys and are wrapped,
and 9 are new keys in all nine catalogues. The contact editor's Save and
Saving… buttons are wrapped as well, on the same line.
This commit is contained in:
2026-09-14 08:03:12 -07:00
parent af092b3942
commit ac4c4bd267
20 changed files with 117 additions and 23 deletions
+2 -2
View File
@@ -289,7 +289,7 @@ export function Composer({ draft }: { draft: Draft }) {
<button className="btn btn-primary" onClick={sendMenu.open} aria-label={translate("Send options")}><ChevronDown size={16} /></button>
</span>
<Popover anchor={sendMenu.anchor} onClose={sendMenu.close} side="top" width={280}>
<MenuItem icon={<Send size={16} />} label={d.sendAt !== null ? "Send now instead" : "Send"} kbd={d.sendAt !== null ? undefined : "Ctrl+↵"} onClick={() => { if (d.sendAt !== null) patch({ sendAt: null }); sendMenu.close(); void doSend(); }} />
<MenuItem icon={<Send size={16} />} label={d.sendAt !== null ? translate("Send now instead") : translate("Send")} kbd={d.sendAt !== null ? undefined : "Ctrl+↵"} onClick={() => { if (d.sendAt !== null) patch({ sendAt: null }); sendMenu.close(); void doSend(); }} />
<MenuItem icon={<Clock size={16} />} label={translate("Undo window: {seconds}s", { seconds: settings.undoSendSeconds })} onClick={() => updateSettings({ undoSendSeconds: settings.undoSendSeconds >= 30 ? 0 : settings.undoSendSeconds + 5 })} />
{canSchedule && <ScheduleMenuItems maxMs={scheduleMax} onPick={scheduleFor} onCustom={() => { sendMenu.close(); setScheduleOpen(true); }} />}
</Popover>
@@ -323,7 +323,7 @@ export function Composer({ draft }: { draft: Draft }) {
</Popover>
<button className="icon-btn" onClick={moreMenu.open} aria-label={translate("More options")}><MoreVertical size={18} /></button>
<Popover anchor={moreMenu.anchor} onClose={moreMenu.close} side="top" width={260}>
<MenuItem icon={<Type size={16} />} label={d.format === "html" ? "Switch to plain text" : "Switch to rich text"} onClick={toggleFormat} />
<MenuItem icon={<Type size={16} />} label={d.format === "html" ? translate("Switch to plain text") : translate("Switch to rich text")} onClick={toggleFormat} />
<MenuItem icon={<CheckCheck size={16} />} label={translate("Request read receipt")} checked={d.requestReceipt} onClick={() => patch({ requestReceipt: !d.requestReceipt })} />
<MenuSep />
<MenuTitle>{translate("Priority")}</MenuTitle>