Call the app by its name in every sentence that names it (#406)

APP_NAME renames an instance, but only the sign-in page, the title bar and
a few headings used it. Two dozen sentences wrote "ihasmail" into
themselves, so a renamed instance still told people to keep an ihasmail
tab open and offered to open mail links "in ihasmail".

Those sentences now take the name as {app}, which also lets a translator
put it where their language wants it. brand.ts grew useAppName() for
components and currentAppName() for the few places that build strings
outside React.

Left as they are: the Files folder "ihasmail", the Sieve script
"ihasmail" and ihasmail.org. Those name things a person can go and look
at, and renaming them would rename real data.

All nine catalogues keep their translations: the name inside each one
became the placeholder. Three of the strings had no translation before
and still fall back to English.

A test walks the sources and the catalogues so a new sentence can't
hard-code the name again.
This commit is contained in:
jcoffey
2026-09-19 14:27:34 -07:00
committed by GitHub
parent 8a7ff5d42f
commit 9e3844e94a
25 changed files with 346 additions and 225 deletions
+7 -4
View File
@@ -1,4 +1,5 @@
import { useState } from "react";
import { currentAppName, useAppName } from "@/lib/brand";
import { BadgeCheck, ShieldAlert, ShieldQuestion, ShieldX } from "lucide-react";
import { formatFingerprint } from "@/lib/smime/x509";
import type { SignatureState } from "@/lib/smime/useSignature";
@@ -22,6 +23,7 @@ import { formatFullDate } from "@/lib/format";
* verified against itself.
*/
export function SignatureBanner({ state }: { state: SignatureState }) {
const appName = useAppName();
const [open, setOpen] = useState(false);
if (state.status !== "done") return null;
const { crypto, trust, previous, warnings } = state.report;
@@ -31,7 +33,7 @@ export function SignatureBanner({ state }: { state: SignatureState }) {
return (
<Banner tone="quiet" icon={<ShieldQuestion size={16} />}>
<span className="grow">
{t("This message is signed, and ihasmail could not check the signature.")} {explain(crypto.reason)}
{t("This message is signed, and {app} could not check the signature.", { app: appName })} {explain(crypto.reason)}
{crypto.detail && <span className="hint"> {crypto.detail}</span>}
</span>
</Banner>
@@ -77,7 +79,7 @@ export function SignatureBanner({ state }: { state: SignatureState }) {
) : (
<>
{tNode("Signed by {name}, seen here for the first time.", { name: <strong className="notranslate" translate="no">{name}</strong> })}{" "}
{t("ihasmail will tell you if a later message from this address is signed by anybody else.")}
{t("{app} will tell you if a later message from this address is signed by anybody else.", { app: appName })}
</>
)}
{warnings.includes("certificate-expired") && <> {t("The certificate has expired.")}</>}
@@ -134,11 +136,12 @@ export function SignatureBanner({ state }: { state: SignatureState }) {
/** The sayable version of why a check did not happen, or did not hold. */
function explain(reason: Reason): string {
const appName = currentAppName();
switch (reason) {
case "openpgp":
return t("It is signed with OpenPGP, and ihasmail has no way to fetch the sender's public key.");
return t("It is signed with OpenPGP, and {app} has no way to fetch the sender's public key.", { app: appName });
case "rsa-pss":
return t("It uses a signature algorithm ihasmail cannot check yet.");
return t("It uses a signature algorithm {app} cannot check yet.", { app: appName });
case "no-certificate":
return t("The signature carries no certificate that can be read.");
case "not-signed-properly":