Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
22 commits
Select commit Hold shift + click to select a range
0c61496
feat: add localized user guide labels
hoangsvit Jul 24, 2026
96789a6
feat: add tracked user guide link
hoangsvit Jul 24, 2026
e156a8a
feat: add localized user guide action to popup header
hoangsvit Jul 24, 2026
c01b230
test: cover localized user guide labels
hoangsvit Jul 24, 2026
73bb4a6
test: cover user guide tab navigation
hoangsvit Jul 24, 2026
527fba0
chore: prepare inline guide link patch
hoangsvit Jul 24, 2026
ef95855
feat: add user guide link to inline popup
github-actions[bot] Jul 24, 2026
c40d80d
fix: guard browser locale resolution for user guide
hoangsvit Jul 24, 2026
bb89f81
fix: handle user guide navigation failures
hoangsvit Jul 24, 2026
7cb836a
fix: handle popup guide navigation errors
hoangsvit Jul 24, 2026
38f72aa
test: cover user guide navigation fallbacks
hoangsvit Jul 24, 2026
9ba3333
test: cover browser locale resolution for user guide
hoangsvit Jul 24, 2026
9c69c7e
chore: prepare inline guide CSS cleanup
hoangsvit Jul 24, 2026
7387184
style: align inline popup guide action
github-actions[bot] Jul 24, 2026
7bf49f4
chore: add one-shot WXT locale migration
hoangsvit Jul 24, 2026
4e471c2
fix: simplify generated locale coverage test
hoangsvit Jul 24, 2026
73ce83a
chore: prepare WXT locale migration trigger
hoangsvit Jul 24, 2026
88f95c9
chore: keep WXT locale migration ready
hoangsvit Jul 24, 2026
99c4c2d
chore: add one-shot WXT locale migration script
hoangsvit Jul 24, 2026
825c707
fix: simplify WXT locale migration workflow
hoangsvit Jul 24, 2026
5b36146
refactor: use WXT locales for user guide labels
github-actions[bot] Jul 24, 2026
0b81a66
test: document WXT locale coverage callbacks
hoangsvit Jul 24, 2026
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
28 changes: 28 additions & 0 deletions entrypoints/content/email-helper.css
Original file line number Diff line number Diff line change
Expand Up @@ -263,11 +263,36 @@ input.gmail-alias-input-preview::placeholder {
color: #2563eb;
}

.gmail-alias-popup-user-guide {
position: relative;
display: flex;
width: 28px;
height: 28px;
box-sizing: border-box;
align-items: center;
justify-content: center;
padding: 0;
border: 1px solid #bfdbfe;
border-radius: 7px;
background: #eff6ff;
color: #2563eb;
cursor: pointer;
text-decoration: none;
}
Comment thread
qodo-code-review[bot] marked this conversation as resolved.

