- 穿山甲
+ {t('brandName')}
{!mobile &&
}
{mobile &&
}
diff --git a/web/usercenter/lib/api/http.ts b/web/usercenter/lib/api/http.ts
index a730b67..9ee4254 100644
--- a/web/usercenter/lib/api/http.ts
+++ b/web/usercenter/lib/api/http.ts
@@ -17,6 +17,7 @@ import {
clearSession,
getAccessToken,
getRefreshToken,
+ setEmail,
setSession,
} from './session';
@@ -171,7 +172,11 @@ export class HttpClient implements ApiClient {
return session;
}
- getMe = async (): Promise
=> mapMe(await this.request('/v1/me'));
+ getMe = async (): Promise => {
+ const me = mapMe(await this.request('/v1/me'));
+ setEmail(me.email); // 同源官网读取显示用户名;clearSession/logout 时删除
+ return me;
+ };
getSubscription = () => this.request('/v1/me/subscription');
resetSubscription = () => this.request('/v1/me/subscription/reset', { method: 'POST' });
listDevices = async (): Promise => {
diff --git a/web/usercenter/lib/api/mock.ts b/web/usercenter/lib/api/mock.ts
index c033958..d636456 100644
--- a/web/usercenter/lib/api/mock.ts
+++ b/web/usercenter/lib/api/mock.ts
@@ -10,7 +10,7 @@ import {
SubscriptionInfo,
TotpSetup,
} from './types';
-import { setSession, clearSession } from './session';
+import { setSession, clearSession, setEmail } from './session';
const delay = (ms = 420) => new Promise((r) => setTimeout(r, ms));
@@ -88,6 +88,7 @@ export class MockClient implements ApiClient {
async getMe(): Promise {
await delay(260);
+ setEmail('me@pangolin.vpn'); // 同源官网读取显示用户名;clearSession/logout 时删除
return {
email: 'me@pangolin.vpn',
plan: 'pro',
diff --git a/web/usercenter/lib/api/session.ts b/web/usercenter/lib/api/session.ts
index a0b0f59..afd3c8b 100644
--- a/web/usercenter/lib/api/session.ts
+++ b/web/usercenter/lib/api/session.ts
@@ -5,6 +5,17 @@
import type { Session } from './types';
const REFRESH_KEY = 'pg_uc_refresh';
+// 登录用户邮箱:同源官网(pangolin website)读取以显示用户名。仅邮箱、非敏感凭证。
+const EMAIL_KEY = 'pg_uc_email';
+
+export function setEmail(email: string): void {
+ if (typeof window === 'undefined' || !email) return;
+ try {
+ window.localStorage.setItem(EMAIL_KEY, email);
+ } catch {
+ /* ignore quota / privacy mode */
+ }
+}
let accessToken: string | null = null;
let accessExpiresAt = 0;
@@ -44,6 +55,7 @@ export function clearSession(): void {
if (typeof window !== 'undefined') {
try {
window.localStorage.removeItem(REFRESH_KEY);
+ window.localStorage.removeItem(EMAIL_KEY);
} catch {
/* ignore */
}
diff --git a/web/usercenter/lib/i18n.ts b/web/usercenter/lib/i18n.ts
index b7f17de..91f3bfe 100644
--- a/web/usercenter/lib/i18n.ts
+++ b/web/usercenter/lib/i18n.ts
@@ -13,6 +13,7 @@ export const STRINGS: Record = {
navSettings: { zh: '设置', en: 'Settings', ja: '設定', ko: '설정', ru: 'Настройки', es: 'Ajustes' },
signOut: { zh: '退出', en: 'Sign out', ja: 'ログアウト', ko: '로그아웃', ru: 'Выйти', es: 'Cerrar sesión' },
backHome: { zh: '返回主页', en: 'Home', ja: 'ホーム', ko: '홈', ru: 'На главную', es: 'Inicio' },
+ brandName: { zh: '穿山甲', en: 'Pangolin', ja: 'Pangolin', ko: 'Pangolin', ru: 'Pangolin', es: 'Pangolin' },
/* login */
loginTitle: { zh: '登录用户中心', en: 'Log in to your account', ja: 'アカウントにログイン', ko: '계정에 로그인', ru: 'Вход в аккаунт', es: 'Inicia sesión en tu cuenta' },
diff --git a/web/usercenter/lib/theme.tsx b/web/usercenter/lib/theme.tsx
index 3f2c6a9..fc17d0d 100644
--- a/web/usercenter/lib/theme.tsx
+++ b/web/usercenter/lib/theme.tsx
@@ -27,7 +27,9 @@ export function UIProvider({ children }: { children: React.ReactNode }) {
const l = window.localStorage.getItem(LANG_KEY) as Lang | null;
const t = window.localStorage.getItem(THEME_KEY) as Theme | null;
if (l && (['zh', 'en', 'ja', 'ko', 'ru', 'es'] as Lang[]).includes(l)) setLangState(l);
- const initial: Theme = t === 'dark' || t === 'light' ? t : window.matchMedia('(prefers-color-scheme: dark)').matches ? 'dark' : 'light';
+ // 默认恒浅色:只有用户手动切换过(localStorage 有显式偏好)才用保存值,
+ // 不跟随系统 prefers-color-scheme(避免系统暗色把登录页/用户中心染黑)。
+ const initial: Theme = t === 'dark' || t === 'light' ? t : 'light';
setThemeState(initial);
} catch {
/* ignore */
diff --git a/web/website/src/components/Docs.astro b/web/website/src/components/Docs.astro
index d9a9418..b827009 100644
--- a/web/website/src/components/Docs.astro
+++ b/web/website/src/components/Docs.astro
@@ -1,15 +1,18 @@
---
import Icon from './Icon.astro';
-import type { T } from '../i18n/strings';
+import type { T, Lang } from '../i18n/strings';
-interface Props { t: T }
-const { t } = Astro.props;
+interface Props { t: T; lang: Lang }
+const { t, lang } = Astro.props;
+
+// 中文走 /zh/docs/*,其余语言暂共用英文文档(/docs/*)。
+const base = lang === 'zh' ? '/zh/docs' : '/docs';
const docs = [
- { icon: 'rocket', t: 'docs.1t', d: 'docs.1d' },
- { icon: 'circle-help', t: 'docs.2t', d: 'docs.2d' },
- { icon: 'shield-check', t: 'docs.3t', d: 'docs.3d' },
- { icon: 'lock', t: 'docs.4t', d: 'docs.4d' },
+ { icon: 'rocket', t: 'docs.1t', d: 'docs.1d', slug: 'quickstart' },
+ { icon: 'circle-help', t: 'docs.2t', d: 'docs.2d', slug: 'faq' },
+ { icon: 'shield-check', t: 'docs.3t', d: 'docs.3d', slug: 'protocol' },
+ { icon: 'lock', t: 'docs.4t', d: 'docs.4d', slug: 'privacy' },
];
---
@@ -20,13 +23,13 @@ const docs = [