Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
13 changes: 7 additions & 6 deletions apps/web/src/composer.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -78,18 +78,19 @@ function EditorReferencePlugin({ onReady }: { onReady: (editor: LexicalEditor) =
return null;
}

function FormatToolbar({ editor }: { editor: LexicalEditor | null }) {
function FormatToolbar({ editor, locale }: { editor: LexicalEditor | null; locale: Locale }) {
const t = (key: Parameters<typeof translate>[1]) => translate(locale, key);
const format = (kind: "bold" | "italic" | "underline") =>
editor?.dispatchCommand(FORMAT_TEXT_COMMAND, kind);
return (
<div className="compose-format" aria-label="Formatting" role="toolbar">
<Button size="icon" aria-label="Bold" onClick={() => format("bold")}>
<div className="compose-format" aria-label={t("formatting")} role="toolbar">
<Button size="icon" aria-label={t("bold")} onClick={() => format("bold")}>
<Bold size={15} />
</Button>
<Button size="icon" aria-label="Italic" onClick={() => format("italic")}>
<Button size="icon" aria-label={t("italic")} onClick={() => format("italic")}>
<Italic size={15} />
</Button>
<Button size="icon" aria-label="Underline" onClick={() => format("underline")}>
<Button size="icon" aria-label={t("underline")} onClick={() => format("underline")}>
<Underline size={15} />
</Button>
</div>
Expand Down Expand Up @@ -534,7 +535,7 @@ export function ComposePanel({
>
<Send size={16} /> {t("send")}
</Button>
<FormatToolbar editor={editor} />
<FormatToolbar editor={editor} locale={locale} />
<input
ref={fileInput}
className="compose-file-input"
Expand Down
4 changes: 4 additions & 0 deletions apps/web/src/i18n.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,10 @@ describe("translations", () => {
resetTranslationCatalogForTests();
expect(translate("en", "inbox")).toBe("Inbox");
expect(translate("es", "inbox")).toBe("Recibidos");
expect(translate("es", "accountMenu")).toBe("Menú de cuenta");
expect(translate("es", "moreActions")).toBe("Más acciones");
expect(translate("es", "trash")).toBe("Papelera");
expect(translate("es", "formatting")).toBe("Formato");
expect(Object.keys(builtinTranslations.es).sort()).toEqual(
Object.keys(builtinTranslations.en).sort(),
);
Expand Down
32 changes: 22 additions & 10 deletions apps/web/src/pages.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -189,7 +189,7 @@ function Header({
return (
<header className="topbar">
<div className="topbar-start">
<Button size="icon" aria-label="Toggle navigation" onClick={onMenu}>
<Button size="icon" aria-label={t("toggleNavigation")} onClick={onMenu}>
<Menu size={19} />
</Button>
<Brand />
Expand Down Expand Up @@ -265,7 +265,7 @@ function Header({
>
<Settings size={18} />
</Button>
<Button size="icon" aria-label="Notifications">
<Button size="icon" aria-label={t("notifications")}>
<Bell size={18} />
</Button>
<div className="locale-menu-anchor" ref={localeMenu}>
Expand Down Expand Up @@ -311,7 +311,7 @@ function Header({
<button
className="avatar"
type="button"
aria-label="Account menu"
aria-label={t("accountMenu")}
aria-haspopup="menu"
aria-expanded={showAccounts}
onClick={() => {
Expand Down Expand Up @@ -410,12 +410,14 @@ function LabelBranch({
collapsed,
onToggle,
onSelect,
t,
}: {
node: LabelTreeNode;
depth: number;
collapsed: Set<string>;
onToggle: (path: string) => void;
onSelect: (label: MailLabel) => void;
t: Translator;
}) {
const hasChildren = node.children.length > 0;
const isCollapsed = collapsed.has(node.path);
Expand All @@ -426,7 +428,7 @@ function LabelBranch({
<button
className="label-disclosure"
type="button"
aria-label={`${isCollapsed ? "Expand" : "Collapse"} ${node.name}`}
aria-label={`${t(isCollapsed ? "expandLabel" : "collapseLabel")} ${node.name}`}
aria-expanded={!isCollapsed}
onClick={() => onToggle(node.path)}
>
Expand Down Expand Up @@ -456,6 +458,7 @@ function LabelBranch({
collapsed={collapsed}
onToggle={onToggle}
onSelect={onSelect}
t={t}
/>
))}
</div>
Expand Down Expand Up @@ -514,12 +517,12 @@ function Sidebar({
<Pencil size={18} />
<span>{t("compose")}</span>
</Button>
<nav aria-label="Mailboxes" className="nav-list">
<nav aria-label={t("mailboxes")} className="nav-list">
{mailboxItems.map(([key, Icon], index) => {
const role = key === "allMail" ? "all" : key;
const mailbox = mailboxByRole.get(role as Mailbox["role"]);
const count = mailbox?.unreadCount ?? 0;
const label = mailbox?.localName || mailbox?.remoteName || t(key);
const label = t(key);
return (
<button
aria-label={label}
Expand Down Expand Up @@ -549,16 +552,18 @@ function Sidebar({
<div id="secondary-mailboxes" className="secondary-mailboxes">
{extraMailboxes.map((mailbox) => {
const Icon = extraIcons[mailbox.role as keyof typeof extraIcons];
const labelKey = mailbox.role === "junk" ? "spam" : mailbox.role;
const label = t(labelKey as TranslationKey);
return (
<button
aria-label={mailbox.localName || mailbox.remoteName}
aria-label={label}
className="nav-item"
type="button"
key={mailbox.id}
onClick={() => onMailboxSelect(mailbox)}
>
<Icon size={17} />
<span>{mailbox.localName || mailbox.remoteName}</span>
<span>{label}</span>
{mailbox.unreadCount > 0 && <strong>{mailbox.unreadCount}</strong>}
</button>
);
Expand Down Expand Up @@ -586,6 +591,7 @@ function Sidebar({
})
}
onSelect={onLabelSelect}
t={t}
/>
))}
</nav>
Expand Down Expand Up @@ -667,7 +673,7 @@ function MailToolbar({
)}
</>
)}
<Button size="icon" aria-label="More actions">
<Button size="icon" aria-label={t("moreActions")}>
<MoreHorizontal size={18} />
</Button>
</div>
Expand Down Expand Up @@ -735,6 +741,7 @@ function MessageRow({
onTrash,
onReadToggle,
onImportant,
locale,
t,
actionsEnabled,
}: {
Expand All @@ -748,6 +755,7 @@ function MessageRow({
onTrash: () => void;
onReadToggle: () => void;
onImportant: () => void;
locale: Locale;
t: Translator;
actionsEnabled: boolean;
}) {
Expand Down Expand Up @@ -835,7 +843,7 @@ function MessageRow({
{message.messageCount}
</span>
<time dateTime={message.lastMessageAt}>
{new Intl.DateTimeFormat(undefined, { month: "short", day: "numeric" }).format(
{new Intl.DateTimeFormat(locale, { month: "short", day: "numeric" }).format(
new Date(message.lastMessageAt),
)}
</time>
Expand Down Expand Up @@ -961,6 +969,7 @@ function VirtualMessageList({
onStar,
onOpen,
onAction,
locale,
t,
actionsEnabled,
}: {
Expand All @@ -970,6 +979,7 @@ function VirtualMessageList({
onStar: (id: string, starred: boolean) => void;
onOpen: (id: string) => void;
onAction: (kind: MailActionKind, ids: string[]) => void;
locale: Locale;
t: Translator;
actionsEnabled: boolean;
}) {
Expand Down Expand Up @@ -1012,6 +1022,7 @@ function VirtualMessageList({
message.id,
])
}
locale={locale}
t={t}
actionsEnabled={actionsEnabled}
/>
Expand Down Expand Up @@ -1679,6 +1690,7 @@ export function MailPage({ initialLocale = "en" }: { initialLocale?: Locale }) {
onSelect={toggleSelection}
onOpen={setActiveThreadId}
onAction={(kind, ids) => void runAction(kind, ids)}
locale={locale}
t={t}
onStar={(id, isStarred) => void runAction(isStarred ? "unstar" : "star", [id])}
actionsEnabled={canActions}
Expand Down
36 changes: 36 additions & 0 deletions apps/web/tests/inbox.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -330,6 +330,14 @@ test("live inbox virtualizes large account-scoped pages and preserves selection"
await expect(page.getByText("Sender 0", { exact: true })).toBeVisible({ timeout: 20_000 });
await expect.poll(() => Boolean(eventSocket)).toBe(true);
expect(await page.locator(".message-row").count()).toBeLessThan(100);
const visualRegressionProjects = new Set(["desktop-large", "tablet-portrait", "mobile"]);
if (visualRegressionProjects.has(testInfo.project.name)) {
await expect(page).toHaveScreenshot(`inbox-${testInfo.project.name}-en.png`, {
animations: "disabled",
caret: "hide",
maxDiffPixelRatio: 0.05,
});
}

await page.getByRole("button", { name: "Account menu" }).click();
await expect(page.getByRole("menu", { name: "Accounts" })).toContainText("Personal");
Expand All @@ -343,6 +351,34 @@ test("live inbox virtualizes large account-scoped pages and preserves selection"
);
await page.getByRole("menuitemradio", { name: "ES Spanish" }).click();
await expect(page.getByRole("button", { name: "ES", exact: true })).toBeVisible();
await expect(page.getByRole("button", { name: "Alternar navegación" })).toBeVisible();
await expect(page.getByRole("button", { name: "Notificaciones" })).toBeVisible();
await expect(page.getByRole("button", { name: "Menú de cuenta" })).toBeVisible();
await expect(page.getByRole("button", { name: "Más acciones" })).toBeVisible();
await expect(page.locator("time").first()).toContainText("sept");
if (visualRegressionProjects.has(testInfo.project.name)) {
await expect(page).toHaveScreenshot(`inbox-${testInfo.project.name}-es.png`, {
animations: "disabled",
caret: "hide",
maxDiffPixelRatio: 0.05,
});
}
if ((page.viewportSize()?.width ?? 0) >= 1200) {
await expect(page.getByRole("navigation", { name: "Buzones" })).toBeVisible();
await expect(page.getByRole("button", { name: "Recibidos", exact: true })).toBeVisible();
await expect(page.getByRole("button", { name: "Developer" })).toBeHidden();
await page.getByRole("button", { name: "Expandir Buzones" }).click();
await expect(page.getByRole("button", { name: "Developer" })).toBeVisible();
await page.getByRole("button", { name: "Contraer Buzones" }).click();
await page.getByRole("button", { name: "Más", exact: true }).click();
await expect(page.getByRole("button", { name: "Papelera", exact: true })).toBeVisible();
await expect(page.getByRole("button", { name: "Spam", exact: true })).toBeVisible();
await page.getByRole("button", { name: "Más", exact: true }).click();
}
await page.locator(".message-row").first().click({ button: "right" });
await expect(page.getByRole("menu", { name: /Acciones para Sender 0/ })).toBeVisible();
await expect(page.getByRole("menuitem", { name: "Abrir", exact: true })).toBeVisible();
await page.keyboard.press("Escape");
await page.getByRole("button", { name: "ES", exact: true }).click();
await page.getByRole("menuitemradio", { name: "EN Inglés" }).click();

Expand Down
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
26 changes: 26 additions & 0 deletions services/api/internal/modules/translations/catalogs.json
Original file line number Diff line number Diff line change
Expand Up @@ -8,11 +8,20 @@
"allMail": "All mail",
"drafts": "Drafts",
"sent": "Sent",
"spam": "Spam",
"trash": "Trash",
"more": "More",
"moreActions": "More actions",
"mailboxes": "Mailboxes",
"toggleNavigation": "Toggle navigation",
"notifications": "Notifications",
"accountMenu": "Account menu",
"language": "Language",
"languageEnglish": "English",
"languageSpanish": "Spanish",
"labels": "Labels",
"expandLabel": "Expand",
"collapseLabel": "Collapse",
"accounts": "Accounts",
"compose": "Compose",
"search": "Search mail",
Expand Down Expand Up @@ -99,6 +108,10 @@
"maximize": "Maximize",
"close": "Close",
"discardDraft": "Discard draft",
"formatting": "Formatting",
"bold": "Bold",
"italic": "Italic",
"underline": "Underline",
"draft.editing": "Editing",
"draft.saving": "Saving…",
"draft.saved": "Saved",
Expand Down Expand Up @@ -364,11 +377,20 @@
"allMail": "Todos",
"drafts": "Borradores",
"sent": "Enviados",
"spam": "Spam",
"trash": "Papelera",
"more": "Más",
"moreActions": "Más acciones",
"mailboxes": "Buzones",
"toggleNavigation": "Alternar navegación",
"notifications": "Notificaciones",
"accountMenu": "Menú de cuenta",
"language": "Idioma",
"languageEnglish": "Inglés",
"languageSpanish": "Español",
"labels": "Etiquetas",
"expandLabel": "Expandir",
"collapseLabel": "Contraer",
"accounts": "Cuentas",
"compose": "Redactar",
"search": "Buscar correo",
Expand Down Expand Up @@ -455,6 +477,10 @@
"maximize": "Maximizar",
"close": "Cerrar",
"discardDraft": "Descartar borrador",
"formatting": "Formato",
"bold": "Negrita",
"italic": "Cursiva",
"underline": "Subrayado",
"draft.editing": "Editando",
"draft.saving": "Guardando…",
"draft.saved": "Guardado",
Expand Down
Loading