.gmail-alias-popup-user-guide:hover,
.gmail-alias-popup-user-guide:focus-visible {
border-color: #93c5fd;
background: #dbeafe;
color: #1d4ed8;
outline: none;
}
Comment thread
qodo-code-review[bot] marked this conversation as resolved.
.gmail-alias-popup-disable-site {
position: relative;
display: flex;
width: 28px;
height: 28px;
box-sizing: border-box;
align-items: center;
justify-content: center;
padding: 0;
Expand All @@ -284,6 +309,7 @@ input.gmail-alias-input-preview::placeholder {
color: #b91c1c;
}

.gmail-alias-popup-user-guide::after,
.gmail-alias-popup-disable-site::after {
position: absolute;
z-index: 2;
Expand All @@ -310,6 +336,8 @@ input.gmail-alias-input-preview::placeholder {
white-space: normal;
}

.gmail-alias-popup-user-guide:hover::after,
.gmail-alias-popup-user-guide:focus-visible::after,
.gmail-alias-popup-disable-site:hover::after,
.gmail-alias-popup-disable-site:focus-visible::after {
opacity: 1;
Expand Down
9 changes: 9 additions & 0 deletions entrypoints/content/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@ import {
parseDisabledInlineSites,
} from "src/utils/inlineSiteSettings";
import { t } from "../../lib/i18n";
import { USER_GUIDE_URL } from "src/utils/externalLinks";
import {
getPreviousAliasForWebsite,
generateSuggestionsForWebsite,
Expand Down Expand Up @@ -240,12 +241,20 @@ function createPopup(
nextPage: escapeHtml(t("nextPage")),
disableInlineForSite: escapeHtml(t("disableInlineForSite")),
extensionName: escapeHtml(t("extensionName")),
userGuide: escapeHtml(t("userGuide")),
reportReview: escapeHtml(t("reportReview")),
};
popup.innerHTML = `
<div class="gmail-alias-popup-header" style="display: flex; justify-content: space-between; align-items: center; padding: 12px 16px; border-bottom: 1px solid #e5e7eb; background: #f9fafb; border-radius: 8px 8px 0 0;">
<span class="gmail-alias-popup-title" title="${safeWebsite}" style="font-weight: 600; font-size: 13px; color: #374151;">${labels.extensionName}</span>
<div class="gmail-alias-popup-header-actions" style="display: flex; align-items: center; gap: 6px;">
<a class="gmail-alias-popup-user-guide" href="${USER_GUIDE_URL}" target="_blank" rel="noopener noreferrer" data-tooltip="${labels.userGuide}" aria-label="${labels.userGuide}">
<svg viewBox="0 0 24 24" width="16" height="16" aria-hidden="true" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round">
<circle cx="12" cy="12" r="10" />
<path d="M9.1 9a3 3 0 1 1 5.83 1c0 2-3 2-3 4" />
<path d="M12 18h.01" />
</svg>
</a>
<button class="gmail-alias-popup-disable-site" type="button" data-tooltip="${labels.disableInlineForSite}" aria-label="${labels.disableInlineForSite}">
<svg viewBox="0 0 24 24" width="16" height="16" aria-hidden="true" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round">
<path d="M2 12s3.5-6 10-6 10 6 10 6-3.5 6-10 6S2 12 2 12Z" />
Expand Down
3 changes: 3 additions & 0 deletions public/_locales/de/messages.json
Original file line number Diff line number Diff line change
Expand Up @@ -770,5 +770,8 @@
},
"storageError": {
"message": "Verlauf konnte nicht geladen werden. Überprüfen Sie den Speicherzugriff."
},
"userGuide": {
"message": "Benutzerhandbuch"
}
}
27 changes: 21 additions & 6 deletions public/_locales/en/messages.json
Original file line number Diff line number Diff line change
Expand Up @@ -736,12 +736,24 @@
"noTagsYet": {
"message": "No tags yet"
},
"disableInlineForSite": { "message": "Turn off the inline helper on this website" },
"inlineDisabledSites": { "message": "Disabled inline helper sites" },
"inlineDisabledSitesDescription": { "message": "The inline icon and quick popup are hidden on these websites." },
"noDisabledSites": { "message": "No disabled websites" },
"enableInlineForSite": { "message": "Enable" },
"reportReview": { "message": "Report / Review" },
"disableInlineForSite": {
"message": "Turn off the inline helper on this website"
},
"inlineDisabledSites": {
"message": "Disabled inline helper sites"
},
"inlineDisabledSitesDescription": {
"message": "The inline icon and quick popup are hidden on these websites."
},
"noDisabledSites": {
"message": "No disabled websites"
},
"enableInlineForSite": {
"message": "Enable"
},
"reportReview": {
"message": "Report / Review"
},
"percentLabel": {
"message": "$PERCENT$%",
"placeholders": {
Expand All @@ -758,5 +770,8 @@
},
"storageError": {
"message": "Failed to load history. Check storage access."
},
"userGuide": {
"message": "User guide"
}
}
3 changes: 3 additions & 0 deletions public/_locales/es/messages.json
Original file line number Diff line number Diff line change
Expand Up @@ -770,5 +770,8 @@
},
"storageError": {
"message": "No se pudo cargar el historial. Verifique el acceso al almacenamiento."
},
"userGuide": {
"message": "Guía del usuario"
}
}
3 changes: 3 additions & 0 deletions public/_locales/fr/messages.json
Original file line number Diff line number Diff line change
Expand Up @@ -770,5 +770,8 @@
},
"storageError": {
"message": "Impossible de charger l'historique. Vérifiez l'accès au stockage."
},
"userGuide": {
"message": "Guide d’utilisation"
}
}
3 changes: 3 additions & 0 deletions public/_locales/hi/messages.json
Original file line number Diff line number Diff line change
Expand Up @@ -770,5 +770,8 @@
},
"storageError": {
"message": "इतिहास लोड नहीं हो सका। संग्रहण पहुंच की जांच करें।"
},
"userGuide": {
"message": "उपयोगकर्ता मार्गदर्शिका"
}
}
3 changes: 3 additions & 0 deletions public/_locales/it/messages.json
Original file line number Diff line number Diff line change
Expand Up @@ -770,5 +770,8 @@
},
"storageError": {
"message": "Impossibile caricare la cronologia. Verificare l'accesso all'archivio."
},
"userGuide": {
"message": "Guida utente"
}
}
3 changes: 3 additions & 0 deletions public/_locales/ja/messages.json
Original file line number Diff line number Diff line change
Expand Up @@ -770,5 +770,8 @@
},
"storageError": {
"message": "履歴を読み込めませんでした。ストレージアクセスを確認してください。"
},
"userGuide": {
"message": "ユーザーガイド"
}
}
3 changes: 3 additions & 0 deletions public/_locales/ko/messages.json
Original file line number Diff line number Diff line change
Expand Up @@ -770,5 +770,8 @@
},
"storageError": {
"message": "기록을 로드할 수 없습니다. 저장소 접근을 확인하세요."
},
"userGuide": {
"message": "사용자 가이드"
}
}
3 changes: 3 additions & 0 deletions public/_locales/pl/messages.json
Original file line number Diff line number Diff line change
Expand Up @@ -770,5 +770,8 @@
},
"storageError": {
"message": "Nie udało się załadować historii. Sprawdź dostęp do pamięci."
},
"userGuide": {
"message": "Podręcznik użytkownika"
}
}
3 changes: 3 additions & 0 deletions public/_locales/pt_BR/messages.json
Original file line number Diff line number Diff line change
Expand Up @@ -770,5 +770,8 @@
},
"storageError": {
"message": "Falha ao carregar o histórico. Verifique o acesso ao armazenamento."
},
"userGuide": {
"message": "Guia do usuário"
}
}
3 changes: 3 additions & 0 deletions public/_locales/ru/messages.json
Original file line number Diff line number Diff line change
Expand Up @@ -770,5 +770,8 @@
},
"storageError": {
"message": "Не удалось загрузить историю. Проверьте доступ к хранилищу."
},
"userGuide": {
"message": "Руководство пользователя"
}
}
3 changes: 3 additions & 0 deletions public/_locales/tr/messages.json
Original file line number Diff line number Diff line change
Expand Up @@ -770,5 +770,8 @@
},
"storageError": {
"message": "Geçmiş yüklenemedi. Depolama erişimini kontrol edin."
},
"userGuide": {
"message": "Kullanım kılavuzu"
}
}
3 changes: 3 additions & 0 deletions public/_locales/vi/messages.json
Original file line number Diff line number Diff line change
Expand Up @@ -770,5 +770,8 @@
},
"storageError": {
"message": "Không thể tải lịch sử. Kiểm tra quyền truy cập bộ nhớ."
},
"userGuide": {
"message": "Hướng dẫn sử dụng"
}
}
3 changes: 3 additions & 0 deletions public/_locales/zh_CN/messages.json
Original file line number Diff line number Diff line change
Expand Up @@ -770,5 +770,8 @@
},
"storageError": {
"message": "无法加载历史记录。请检查存储访问权限。"
},
"userGuide": {
"message": "用户指南"
}
}
25 changes: 23 additions & 2 deletions src/components/alias/PopupHeader.tsx
Original file line number Diff line number Diff line change
@@ -1,8 +1,9 @@
// skipcq: JS-0415 - Header markup is a compact visual composition with no nested business logic.
import { Settings, Sparkles } from "lucide-react";
import { CircleHelp, Settings, Sparkles } from "lucide-react";
import { Button } from "src/components/motion/button/base";
import { Tooltip } from "src/components/motion/tooltip";
import { ThemeToggle } from "src/components/motion/theme-toggle";
import { openUserGuide } from "src/utils/externalLinks";
import { t } from "../../../lib/i18n";

export interface PopupHeaderProps {
Expand Down Expand Up @@ -37,12 +38,21 @@ interface HeaderActionsProps {
onThemeChange: (theme: "light" | "dark") => void;
}

/** Header actions for theme switching and opening settings. */
/** Opens the public user guide and contains any unexpected navigation error. */
function handleOpenUserGuide() {
void openUserGuide().catch((error) => {
console.error("Failed to open the Gmail Alias Toolkit user guide.", error);
});
}

/** Header actions for theme switching, help, and opening settings. */
function HeaderActions({
isDark,
onOpenSettings,
onThemeChange,
}: HeaderActionsProps) {
const userGuideLabel = t("userGuide");

return (
<div className="flex shrink-0 items-center gap-1.5">
<Tooltip
Expand All @@ -61,6 +71,17 @@ function HeaderActions({
iconClassName="h-4 w-4"
/>
</Tooltip>
<Tooltip content={userGuideLabel} side="bottom">
<Button
onClick={handleOpenUserGuide}
variant="ghost"
size="icon"
className="shrink-0 rounded-2xl text-primary-foreground hover:bg-primary-foreground/15 focus-visible:ring-primary-foreground/60"
aria-label={userGuideLabel}
>
<CircleHelp className="h-5 w-5" aria-hidden="true" />
</Button>
</Tooltip>
<Tooltip content={t("settings")} side="bottom">
<Button
onClick={onOpenSettings}
Expand Down
27 changes: 27 additions & 0 deletions src/utils/externalLinks.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,27 @@
export const USER_GUIDE_URL =
"https://ext.eplus.dev/gmail-alias-toolkit/introduction?utm_source=gmail-alias-toolkit&utm_medium=extension&utm_campaign=user-guide";

/** Opens the guide through a regular window when the tabs API is unavailable. */
function openUserGuideWindow(): boolean {
try {
const openedWindow = globalThis.open?.(
USER_GUIDE_URL,
"_blank",
"noopener,noreferrer",
);
if (openedWindow) openedWindow.opener = null;
return Boolean(openedWindow);
} catch {
return false;
}
}

/** Opens the public Gmail Alias Toolkit guide with a safe browser fallback. */
export async function openUserGuide(): Promise<boolean> {
try {
await browser.tabs.create({ url: USER_GUIDE_URL });
return true;
} catch {
return openUserGuideWindow();
}
}
48 changes: 48 additions & 0 deletions tests/i18n/userGuideLocales.test.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,48 @@
import { type Dirent, readdirSync, readFileSync } from "node:fs";
import { resolve } from "node:path";
import { describe, expect, it } from "vitest";

/** Returns whether a locale directory entry is a directory. */
function isLocaleDirectory(entry: Dirent): boolean {
return entry.isDirectory();
}

/** Verifies that every WXT locale defines a non-empty user guide label. */
function assertUserGuideMessages(): void {
const localesRoot = resolve(process.cwd(), "public/_locales");
const localeDirectories = readdirSync(localesRoot, {
withFileTypes: true,
}).filter(isLocaleDirectory);

expect(localeDirectories.length).toBeGreaterThan(0);

for (const locale of localeDirectories) {
const messagesPath = resolve(
localesRoot,
locale.name,
"messages.json",
);
const messages = JSON.parse(readFileSync(messagesPath, "utf8")) as Record<
string,
{ message?: unknown } | undefined
>;
const userGuideMessage = messages.userGuide?.message;

expect(messages.userGuide, locale.name).toBeDefined();
expect(userGuideMessage, locale.name).toEqual(expect.any(String));
expect(
typeof userGuideMessage === "string" ? userGuideMessage.trim() : "",
locale.name,
).not.toBe("");
}
}

/** Registers the WXT user guide localization regression test. */
function defineUserGuideLocaleSuite(): void {
it(
"defines a non-empty userGuide message for every locale",
assertUserGuideMessages,
);
}

describe("WXT user guide translations", defineUserGuideLocaleSuite);
Loading