Unify workspace dropdowns; Update layouts (#458)

Unified Account and Workspace drop-downs; Layout updates, now header lives within the PageBody component; Sidebars now use floating variant
This commit is contained in:
Giancarlo Buomprisco
2026-03-11 14:45:42 +08:00
committed by GitHub
parent ca585e09be
commit 4bc8448a1d
530 changed files with 14398 additions and 11198 deletions

View File

@@ -29,11 +29,11 @@ export async function renderAccountDeleteEmail(props: Props) {
namespace,
});
const previewText = t(`${namespace}:previewText`, {
const previewText = t(`previewText`, {
productName: props.productName,
});
const subject = t(`${namespace}:subject`, {
const subject = t(`subject`, {
productName: props.productName,
});
@@ -54,27 +54,27 @@ export async function renderAccountDeleteEmail(props: Props) {
<EmailContent>
<Text className="text-[16px] leading-[24px] text-[#242424]">
{t(`${namespace}:hello`)}
{t(`hello`)}
</Text>
<Text className="text-[16px] leading-[24px] text-[#242424]">
{t(`${namespace}:paragraph1`, {
{t(`paragraph1`, {
productName: props.productName,
})}
</Text>
<Text className="text-[16px] leading-[24px] text-[#242424]">
{t(`${namespace}:paragraph2`)}
{t(`paragraph2`)}
</Text>
<Text className="text-[16px] leading-[24px] text-[#242424]">
{t(`${namespace}:paragraph3`, {
{t(`paragraph3`, {
productName: props.productName,
})}
</Text>
<Text className="text-[16px] leading-[24px] text-[#242424]">
{t(`${namespace}:paragraph4`, {
{t(`paragraph4`, {
productName: props.productName,
})}
</Text>

View File

@@ -42,24 +42,24 @@ export async function renderInviteEmail(props: Props) {
});
const previewText = `Join ${props.invitedUserEmail} on ${props.productName}`;
const subject = t(`${namespace}:subject`);
const subject = t(`subject`);
const heading = t(`${namespace}:heading`, {
const heading = t(`heading`, {
teamName: props.teamName,
productName: props.productName,
});
const hello = t(`${namespace}:hello`, {
const hello = t(`hello`, {
invitedUserEmail: props.invitedUserEmail,
});
const mainText = t(`${namespace}:mainText`, {
const mainText = t(`mainText`, {
inviter: props.inviter,
teamName: props.teamName,
productName: props.productName,
});
const joinTeam = t(`${namespace}:joinTeam`, {
const joinTeam = t(`joinTeam`, {
teamName: props.teamName,
});
@@ -108,7 +108,7 @@ export async function renderInviteEmail(props: Props) {
</Section>
<Text className="text-[16px] leading-[24px] text-[#242424]">
{t(`${namespace}:copyPasteLink`)}{' '}
{t(`copyPasteLink`)}{' '}
<Link href={props.link} className="text-blue-600 no-underline">
{props.link}
</Link>
@@ -117,7 +117,7 @@ export async function renderInviteEmail(props: Props) {
<Hr className="mx-0 my-[26px] w-full border border-solid border-[#eaeaea]" />
<Text className="text-[12px] leading-[24px] text-[#666666]">
{t(`${namespace}:invitationIntendedFor`, {
{t(`invitationIntendedFor`, {
invitedUserEmail: props.invitedUserEmail,
})}
</Text>

View File

@@ -32,22 +32,22 @@ export async function renderOtpEmail(props: Props) {
namespace,
});
const subject = t(`${namespace}:subject`, {
const subject = t(`subject`, {
productName: props.productName,
});
const previewText = subject;
const heading = t(`${namespace}:heading`, {
const heading = t(`heading`, {
productName: props.productName,
});
const otpText = t(`${namespace}:otpText`, {
const otpText = t(`otpText`, {
otp: props.otp,
});
const mainText = t(`${namespace}:mainText`);
const footerText = t(`${namespace}:footerText`);
const mainText = t(`mainText`);
const footerText = t(`footerText`);
const html = await render(
<Html>

View File

@@ -1,32 +1,47 @@
import { createI18nSettings } from '@kit/i18n';
import { initializeServerI18n } from '@kit/i18n/server';
import type { AbstractIntlMessages } from 'next-intl';
import { createTranslator } from 'next-intl';
export function initializeEmailI18n(params: {
export async function initializeEmailI18n(params: {
language: string | undefined;
namespace: string;
}) {
const language =
params.language ?? process.env.NEXT_PUBLIC_DEFAULT_LOCALE ?? 'en';
const language = params.language ?? 'en';
return initializeServerI18n(
createI18nSettings({
try {
// Load the translation messages for the specified namespace
const messages = (await import(
`../locales/${language}/${params.namespace}.json`
)) as AbstractIntlMessages;
// Create a translator function with the messages
const translator = createTranslator({
locale: language,
messages,
});
// Type-cast to make it compatible with the i18next API
const t = translator as unknown as (
key: string,
values?: Record<string, unknown>,
) => string;
// Return an object compatible with the i18next API
return {
t,
language,
languages: [language],
namespaces: params.namespace,
}),
async (language, namespace) => {
try {
const data = await import(`../locales/${language}/${namespace}.json`);
};
} catch (error) {
console.log(
`Error loading i18n file: locales/${language}/${params.namespace}.json`,
error,
);
return data as Record<string, string>;
} catch (error) {
console.log(
`Error loading i18n file: locales/${language}/${namespace}.json`,
error,
);
// Return a fallback translator that returns the key as-is
const t = (key: string) => key;
return {};
}
},
);
return {
t,
language,
};
}
}

View File

@@ -1,9 +1,9 @@
{
"subject": "We have deleted your {{productName}} account",
"previewText": "We have deleted your {{productName}} account",
"hello": "Hello {{displayName}},",
"paragraph1": "This is to confirm that we have processed your request to delete your account with {{productName}}.",
"subject": "We have deleted your {productName} account",
"previewText": "We have deleted your {productName} account",
"hello": "Hello {displayName},",
"paragraph1": "This is to confirm that we have processed your request to delete your account with {productName}.",
"paragraph2": "We're sorry to see you go. Please note that this action is irreversible, and we'll make sure to delete all of your data from our systems.",
"paragraph3": "We thank you again for using {{productName}}.",
"paragraph4": "The {{productName}} Team"
"paragraph3": "We thank you again for using {productName}.",
"paragraph4": "The {productName} Team"
}

View File

@@ -1,9 +1,9 @@
{
"subject": "You have been invited to join a team",
"heading": "Join {{teamName}} on {{productName}}",
"hello": "Hello {{invitedUserEmail}},",
"mainText": "<strong>{{inviter}}</strong> has invited you to the <strong>{{teamName}}</strong> team on <strong>{{productName}}</strong>.",
"joinTeam": "Join {{teamName}}",
"heading": "Join {teamName} on {productName}",
"hello": "Hello {invitedUserEmail},",
"mainText": "<strong>{inviter}</strong> has invited you to the <strong>{teamName}</strong> team on <strong>{productName}</strong>.",
"joinTeam": "Join {teamName}",
"copyPasteLink": "or copy and paste this URL into your browser:",
"invitationIntendedFor": "This invitation is intended for {{invitedUserEmail}}."
"invitationIntendedFor": "This invitation is intended for {invitedUserEmail}."
}

View File

@@ -1,7 +1,7 @@
{
"subject": "One-time password for {{productName}}",
"heading": "One-time password for {{productName}}",
"otpText": "Your one-time password is: {{otp}}",
"subject": "One-time password for {productName}",
"heading": "One-time password for {productName}",
"otpText": "Your one-time password is: {otp}",
"footerText": "Please enter the one-time password in the app to continue.",
"mainText": "You're receiving this email because you need to verify your identity using a one-time password."
}