v4: every message named by an id carries its English in the code — the emails and the four pages that share their words called Lingui with a bare id (i18n._("digest.community"), <Trans id="digest.community" />), so their English lived only in a hand-kept en.po, extract couldn't run with --overwrite, and a page reading the code couldn't tell what it said. The emails now pass descriptors, i18n._({ id, message, values }), the form Lingui reads without a macro, as the server needs. The fifteen messages the digest shares with the community page, Settings, the unsubscribe page and the avatar menu are declared once, as /*i18n*/ descriptors in shared/digest-messages.ts, which those pages translate with useLingui()'s i18n instead of the global one. lingui.config.ts scans shared/ too, and i18n:extract runs with --overwrite, so en.po is written from the code. Every other message already used the macros. The compiled catalogs are unchanged, key for key, in both languages. 272 backend tests and 677 frontend tests pass, and deno check, tsc, eslint, doc:lint and i18n:compile --strict are clean.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
@@ -12,6 +12,7 @@
|
||||
import { EMAIL_LOCALE, OG_SITE_NAME } from "../config.ts";
|
||||
import type { DigestCadence } from "../../shared/settings.ts";
|
||||
import { i18n } from "./i18n.ts";
|
||||
import { DIGEST_MESSAGES } from "../../shared/digest-messages.ts";
|
||||
import {
|
||||
EmailKit,
|
||||
type EmailLook,
|
||||
@@ -127,17 +128,33 @@ const ACTIVITY_ORDER: ActivityKind[] = [
|
||||
function countLabel(kind: ActivityKind, n: number): string {
|
||||
switch (kind) {
|
||||
case "upvotes":
|
||||
return i18n._("digest.count.upvotes", { n });
|
||||
return i18n._({ ...DIGEST_MESSAGES.countUpvotes, values: { n } });
|
||||
case "followers":
|
||||
return i18n._("email.digest.count.followers", { n });
|
||||
return i18n._({
|
||||
id: "email.digest.count.followers",
|
||||
message: "{n, plural, one {new follower} other {new followers}}",
|
||||
values: { n },
|
||||
});
|
||||
case "comments":
|
||||
return i18n._("digest.count.comments", { n });
|
||||
return i18n._({ ...DIGEST_MESSAGES.countComments, values: { n } });
|
||||
case "mentions":
|
||||
return i18n._("email.digest.count.mentions", { n });
|
||||
return i18n._({
|
||||
id: "email.digest.count.mentions",
|
||||
message: "{n, plural, one {mention} other {mentions}}",
|
||||
values: { n },
|
||||
});
|
||||
case "likes":
|
||||
return i18n._("email.digest.count.likes", { n });
|
||||
return i18n._({
|
||||
id: "email.digest.count.likes",
|
||||
message: "{n, plural, one {comment like} other {comment likes}}",
|
||||
values: { n },
|
||||
});
|
||||
case "playlistFollows":
|
||||
return i18n._("email.digest.count.playlistFollows", { n });
|
||||
return i18n._({
|
||||
id: "email.digest.count.playlistFollows",
|
||||
message: "{n, plural, one {playlist follow} other {playlist follows}}",
|
||||
values: { n },
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
@@ -153,36 +170,87 @@ function highlightSentence(
|
||||
const values = { who, target };
|
||||
switch (kind) {
|
||||
case "mentionComment":
|
||||
return i18n._("email.digest.hl.mentionComment", values);
|
||||
return i18n._({
|
||||
id: "email.digest.hl.mentionComment",
|
||||
message: "{who} mentioned you in a comment on {target}",
|
||||
values,
|
||||
});
|
||||
case "mentionDump":
|
||||
case "mentionChat":
|
||||
return i18n._("email.digest.hl.mentionIn", values);
|
||||
return i18n._({
|
||||
id: "email.digest.hl.mentionIn",
|
||||
message: "{who} mentioned you in {target}",
|
||||
values,
|
||||
});
|
||||
case "mentionPlaylist":
|
||||
return i18n._("email.digest.hl.mentionPlaylist", values);
|
||||
return i18n._({
|
||||
id: "email.digest.hl.mentionPlaylist",
|
||||
message: "{who} mentioned you in the playlist {target}",
|
||||
values,
|
||||
});
|
||||
case "mentionEveryone":
|
||||
return i18n._("email.digest.hl.mentionEveryone", values);
|
||||
return i18n._({
|
||||
id: "email.digest.hl.mentionEveryone",
|
||||
message: "{who} mentioned everyone in {target}",
|
||||
values,
|
||||
});
|
||||
case "comment":
|
||||
return i18n._("email.digest.hl.comment", values);
|
||||
return i18n._({
|
||||
id: "email.digest.hl.comment",
|
||||
message: "{who} commented on your dump {target}",
|
||||
values,
|
||||
});
|
||||
case "playlistFollow":
|
||||
return i18n._("email.digest.hl.playlistFollow", values);
|
||||
return i18n._({
|
||||
id: "email.digest.hl.playlistFollow",
|
||||
message: "{who} followed your playlist {target}",
|
||||
values,
|
||||
});
|
||||
case "userFollow":
|
||||
return i18n._("email.digest.hl.userFollow", values);
|
||||
return i18n._({
|
||||
id: "email.digest.hl.userFollow",
|
||||
message: "{who} started following you",
|
||||
values,
|
||||
});
|
||||
case "commentLike":
|
||||
return i18n._("email.digest.hl.commentLike", values);
|
||||
return i18n._({
|
||||
id: "email.digest.hl.commentLike",
|
||||
message: "{who} liked your comment on {target}",
|
||||
values,
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
const byLine = (username: string) => i18n._("email.digest.by", { username });
|
||||
const byLine = (username: string) =>
|
||||
i18n._({
|
||||
id: "email.digest.by",
|
||||
message: "by @{username}",
|
||||
values: { username },
|
||||
});
|
||||
const addedToLine = (playlist: string) =>
|
||||
i18n._("email.digest.addedTo", { playlist });
|
||||
const uploadLabel = () => i18n._("email.digest.upload");
|
||||
i18n._({
|
||||
id: "email.digest.addedTo",
|
||||
message: "added to {playlist}",
|
||||
values: { playlist },
|
||||
});
|
||||
const uploadLabel = () =>
|
||||
i18n._({ id: "email.digest.upload", message: "upload" });
|
||||
const invitedLine = (inviter: string, reader: string) =>
|
||||
inviter === reader
|
||||
? i18n._("digest.invitedByYou")
|
||||
: i18n._("digest.invitedBy", { username: inviter });
|
||||
const andMoreLine = (n: number) => i18n._("email.digest.andMore", { n });
|
||||
? i18n._(DIGEST_MESSAGES.invitedByYou)
|
||||
: i18n._({ ...DIGEST_MESSAGES.invitedBy, values: { username: inviter } });
|
||||
const andMoreLine = (n: number) =>
|
||||
i18n._({
|
||||
id: "email.digest.andMore",
|
||||
message: "and {n} more",
|
||||
values: { n },
|
||||
});
|
||||
const playlistMeta = (owner: string, n: number) =>
|
||||
i18n._("email.digest.playlistMeta", { username: owner, n });
|
||||
i18n._({
|
||||
id: "email.digest.playlistMeta",
|
||||
message: "by @{username} · {n, plural, one {# dump} other {# dumps}}",
|
||||
values: { username: owner, n },
|
||||
});
|
||||
|
||||
/**
|
||||
* Returns the digest `content` describes, written as Markdown and drawn in
|
||||
@@ -205,7 +273,7 @@ export function renderDigestEmail(
|
||||
new Intl.DateTimeFormat(EMAIL_LOCALE, { weekday: "short" }).format(d);
|
||||
const figure = (n: number, label: string) =>
|
||||
`**${nf.format(n)}** ${escMd(label)}`;
|
||||
const yours = i18n._("email.digest.yours");
|
||||
const yours = i18n._({ id: "email.digest.yours", message: "yours" });
|
||||
/** A dump as a list item: its thumbnail, title, pill and detail. */
|
||||
const dumpItem = (d: DigestDump, bullet: string, image = true) =>
|
||||
`${bullet} ${image && d.thumbnail ? ` ` : ""}${
|
||||
@@ -223,16 +291,26 @@ export function renderDigestEmail(
|
||||
const md: string[] = [
|
||||
`*${
|
||||
escMd(
|
||||
`${monthly ? i18n._("digest.monthly") : i18n._("digest.weekly")} · ${
|
||||
range(true)
|
||||
}`,
|
||||
`${
|
||||
monthly
|
||||
? i18n._(DIGEST_MESSAGES.monthly)
|
||||
: i18n._(DIGEST_MESSAGES.weekly)
|
||||
} · ${range(true)}`,
|
||||
)
|
||||
}*`,
|
||||
`# ${
|
||||
escMd(
|
||||
monthly
|
||||
? i18n._("email.digest.monthly.title", { site: OG_SITE_NAME })
|
||||
: i18n._("email.digest.weekly.title", { username: content.reader }),
|
||||
? i18n._({
|
||||
id: "email.digest.monthly.title",
|
||||
message: "The month on {site}",
|
||||
values: { site: OG_SITE_NAME },
|
||||
})
|
||||
: i18n._({
|
||||
id: "email.digest.weekly.title",
|
||||
message: "Your week, {username}",
|
||||
values: { username: content.reader },
|
||||
}),
|
||||
)
|
||||
}`,
|
||||
];
|
||||
@@ -240,7 +318,7 @@ export function renderDigestEmail(
|
||||
const { activity, network, community } = content;
|
||||
if (activity) {
|
||||
md.push(
|
||||
`## ${escMd(i18n._("email.digest.forYou"))}`,
|
||||
`## ${escMd(i18n._({ id: "email.digest.forYou", message: "For you" }))}`,
|
||||
ACTIVITY_ORDER.filter((k) => (activity.counts[k] ?? 0) > 0)
|
||||
.map((k) =>
|
||||
figure(activity.counts[k]!, countLabel(k, activity.counts[k]!))
|
||||
@@ -249,7 +327,7 @@ export function renderDigestEmail(
|
||||
activity.highlights.map((h) => {
|
||||
const target = h.kind === "mentionChat" || h.kind === "mentionEveryone"
|
||||
? mdLink(
|
||||
i18n._("email.digest.theChat"),
|
||||
i18n._({ id: "email.digest.theChat", message: "the chat" }),
|
||||
content.links.chat,
|
||||
)
|
||||
: h.target?.url
|
||||
@@ -264,7 +342,12 @@ export function renderDigestEmail(
|
||||
}).join("\n"),
|
||||
`${
|
||||
mdLink(
|
||||
i18n._("email.digest.seeAll", { n: activity.total }),
|
||||
i18n._({
|
||||
id: "email.digest.seeAll",
|
||||
message:
|
||||
"{n, plural, one {See the notification} other {See all # notifications}}",
|
||||
values: { n: activity.total },
|
||||
}),
|
||||
content.links.notifications,
|
||||
)
|
||||
} →`,
|
||||
@@ -273,13 +356,24 @@ export function renderDigestEmail(
|
||||
|
||||
if (network) {
|
||||
md.push(
|
||||
`## ${escMd(i18n._("email.digest.network"))}`,
|
||||
`## ${
|
||||
escMd(
|
||||
i18n._({
|
||||
id: "email.digest.network",
|
||||
message: "From people you follow",
|
||||
}),
|
||||
)
|
||||
}`,
|
||||
network.dumps.map((d) => dumpItem(d, "-")).join("\n"),
|
||||
);
|
||||
if (network.more > 0) {
|
||||
md.push(`${
|
||||
mdLink(
|
||||
i18n._("email.digest.more", { n: network.more }),
|
||||
i18n._({
|
||||
id: "email.digest.more",
|
||||
message: "{n} more on your Followed feed",
|
||||
values: { n: network.more },
|
||||
}),
|
||||
content.links.followed,
|
||||
)
|
||||
} →`);
|
||||
@@ -291,7 +385,9 @@ export function renderDigestEmail(
|
||||
md.push(
|
||||
`## ${
|
||||
escMd(
|
||||
monthly ? i18n._("digest.topMonth") : i18n._("digest.topWeek"),
|
||||
monthly
|
||||
? i18n._(DIGEST_MESSAGES.topMonth)
|
||||
: i18n._(DIGEST_MESSAGES.topWeek),
|
||||
)
|
||||
}`,
|
||||
);
|
||||
@@ -307,20 +403,23 @@ export function renderDigestEmail(
|
||||
if (community) {
|
||||
const { dumps, upvotes, comments } = community.totals;
|
||||
md.push(
|
||||
`## ${escMd(i18n._("digest.community"))}`,
|
||||
`## ${escMd(i18n._(DIGEST_MESSAGES.community))}`,
|
||||
);
|
||||
if (monthly) {
|
||||
md.push(
|
||||
[
|
||||
figure(
|
||||
dumps,
|
||||
i18n._("digest.stat.dumps", { n: dumps }),
|
||||
i18n._({ ...DIGEST_MESSAGES.statDumps, values: { n: dumps } }),
|
||||
),
|
||||
figure(upvotes, countLabel("upvotes", upvotes)),
|
||||
figure(comments, countLabel("comments", comments)),
|
||||
figure(
|
||||
community.chatMessages,
|
||||
i18n._("digest.stat.chat", { n: community.chatMessages }),
|
||||
i18n._({
|
||||
...DIGEST_MESSAGES.statChat,
|
||||
values: { n: community.chatMessages },
|
||||
}),
|
||||
),
|
||||
].join(" · "),
|
||||
);
|
||||
@@ -329,7 +428,7 @@ export function renderDigestEmail(
|
||||
md.push(
|
||||
`### ${
|
||||
escMd(
|
||||
i18n._("digest.newMembers"),
|
||||
i18n._(DIGEST_MESSAGES.newMembers),
|
||||
)
|
||||
}`,
|
||||
[
|
||||
@@ -348,7 +447,7 @@ export function renderDigestEmail(
|
||||
}
|
||||
if (community.playlists.length > 0) {
|
||||
md.push(
|
||||
`### ${escMd(i18n._("digest.newPlaylists"))}`,
|
||||
`### ${escMd(i18n._(DIGEST_MESSAGES.newPlaylists))}`,
|
||||
[
|
||||
...community.playlists.map((p) =>
|
||||
`- ${mdLink(p.title, p.url)} · ${
|
||||
@@ -363,12 +462,21 @@ export function renderDigestEmail(
|
||||
}
|
||||
if (!monthly && community.chatMessages > 0) {
|
||||
md.push(
|
||||
`### ${escMd(i18n._("email.digest.inChat"))}`,
|
||||
`### ${
|
||||
escMd(i18n._({ id: "email.digest.inChat", message: "In the chat" }))
|
||||
}`,
|
||||
`${
|
||||
escMd(i18n._("email.digest.chatWeek", { n: community.chatMessages }))
|
||||
escMd(
|
||||
i18n._({
|
||||
id: "email.digest.chatWeek",
|
||||
message:
|
||||
"{n, plural, one {# message this week} other {# messages this week}}",
|
||||
values: { n: community.chatMessages },
|
||||
}),
|
||||
)
|
||||
} · ${
|
||||
mdLink(
|
||||
i18n._("email.digest.openChat"),
|
||||
i18n._({ id: "email.digest.openChat", message: "Open the chat" }),
|
||||
content.links.chat,
|
||||
)
|
||||
}`,
|
||||
@@ -377,13 +485,21 @@ export function renderDigestEmail(
|
||||
if (!monthly && dumps + upvotes + comments > 0) {
|
||||
md.push(
|
||||
`*${
|
||||
escMd(i18n._("email.digest.totals", { dumps, upvotes, comments }))
|
||||
escMd(i18n._({
|
||||
id: "email.digest.totals",
|
||||
message:
|
||||
"{dumps, plural, one {# dump} other {# dumps}}, {upvotes, plural, one {# upvote} other {# upvotes}} and {comments, plural, one {# comment} other {# comments}}.",
|
||||
values: { dumps, upvotes, comments },
|
||||
}))
|
||||
}*`,
|
||||
);
|
||||
}
|
||||
md.push(`${
|
||||
mdLink(
|
||||
i18n._("email.digest.communityPage"),
|
||||
i18n._({
|
||||
id: "email.digest.communityPage",
|
||||
message: "See it all on the community page",
|
||||
}),
|
||||
content.links.community,
|
||||
)
|
||||
} →`);
|
||||
@@ -393,17 +509,27 @@ export function renderDigestEmail(
|
||||
"---",
|
||||
escMd(
|
||||
monthly
|
||||
? i18n._("email.digest.monthly.reason")
|
||||
: i18n._("email.digest.weekly.reason"),
|
||||
? i18n._({
|
||||
id: "email.digest.monthly.reason",
|
||||
message:
|
||||
"You're getting the monthly recap because it's on in your settings.",
|
||||
})
|
||||
: i18n._({
|
||||
id: "email.digest.weekly.reason",
|
||||
message:
|
||||
"You're getting the weekly digest because it's on in your settings.",
|
||||
}),
|
||||
),
|
||||
`${
|
||||
mdLink(
|
||||
monthly ? i18n._("digest.monthly.off") : i18n._("digest.weekly.off"),
|
||||
monthly
|
||||
? i18n._(DIGEST_MESSAGES.monthlyOff)
|
||||
: i18n._(DIGEST_MESSAGES.weeklyOff),
|
||||
content.links.unsubscribe,
|
||||
)
|
||||
} · ${
|
||||
mdLink(
|
||||
i18n._("email.settings"),
|
||||
i18n._({ id: "email.settings", message: "Settings" }),
|
||||
content.links.settings,
|
||||
)
|
||||
}`,
|
||||
@@ -411,13 +537,15 @@ export function renderDigestEmail(
|
||||
|
||||
return new EmailKit(look).render({
|
||||
subject: monthly
|
||||
? i18n._("email.digest.monthly.subject", {
|
||||
site: OG_SITE_NAME,
|
||||
range: range(false),
|
||||
? i18n._({
|
||||
id: "email.digest.monthly.subject",
|
||||
message: "The month on {site}, {range}",
|
||||
values: { site: OG_SITE_NAME, range: range(false) },
|
||||
})
|
||||
: i18n._("email.digest.weekly.subject", {
|
||||
site: OG_SITE_NAME,
|
||||
range: range(false),
|
||||
: i18n._({
|
||||
id: "email.digest.weekly.subject",
|
||||
message: "Your week on {site}, {range}",
|
||||
values: { site: OG_SITE_NAME, range: range(false) },
|
||||
}),
|
||||
preheader: preheader(content),
|
||||
markdown: md.join("\n\n"),
|
||||
@@ -438,7 +566,14 @@ function preheader(content: DigestContent): string {
|
||||
const n = (content.network?.dumps.length ?? 0) +
|
||||
(content.network?.more ?? 0);
|
||||
if (n > 0) {
|
||||
parts.push(i18n._("email.digest.pre.network", { n }));
|
||||
parts.push(
|
||||
i18n._({
|
||||
id: "email.digest.pre.network",
|
||||
message:
|
||||
"{n, plural, one {# new dump from people you follow} other {# new dumps from people you follow}}",
|
||||
values: { n },
|
||||
}),
|
||||
);
|
||||
}
|
||||
} else {
|
||||
const members = (content.community?.members.length ?? 0) +
|
||||
@@ -447,18 +582,42 @@ function preheader(content: DigestContent): string {
|
||||
(content.community?.playlistsMore ?? 0);
|
||||
if (content.top.length > 0) {
|
||||
parts.push(
|
||||
i18n._("email.digest.pre.topMonth", { n: content.top.length }),
|
||||
i18n._({
|
||||
id: "email.digest.pre.topMonth",
|
||||
message:
|
||||
"{n, plural, one {the most upvoted dump of the month} other {the # most upvoted dumps of the month}}",
|
||||
values: { n: content.top.length },
|
||||
}),
|
||||
);
|
||||
}
|
||||
if (members > 0) {
|
||||
parts.push(i18n._("email.digest.pre.members", { n: members }));
|
||||
parts.push(
|
||||
i18n._({
|
||||
id: "email.digest.pre.members",
|
||||
message: "{n, plural, one {# new member} other {# new members}}",
|
||||
values: { n: members },
|
||||
}),
|
||||
);
|
||||
}
|
||||
if (playlists > 0) {
|
||||
parts.push(i18n._("email.digest.pre.playlists", { n: playlists }));
|
||||
parts.push(
|
||||
i18n._({
|
||||
id: "email.digest.pre.playlists",
|
||||
message: "{n, plural, one {# new playlist} other {# new playlists}}",
|
||||
values: { n: playlists },
|
||||
}),
|
||||
);
|
||||
}
|
||||
}
|
||||
if (parts.length === 0 && content.top.length > 0) {
|
||||
parts.push(i18n._("email.digest.pre.topWeek", { n: content.top.length }));
|
||||
parts.push(
|
||||
i18n._({
|
||||
id: "email.digest.pre.topWeek",
|
||||
message:
|
||||
"{n, plural, one {the most upvoted dump of the week} other {the # most upvoted dumps of the week}}",
|
||||
values: { n: content.top.length },
|
||||
}),
|
||||
);
|
||||
}
|
||||
if (parts.length === 0) return "";
|
||||
const line = new Intl.ListFormat(EMAIL_LOCALE, { type: "conjunction" })
|
||||
|
||||
@@ -1,12 +1,13 @@
|
||||
/**
|
||||
* Lets the emails speak the instance's language from the same catalogs as
|
||||
* the interface. A template names each message by its id, as
|
||||
* `i18n._("email.reset.title", values)`, and its words, English included,
|
||||
* are written in `locales/<lang>.po` only. `lingui extract` lists the ids it
|
||||
* finds there beside the interface's messages, and `lingui compile` turns
|
||||
* the catalogs into the `locales/<lang>.mjs` imported here, failing when a
|
||||
* language lacks one. The ids are named rather than hashed from the English,
|
||||
* since the server runs no Lingui macro.
|
||||
* the interface. A template passes each message as a descriptor holding its
|
||||
* id and its English, as `i18n._({ id: "email.reset.title", message: "Reset
|
||||
* your password" })`, or one of `shared/digest-messages.ts`, which a page
|
||||
* says too. `lingui extract` reads them into `locales/<lang>.po` beside the
|
||||
* interface's messages, and `lingui compile` turns the catalogs into the
|
||||
* `locales/<lang>.mjs` imported here, failing when a language lacks one. The
|
||||
* ids are named rather than hashed from the English, since the server runs
|
||||
* no Lingui macro.
|
||||
*
|
||||
* @module
|
||||
*/
|
||||
|
||||
@@ -26,23 +26,67 @@ export function renderPasswordResetEmail(
|
||||
hours: number,
|
||||
): RenderedEmail {
|
||||
const markdown = [
|
||||
`# ${escMd(i18n._("email.reset.title"))}`,
|
||||
escMd(i18n._("email.reset.hi", { username })),
|
||||
escMd(i18n._("email.reset.body", { site: OG_SITE_NAME, hours })),
|
||||
`# ${
|
||||
escMd(i18n._({ id: "email.reset.title", message: "Reset your password" }))
|
||||
}`,
|
||||
escMd(
|
||||
i18n._({
|
||||
id: "email.reset.hi",
|
||||
message: "Hi {username},",
|
||||
values: { username },
|
||||
}),
|
||||
),
|
||||
escMd(i18n._({
|
||||
id: "email.reset.body",
|
||||
message:
|
||||
"Someone asked to reset the password of your {site} account. The button below lets you choose a new one. It works once, for {hours, plural, one {# hour} other {# hours}}.",
|
||||
values: { site: OG_SITE_NAME, hours },
|
||||
})),
|
||||
mdLink(
|
||||
i18n._("email.reset.button"),
|
||||
i18n._({ id: "email.reset.button", message: "Reset password" }),
|
||||
resetUrl,
|
||||
),
|
||||
`*${escMd(i18n._("email.reset.fallback"))}*`,
|
||||
`*${
|
||||
escMd(
|
||||
i18n._({
|
||||
id: "email.reset.fallback",
|
||||
message:
|
||||
"If the button doesn't work, paste this link into your browser:",
|
||||
}),
|
||||
)
|
||||
}*`,
|
||||
"```\n" + resetUrl + "\n```",
|
||||
`*${escMd(i18n._("email.reset.ignore"))}*`,
|
||||
`*${
|
||||
escMd(
|
||||
i18n._({
|
||||
id: "email.reset.ignore",
|
||||
message:
|
||||
"If you didn't ask for this, ignore this email. Your password stays the same.",
|
||||
}),
|
||||
)
|
||||
}*`,
|
||||
"---",
|
||||
escMd(i18n._("email.reset.reason")),
|
||||
escMd(
|
||||
i18n._({
|
||||
id: "email.reset.reason",
|
||||
message:
|
||||
"You're getting this email because a password reset was requested for this address.",
|
||||
}),
|
||||
),
|
||||
].join("\n\n");
|
||||
|
||||
return new EmailKit(lookOf()).render({
|
||||
subject: i18n._("email.reset.subject", { site: OG_SITE_NAME }),
|
||||
preheader: i18n._("email.reset.preheader", { hours }),
|
||||
subject: i18n._({
|
||||
id: "email.reset.subject",
|
||||
message: "Reset your {site} password",
|
||||
values: { site: OG_SITE_NAME },
|
||||
}),
|
||||
preheader: i18n._({
|
||||
id: "email.reset.preheader",
|
||||
message:
|
||||
"The link works once, for {hours, plural, one {# hour} other {# hours}}.",
|
||||
values: { hours },
|
||||
}),
|
||||
markdown,
|
||||
});
|
||||
}
|
||||
|
||||
@@ -29,24 +29,52 @@ export function renderWelcomeEmail(username: string): RenderedEmail {
|
||||
? WELCOME_EMAIL_BODY
|
||||
.replaceAll("{{username}}", escMd(username))
|
||||
.replaceAll("{{site_name}}", escMd(OG_SITE_NAME))
|
||||
: i18n._("email.welcome.body", {
|
||||
site: escMd(OG_SITE_NAME),
|
||||
username: escMd(username),
|
||||
: i18n._({
|
||||
id: "email.welcome.body",
|
||||
message:
|
||||
"# Welcome to {site}!\n\nHi **{username}**,\n\nYour account has been created successfully. Welcome aboard!",
|
||||
values: { site: escMd(OG_SITE_NAME), username: escMd(username) },
|
||||
});
|
||||
const markdown = [
|
||||
body,
|
||||
mdLink(
|
||||
i18n._("email.welcome.button", { site: OG_SITE_NAME }),
|
||||
i18n._({
|
||||
id: "email.welcome.button",
|
||||
message: "Open {site}",
|
||||
values: { site: OG_SITE_NAME },
|
||||
}),
|
||||
FRONTEND_URL,
|
||||
),
|
||||
`*${escMd(i18n._("email.welcome.digests"))}*`,
|
||||
`*${
|
||||
escMd(
|
||||
i18n._({
|
||||
id: "email.welcome.digests",
|
||||
message:
|
||||
"Once a week you'll get a digest of what happened here, and once a month a recap of the most upvoted dumps. You can turn either off in your settings.",
|
||||
}),
|
||||
)
|
||||
}*`,
|
||||
"---",
|
||||
escMd(i18n._("email.welcome.reason", { site: OG_SITE_NAME })),
|
||||
escMd(
|
||||
i18n._({
|
||||
id: "email.welcome.reason",
|
||||
message:
|
||||
"You're getting this email because an account was created on {site} with this address.",
|
||||
values: { site: OG_SITE_NAME },
|
||||
}),
|
||||
),
|
||||
].join("\n\n");
|
||||
|
||||
return new EmailKit(lookOf()).render({
|
||||
subject: i18n._("email.welcome.subject", { site: OG_SITE_NAME }),
|
||||
preheader: i18n._("email.welcome.preheader"),
|
||||
subject: i18n._({
|
||||
id: "email.welcome.subject",
|
||||
message: "Welcome to {site}",
|
||||
values: { site: OG_SITE_NAME },
|
||||
}),
|
||||
preheader: i18n._({
|
||||
id: "email.welcome.preheader",
|
||||
message: "Your account is ready.",
|
||||
}),
|
||||
markdown,
|
||||
});
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user