mirror of
https://github.com/router-for-me/Cli-Proxy-API-Management-Center.git
synced 2026-02-19 03:00:49 +08:00
fix: defult language
This commit is contained in:
@@ -6,14 +6,14 @@ import i18n from 'i18next';
|
|||||||
import { initReactI18next } from 'react-i18next';
|
import { initReactI18next } from 'react-i18next';
|
||||||
import zhCN from './locales/zh-CN.json';
|
import zhCN from './locales/zh-CN.json';
|
||||||
import en from './locales/en.json';
|
import en from './locales/en.json';
|
||||||
import { STORAGE_KEY_LANGUAGE } from '@/utils/constants';
|
import { getInitialLanguage } from '@/utils/language';
|
||||||
|
|
||||||
i18n.use(initReactI18next).init({
|
i18n.use(initReactI18next).init({
|
||||||
resources: {
|
resources: {
|
||||||
'zh-CN': { translation: zhCN },
|
'zh-CN': { translation: zhCN },
|
||||||
en: { translation: en }
|
en: { translation: en }
|
||||||
},
|
},
|
||||||
lng: localStorage.getItem(STORAGE_KEY_LANGUAGE) || 'zh-CN',
|
lng: getInitialLanguage(),
|
||||||
fallbackLng: 'zh-CN',
|
fallbackLng: 'zh-CN',
|
||||||
interpolation: {
|
interpolation: {
|
||||||
escapeValue: false // React 已经转义
|
escapeValue: false // React 已经转义
|
||||||
|
|||||||
@@ -4,7 +4,7 @@ import { useTranslation } from 'react-i18next';
|
|||||||
import { Button } from '@/components/ui/Button';
|
import { Button } from '@/components/ui/Button';
|
||||||
import { Input } from '@/components/ui/Input';
|
import { Input } from '@/components/ui/Input';
|
||||||
import { IconEye, IconEyeOff } from '@/components/ui/icons';
|
import { IconEye, IconEyeOff } from '@/components/ui/icons';
|
||||||
import { useAuthStore, useNotificationStore } from '@/stores';
|
import { useAuthStore, useLanguageStore, useNotificationStore } from '@/stores';
|
||||||
import { detectApiBaseFromLocation, normalizeApiBase } from '@/utils/connection';
|
import { detectApiBaseFromLocation, normalizeApiBase } from '@/utils/connection';
|
||||||
|
|
||||||
export function LoginPage() {
|
export function LoginPage() {
|
||||||
@@ -12,6 +12,8 @@ export function LoginPage() {
|
|||||||
const navigate = useNavigate();
|
const navigate = useNavigate();
|
||||||
const location = useLocation();
|
const location = useLocation();
|
||||||
const { showNotification } = useNotificationStore();
|
const { showNotification } = useNotificationStore();
|
||||||
|
const language = useLanguageStore((state) => state.language);
|
||||||
|
const toggleLanguage = useLanguageStore((state) => state.toggleLanguage);
|
||||||
const isAuthenticated = useAuthStore((state) => state.isAuthenticated);
|
const isAuthenticated = useAuthStore((state) => state.isAuthenticated);
|
||||||
const login = useAuthStore((state) => state.login);
|
const login = useAuthStore((state) => state.login);
|
||||||
const restoreSession = useAuthStore((state) => state.restoreSession);
|
const restoreSession = useAuthStore((state) => state.restoreSession);
|
||||||
@@ -27,6 +29,7 @@ export function LoginPage() {
|
|||||||
const [error, setError] = useState('');
|
const [error, setError] = useState('');
|
||||||
|
|
||||||
const detectedBase = useMemo(() => detectApiBaseFromLocation(), []);
|
const detectedBase = useMemo(() => detectApiBaseFromLocation(), []);
|
||||||
|
const nextLanguageLabel = language === 'zh-CN' ? t('language.english') : t('language.chinese');
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
const init = async () => {
|
const init = async () => {
|
||||||
@@ -49,10 +52,6 @@ export function LoginPage() {
|
|||||||
return <Navigate to={redirect} replace />;
|
return <Navigate to={redirect} replace />;
|
||||||
}
|
}
|
||||||
|
|
||||||
const handleUseCurrent = () => {
|
|
||||||
setApiBase(detectedBase);
|
|
||||||
};
|
|
||||||
|
|
||||||
const handleSubmit = async () => {
|
const handleSubmit = async () => {
|
||||||
if (!managementKey.trim()) {
|
if (!managementKey.trim()) {
|
||||||
setError(t('login.error_required'));
|
setError(t('login.error_required'));
|
||||||
@@ -79,7 +78,20 @@ export function LoginPage() {
|
|||||||
<div className="login-page">
|
<div className="login-page">
|
||||||
<div className="login-card">
|
<div className="login-card">
|
||||||
<div className="login-header">
|
<div className="login-header">
|
||||||
<div className="title">{t('title.login')}</div>
|
<div className="login-title-row">
|
||||||
|
<div className="title">{t('title.login')}</div>
|
||||||
|
<Button
|
||||||
|
type="button"
|
||||||
|
variant="ghost"
|
||||||
|
size="sm"
|
||||||
|
className="login-language-btn"
|
||||||
|
onClick={toggleLanguage}
|
||||||
|
title={t('language.switch')}
|
||||||
|
aria-label={t('language.switch')}
|
||||||
|
>
|
||||||
|
{nextLanguageLabel}
|
||||||
|
</Button>
|
||||||
|
</div>
|
||||||
<div className="subtitle">{t('login.subtitle')}</div>
|
<div className="subtitle">{t('login.subtitle')}</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
@@ -136,14 +148,9 @@ export function LoginPage() {
|
|||||||
}
|
}
|
||||||
/>
|
/>
|
||||||
|
|
||||||
<div style={{ display: 'flex', gap: 12, alignItems: 'center' }}>
|
<Button fullWidth onClick={handleSubmit} loading={loading}>
|
||||||
<Button variant="secondary" onClick={handleUseCurrent}>
|
{loading ? t('login.submitting') : t('login.submit_button')}
|
||||||
{t('login.use_current_address')}
|
</Button>
|
||||||
</Button>
|
|
||||||
<Button fullWidth onClick={handleSubmit} loading={loading}>
|
|
||||||
{loading ? t('login.submitting') : t('login.submit_button')}
|
|
||||||
</Button>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
{error && <div className="error-box">{error}</div>}
|
{error && <div className="error-box">{error}</div>}
|
||||||
|
|
||||||
|
|||||||
@@ -8,6 +8,7 @@ import { persist } from 'zustand/middleware';
|
|||||||
import type { Language } from '@/types';
|
import type { Language } from '@/types';
|
||||||
import { STORAGE_KEY_LANGUAGE } from '@/utils/constants';
|
import { STORAGE_KEY_LANGUAGE } from '@/utils/constants';
|
||||||
import i18n from '@/i18n';
|
import i18n from '@/i18n';
|
||||||
|
import { getInitialLanguage } from '@/utils/language';
|
||||||
|
|
||||||
interface LanguageState {
|
interface LanguageState {
|
||||||
language: Language;
|
language: Language;
|
||||||
@@ -18,7 +19,7 @@ interface LanguageState {
|
|||||||
export const useLanguageStore = create<LanguageState>()(
|
export const useLanguageStore = create<LanguageState>()(
|
||||||
persist(
|
persist(
|
||||||
(set, get) => ({
|
(set, get) => ({
|
||||||
language: 'zh-CN',
|
language: getInitialLanguage(),
|
||||||
|
|
||||||
setLanguage: (language) => {
|
setLanguage: (language) => {
|
||||||
// 切换 i18next 语言
|
// 切换 i18next 语言
|
||||||
|
|||||||
@@ -431,6 +431,7 @@
|
|||||||
display: flex;
|
display: flex;
|
||||||
flex-direction: column;
|
flex-direction: column;
|
||||||
gap: $spacing-sm;
|
gap: $spacing-sm;
|
||||||
|
text-align: center;
|
||||||
|
|
||||||
.title {
|
.title {
|
||||||
font-size: 22px;
|
font-size: 22px;
|
||||||
@@ -443,6 +444,18 @@
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.login-title-row {
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
justify-content: center;
|
||||||
|
gap: $spacing-sm;
|
||||||
|
flex-wrap: wrap;
|
||||||
|
}
|
||||||
|
|
||||||
|
.login-language-btn {
|
||||||
|
white-space: nowrap;
|
||||||
|
}
|
||||||
|
|
||||||
.connection-box {
|
.connection-box {
|
||||||
background: var(--bg-secondary);
|
background: var(--bg-secondary);
|
||||||
border: 1px dashed var(--border-color);
|
border: 1px dashed var(--border-color);
|
||||||
|
|||||||
42
src/utils/language.ts
Normal file
42
src/utils/language.ts
Normal file
@@ -0,0 +1,42 @@
|
|||||||
|
import type { Language } from '@/types';
|
||||||
|
import { STORAGE_KEY_LANGUAGE } from '@/utils/constants';
|
||||||
|
|
||||||
|
const parseStoredLanguage = (value: string): Language | null => {
|
||||||
|
try {
|
||||||
|
const parsed = JSON.parse(value);
|
||||||
|
const candidate = parsed?.state?.language ?? parsed?.language ?? parsed;
|
||||||
|
if (candidate === 'zh-CN' || candidate === 'en') {
|
||||||
|
return candidate;
|
||||||
|
}
|
||||||
|
} catch {
|
||||||
|
if (value === 'zh-CN' || value === 'en') {
|
||||||
|
return value;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return null;
|
||||||
|
};
|
||||||
|
|
||||||
|
const getStoredLanguage = (): Language | null => {
|
||||||
|
if (typeof window === 'undefined') {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
try {
|
||||||
|
const stored = localStorage.getItem(STORAGE_KEY_LANGUAGE);
|
||||||
|
if (!stored) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
return parseStoredLanguage(stored);
|
||||||
|
} catch {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
const getBrowserLanguage = (): Language => {
|
||||||
|
if (typeof navigator === 'undefined') {
|
||||||
|
return 'zh-CN';
|
||||||
|
}
|
||||||
|
const raw = navigator.languages?.[0] || navigator.language || 'zh-CN';
|
||||||
|
return raw.toLowerCase().startsWith('zh') ? 'zh-CN' : 'en';
|
||||||
|
};
|
||||||
|
|
||||||
|
export const getInitialLanguage = (): Language => getStoredLanguage() ?? getBrowserLanguage();
|
||||||
Reference in New Issue
Block a user