mirror of
https://github.com/Rain-kl/OpenFlare.git
synced 2026-10-03 23:06:36 +08:00
feat(frontend): add next-intl bilingual i18n for core paths
Wire next-intl without locale routes, add zh-CN/en catalogs, language switcher, and migrate layout/auth/settings UI copy. Document i18n rules in AGENTS.md and keep static export builds working.
This commit is contained in:
@@ -49,10 +49,17 @@ import type { CreateTokenResponse } from '@/lib/services/user';
|
||||
import { UserService } from '@/lib/services/user';
|
||||
import { useAuth } from '@/components/providers/auth-provider';
|
||||
import { toast } from 'sonner';
|
||||
import { useLocale, useTranslations } from 'next-intl';
|
||||
import { formatDateTime } from '@/i18n/format';
|
||||
import type { AppLocale } from '@/i18n/config';
|
||||
|
||||
export function AccessTokenMain() {
|
||||
const { user } = useAuth();
|
||||
const queryClient = useQueryClient();
|
||||
const t = useTranslations('settings');
|
||||
const tCommon = useTranslations('common');
|
||||
const ta = useTranslations('settings.accessToken');
|
||||
const locale = useLocale() as AppLocale;
|
||||
const [createDialogOpen, setCreateDialogOpen] = React.useState(false);
|
||||
const [viewDialogOpen, setViewDialogOpen] = React.useState(false);
|
||||
const [tokenName, setTokenName] = React.useState('');
|
||||
@@ -80,10 +87,10 @@ export function AccessTokenMain() {
|
||||
void queryClient.invalidateQueries({
|
||||
queryKey: ['user', 'access-tokens'],
|
||||
});
|
||||
toast.success('访问令牌创建成功');
|
||||
toast.success(ta('createSuccess'));
|
||||
},
|
||||
onError: (error: Error) => {
|
||||
toast.error(error.message || '创建访问令牌失败');
|
||||
toast.error(error.message || ta('createFailed'));
|
||||
},
|
||||
});
|
||||
|
||||
@@ -94,10 +101,10 @@ export function AccessTokenMain() {
|
||||
void queryClient.invalidateQueries({
|
||||
queryKey: ['user', 'access-tokens'],
|
||||
});
|
||||
toast.success('访问令牌已撤销');
|
||||
toast.success(ta('deleteSuccess'));
|
||||
},
|
||||
onError: (error: Error) => {
|
||||
toast.error(error.message || '删除访问令牌失败');
|
||||
toast.error(error.message || ta('deleteFailed'));
|
||||
},
|
||||
});
|
||||
|
||||
@@ -110,17 +117,17 @@ export function AccessTokenMain() {
|
||||
void queryClient.invalidateQueries({
|
||||
queryKey: ['user', 'access-tokens'],
|
||||
});
|
||||
toast.success('访问令牌轮换成功,旧密钥已失效');
|
||||
toast.success(ta('rotateSuccess'));
|
||||
},
|
||||
onError: (error: Error) => {
|
||||
toast.error(error.message || '轮换访问令牌失败');
|
||||
toast.error(error.message || ta('rotateFailed'));
|
||||
},
|
||||
});
|
||||
|
||||
const handleCreateToken = (e: React.FormEvent) => {
|
||||
e.preventDefault();
|
||||
if (!tokenName.trim()) {
|
||||
toast.error('请输入令牌名称');
|
||||
toast.error(ta('nameRequired'));
|
||||
return;
|
||||
}
|
||||
createTokenMutation.mutate({
|
||||
@@ -130,21 +137,13 @@ export function AccessTokenMain() {
|
||||
};
|
||||
|
||||
const handleDeleteToken = (id: number, name: string) => {
|
||||
if (
|
||||
window.confirm(
|
||||
`确定要删除并撤销令牌「${name}」吗?删除后此令牌将立即失效且不可恢复。`,
|
||||
)
|
||||
) {
|
||||
if (window.confirm(ta('deleteConfirm', { name }))) {
|
||||
deleteTokenMutation.mutate(id);
|
||||
}
|
||||
};
|
||||
|
||||
const handleRotateToken = (id: number, name: string) => {
|
||||
if (
|
||||
window.confirm(
|
||||
`确定要轮换令牌「${name}」的密钥吗?轮换后系统将生成全新密钥,原令牌密钥将立即失效。`,
|
||||
)
|
||||
) {
|
||||
if (window.confirm(ta('rotateConfirm', { name }))) {
|
||||
rotateTokenMutation.mutate(id);
|
||||
}
|
||||
};
|
||||
@@ -153,22 +152,16 @@ export function AccessTokenMain() {
|
||||
try {
|
||||
await navigator.clipboard.writeText(text);
|
||||
setCopiedId(id);
|
||||
toast.success('复制成功');
|
||||
toast.success(ta('copySuccess'));
|
||||
setTimeout(() => setCopiedId(null), 2000);
|
||||
} catch {
|
||||
toast.error('复制失败');
|
||||
toast.error(ta('copyFailed'));
|
||||
}
|
||||
};
|
||||
|
||||
const formatDate = (dateStr?: string) => {
|
||||
if (!dateStr) return '未使用';
|
||||
return new Date(dateStr).toLocaleString('zh-CN', {
|
||||
year: 'numeric',
|
||||
month: '2-digit',
|
||||
day: '2-digit',
|
||||
hour: '2-digit',
|
||||
minute: '2-digit',
|
||||
});
|
||||
if (!dateStr) return '—';
|
||||
return formatDateTime(dateStr, locale);
|
||||
};
|
||||
|
||||
return (
|
||||
@@ -184,14 +177,14 @@ export function AccessTokenMain() {
|
||||
<BreadcrumbItem>
|
||||
<BreadcrumbLink asChild>
|
||||
<Link href='/settings' className='text-base text-primary'>
|
||||
设置
|
||||
{t('title')}
|
||||
</Link>
|
||||
</BreadcrumbLink>
|
||||
</BreadcrumbItem>
|
||||
<BreadcrumbSeparator />
|
||||
<BreadcrumbItem>
|
||||
<BreadcrumbPage className='text-base font-semibold'>
|
||||
访问令牌
|
||||
{ta('breadcrumb')}
|
||||
</BreadcrumbPage>
|
||||
</BreadcrumbItem>
|
||||
</BreadcrumbList>
|
||||
@@ -202,11 +195,9 @@ export function AccessTokenMain() {
|
||||
<div className='flex items-center gap-4'>
|
||||
<div>
|
||||
<h1 className='text-xl font-bold tracking-tight bg-gradient-to-r from-foreground via-foreground/90 to-muted-foreground bg-clip-text text-transparent'>
|
||||
个人访问令牌 (AccessToken)
|
||||
{ta('title')}
|
||||
</h1>
|
||||
<p className='text-sm text-muted-foreground'>
|
||||
管理您的 API 访问密钥,用于开发或第三方工具直接调用系统 API
|
||||
</p>
|
||||
<p className='text-sm text-muted-foreground'>{ta('subtitle')}</p>
|
||||
</div>
|
||||
</div>
|
||||
<Button
|
||||
@@ -215,7 +206,7 @@ export function AccessTokenMain() {
|
||||
variant={'secondary'}
|
||||
>
|
||||
<Plus className='mr-1.5 size-4' />
|
||||
生成新令牌
|
||||
{ta('generate')}
|
||||
</Button>
|
||||
</div>
|
||||
|
||||
@@ -223,20 +214,18 @@ export function AccessTokenMain() {
|
||||
<div className='rounded-xl border border-amber-500/20 bg-amber-500/5 p-4 flex gap-3 text-amber-600 text-xs leading-relaxed'>
|
||||
<AlertTriangle className='size-4 shrink-0 mt-0.5' />
|
||||
<div className='space-y-1'>
|
||||
<span className='font-bold'>安全提示:</span>
|
||||
<p className='text-muted-foreground'>
|
||||
访问令牌具有您账户的完整接口调用权限。为了您的账户与资产安全,请切勿通过任何代码库提交、即时通讯工具或公共媒介泄露此令牌。推荐按需创建,不使用时及时撤销。
|
||||
</p>
|
||||
<span className='font-bold'>{ta('securityTitle')}</span>
|
||||
<p className='text-muted-foreground'>{ta('securityBody')}</p>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<Card className='border border-dashed shadow-sm'>
|
||||
<CardHeader className='border-b border-dashed pb-4'>
|
||||
<CardTitle className='text-base font-semibold'>
|
||||
活动令牌列表
|
||||
{ta('listTitle')}
|
||||
</CardTitle>
|
||||
<CardDescription className='text-xs'>
|
||||
当前可用的所有访问令牌
|
||||
{ta('listDesc')}
|
||||
</CardDescription>
|
||||
</CardHeader>
|
||||
<CardContent className='pt-6 space-y-4'>
|
||||
@@ -262,14 +251,14 @@ export function AccessTokenMain() {
|
||||
className='text-[10px] px-1.5 py-0 h-4 border-rose-500/40 text-rose-500 bg-rose-500/5 font-semibold'
|
||||
>
|
||||
<Shield className='size-2.5 mr-0.5' />
|
||||
管理员
|
||||
{ta('adminBadge')}
|
||||
</Badge>
|
||||
) : (
|
||||
<Badge
|
||||
variant='outline'
|
||||
className='text-[10px] px-1.5 py-0 h-4 border-border/50 text-muted-foreground bg-muted/10 font-semibold'
|
||||
>
|
||||
用户令牌
|
||||
{ta('userToken')}
|
||||
</Badge>
|
||||
)}
|
||||
</div>
|
||||
@@ -278,7 +267,11 @@ export function AccessTokenMain() {
|
||||
{token.masked_token}
|
||||
</div>
|
||||
<div className='flex flex-wrap gap-x-4 gap-y-0.5 pt-1'>
|
||||
<span>创建于: {formatDate(token.created_at)}</span>
|
||||
<span>
|
||||
{ta('createdAt', {
|
||||
date: formatDate(token.created_at),
|
||||
})}
|
||||
</span>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
@@ -297,7 +290,7 @@ export function AccessTokenMain() {
|
||||
) : (
|
||||
<Copy className='size-3.5 mr-1' />
|
||||
)}
|
||||
复制
|
||||
{ta('copy')}
|
||||
</Button>
|
||||
<Button
|
||||
type='button'
|
||||
@@ -310,7 +303,7 @@ export function AccessTokenMain() {
|
||||
<RefreshCw
|
||||
className={`size-3.5 mr-1 ${rotateTokenMutation.isPending && rotateTokenMutation.variables === token.id ? 'animate-spin' : ''}`}
|
||||
/>
|
||||
轮换
|
||||
{ta('rotate')}
|
||||
</Button>
|
||||
<Button
|
||||
type='button'
|
||||
@@ -321,7 +314,7 @@ export function AccessTokenMain() {
|
||||
disabled={deleteTokenMutation.isPending}
|
||||
>
|
||||
<Trash2 className='size-3.5 mr-1' />
|
||||
撤销
|
||||
{ta('revoke')}
|
||||
</Button>
|
||||
</div>
|
||||
</div>
|
||||
@@ -330,7 +323,7 @@ export function AccessTokenMain() {
|
||||
) : (
|
||||
<div className='rounded-xl border border-dashed border-border/50 px-4 py-10 text-center text-xs text-muted-foreground bg-muted/5 flex flex-col items-center justify-center gap-3'>
|
||||
<Key className='size-8 text-muted-foreground/30' />
|
||||
<span>您当前暂无生成任何访问令牌</span>
|
||||
<span>{ta('empty')}</span>
|
||||
<Button
|
||||
type='button'
|
||||
variant='outline'
|
||||
@@ -339,7 +332,7 @@ export function AccessTokenMain() {
|
||||
onClick={() => setCreateDialogOpen(true)}
|
||||
>
|
||||
<Plus className='mr-1 size-3.5' />
|
||||
生成第一个令牌
|
||||
{ta('generateFirst')}
|
||||
</Button>
|
||||
</div>
|
||||
)}
|
||||
@@ -352,20 +345,20 @@ export function AccessTokenMain() {
|
||||
<form onSubmit={handleCreateToken}>
|
||||
<DialogHeader>
|
||||
<DialogTitle className='text-base font-semibold'>
|
||||
生成新令牌
|
||||
{ta('generate')}
|
||||
</DialogTitle>
|
||||
<DialogDescription className='text-xs text-muted-foreground'>
|
||||
请为新的访问令牌设置一个易于识别的名称,以便将来管理。
|
||||
{ta('createDesc')}
|
||||
</DialogDescription>
|
||||
</DialogHeader>
|
||||
<div className='space-y-4 py-4'>
|
||||
<div className='space-y-2'>
|
||||
<Label htmlFor='token-name' className='text-xs font-semibold'>
|
||||
令牌名称
|
||||
{ta('name')}
|
||||
</Label>
|
||||
<Input
|
||||
id='token-name'
|
||||
placeholder='例如:my-development-key'
|
||||
placeholder={ta('namePlaceholder')}
|
||||
value={tokenName}
|
||||
onChange={(e) => setTokenName(e.target.value)}
|
||||
disabled={createTokenMutation.isPending}
|
||||
@@ -380,10 +373,10 @@ export function AccessTokenMain() {
|
||||
className='text-xs font-semibold flex items-center gap-1.5'
|
||||
>
|
||||
<Shield className='size-3.5 text-rose-500' />
|
||||
管理员权限
|
||||
{ta('adminPermission')}
|
||||
</Label>
|
||||
<p className='text-[11px] text-muted-foreground leading-normal'>
|
||||
开启后此令牌可访问 /admin/** 管理端点,默认关闭
|
||||
{ta('adminHint')}
|
||||
</p>
|
||||
</div>
|
||||
<Switch
|
||||
@@ -403,7 +396,7 @@ export function AccessTokenMain() {
|
||||
disabled={createTokenMutation.isPending}
|
||||
className='rounded-xl text-xs h-9 border-dashed'
|
||||
>
|
||||
取消
|
||||
{tCommon('cancel')}
|
||||
</Button>
|
||||
<Button
|
||||
type='submit'
|
||||
@@ -413,10 +406,10 @@ export function AccessTokenMain() {
|
||||
{createTokenMutation.isPending ? (
|
||||
<>
|
||||
<Loader2 className='mr-1.5 size-3.5 animate-spin' />
|
||||
正在生成...
|
||||
{ta('generating')}
|
||||
</>
|
||||
) : (
|
||||
'生成令牌'
|
||||
ta('generateAction')
|
||||
)}
|
||||
</Button>
|
||||
</DialogFooter>
|
||||
@@ -438,7 +431,7 @@ export function AccessTokenMain() {
|
||||
<DialogHeader>
|
||||
<DialogTitle className='text-base font-bold text-foreground flex items-center gap-1.5'>
|
||||
<Check className='size-5 text-emerald-500 border border-emerald-500 rounded-full p-0.5' />
|
||||
令牌密钥已就绪
|
||||
{ta('readyTitle')}
|
||||
</DialogTitle>
|
||||
</DialogHeader>
|
||||
|
||||
@@ -468,10 +461,8 @@ export function AccessTokenMain() {
|
||||
<div className='rounded-xl border border-rose-500/20 bg-rose-500/5 p-4 flex gap-3 text-rose-600 text-xs leading-relaxed'>
|
||||
<Info className='size-4 shrink-0 mt-0.5' />
|
||||
<div className='space-y-1'>
|
||||
<span className='font-bold'>重要提示:</span>
|
||||
<p className='text-muted-foreground'>
|
||||
这是您唯一一次能够查看此访问令牌明文密钥的机会。请立即将其复制并安全地保存。
|
||||
</p>
|
||||
<span className='font-bold'>{ta('importantTitle')}</span>
|
||||
<p className='text-muted-foreground'>{ta('readyBody')}</p>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
@@ -486,7 +477,7 @@ export function AccessTokenMain() {
|
||||
}}
|
||||
className='rounded-xl w-full'
|
||||
>
|
||||
我已经复制并妥善保存
|
||||
{ta('savedConfirm')}
|
||||
</Button>
|
||||
</DialogFooter>
|
||||
</DialogContent>
|
||||
|
||||
@@ -5,6 +5,7 @@ import Link from 'next/link';
|
||||
import { Check, Loader2, Monitor, Moon, Sun } from 'lucide-react';
|
||||
import { useTheme } from 'next-themes';
|
||||
import { toast } from 'sonner';
|
||||
import { useTranslations } from 'next-intl';
|
||||
import { Button } from '@/components/ui/button';
|
||||
import { useCustomTheme } from '@/lib/theme';
|
||||
import { cn } from '@/lib/utils';
|
||||
@@ -16,9 +17,11 @@ import {
|
||||
BreadcrumbPage,
|
||||
BreadcrumbSeparator,
|
||||
} from '@/components/ui/breadcrumb';
|
||||
import { LanguageSwitcher } from '@/components/common/language-switcher';
|
||||
|
||||
function ThemeModeSection() {
|
||||
const { theme, setTheme } = useTheme();
|
||||
const t = useTranslations('settings.appearance');
|
||||
const [mounted, setMounted] = React.useState(false);
|
||||
|
||||
React.useEffect(() => {
|
||||
@@ -26,19 +29,19 @@ function ThemeModeSection() {
|
||||
}, []);
|
||||
|
||||
const modes = [
|
||||
{ id: 'light', name: '明亮', icon: Sun },
|
||||
{ id: 'dark', name: '黑暗', icon: Moon },
|
||||
{ id: 'system', name: '自动', icon: Monitor },
|
||||
{ id: 'light', name: t('modeLight'), icon: Sun },
|
||||
{ id: 'dark', name: t('modeDark'), icon: Moon },
|
||||
{ id: 'system', name: t('modeSystem'), icon: Monitor },
|
||||
];
|
||||
|
||||
if (!mounted) {
|
||||
return (
|
||||
<div className='space-y-6'>
|
||||
<div>
|
||||
<h2 className='font-medium text-sm text-foreground'>主题模式</h2>
|
||||
<p className='text-xs text-muted-foreground'>
|
||||
选择应用程序的整体明暗主题
|
||||
</p>
|
||||
<h2 className='font-medium text-sm text-foreground'>
|
||||
{t('themeMode')}
|
||||
</h2>
|
||||
<p className='text-xs text-muted-foreground'>{t('themeModeDesc')}</p>
|
||||
</div>
|
||||
<div className='flex gap-2'>
|
||||
{modes.map((mode) => {
|
||||
@@ -63,10 +66,10 @@ function ThemeModeSection() {
|
||||
return (
|
||||
<div className='space-y-6'>
|
||||
<div>
|
||||
<h2 className='font-medium text-sm text-foreground'>主题模式</h2>
|
||||
<p className='text-xs text-muted-foreground'>
|
||||
选择应用程序的整体明暗主题
|
||||
</p>
|
||||
<h2 className='font-medium text-sm text-foreground'>
|
||||
{t('themeMode')}
|
||||
</h2>
|
||||
<p className='text-xs text-muted-foreground'>{t('themeModeDesc')}</p>
|
||||
</div>
|
||||
|
||||
<div className='flex gap-2'>
|
||||
@@ -100,6 +103,7 @@ function InterfaceAppearanceSection() {
|
||||
isLoading,
|
||||
} = useCustomTheme();
|
||||
const { theme: mode } = useTheme();
|
||||
const t = useTranslations('settings.appearance');
|
||||
const [mounted, setMounted] = React.useState(false);
|
||||
const [switching, setSwitching] = React.useState<string | null>(null);
|
||||
|
||||
@@ -113,9 +117,9 @@ function InterfaceAppearanceSection() {
|
||||
try {
|
||||
setCustomTheme(themeId);
|
||||
const themeName = themes.find((t) => t.id === themeId)?.name || themeId;
|
||||
toast.success(`外观已切换为 ${themeName}`);
|
||||
toast.success(t('switched', { name: themeName }));
|
||||
} catch {
|
||||
toast.error('切换失败');
|
||||
toast.error(t('switchFailed'));
|
||||
} finally {
|
||||
setSwitching(null);
|
||||
}
|
||||
@@ -131,10 +135,10 @@ function InterfaceAppearanceSection() {
|
||||
return (
|
||||
<div className='space-y-6'>
|
||||
<div>
|
||||
<h2 className='font-medium text-sm text-foreground'>界面外观</h2>
|
||||
<p className='text-xs text-muted-foreground'>
|
||||
选择界面的配色方案和视觉风格
|
||||
</p>
|
||||
<h2 className='font-medium text-sm text-foreground'>
|
||||
{t('interface')}
|
||||
</h2>
|
||||
<p className='text-xs text-muted-foreground'>{t('interfaceDesc')}</p>
|
||||
</div>
|
||||
|
||||
<div>
|
||||
@@ -229,6 +233,9 @@ function InterfaceAppearanceSection() {
|
||||
}
|
||||
|
||||
export function AppearanceMain() {
|
||||
const t = useTranslations('settings');
|
||||
const ta = useTranslations('settings.appearance');
|
||||
|
||||
return (
|
||||
<div className='py-6 space-y-6'>
|
||||
<div className='font-semibold'>
|
||||
@@ -237,14 +244,14 @@ export function AppearanceMain() {
|
||||
<BreadcrumbItem>
|
||||
<BreadcrumbLink asChild>
|
||||
<Link href='/settings' className='text-base text-primary'>
|
||||
设置
|
||||
{t('title')}
|
||||
</Link>
|
||||
</BreadcrumbLink>
|
||||
</BreadcrumbItem>
|
||||
<BreadcrumbSeparator />
|
||||
<BreadcrumbItem>
|
||||
<BreadcrumbPage className='text-base font-semibold'>
|
||||
外观设置
|
||||
{ta('breadcrumb')}
|
||||
</BreadcrumbPage>
|
||||
</BreadcrumbItem>
|
||||
</BreadcrumbList>
|
||||
@@ -253,13 +260,25 @@ export function AppearanceMain() {
|
||||
|
||||
<ThemeModeSection />
|
||||
|
||||
<div className='space-y-6'>
|
||||
<div>
|
||||
<h2 className='font-medium text-sm text-foreground'>
|
||||
{ta('language')}
|
||||
</h2>
|
||||
<p className='text-xs text-muted-foreground'>{ta('languageDesc')}</p>
|
||||
</div>
|
||||
<LanguageSwitcher variant='full' />
|
||||
</div>
|
||||
|
||||
<InterfaceAppearanceSection />
|
||||
|
||||
<div className='space-y-4 opacity-50 pointer-events-none grayscale'>
|
||||
<h2 className='font-medium text-sm text-muted-foreground'>即将推出</h2>
|
||||
<h2 className='font-medium text-sm text-muted-foreground'>
|
||||
{ta('comingSoon')}
|
||||
</h2>
|
||||
<div className='bg-muted/30 rounded-xl p-6 h-32 flex items-center justify-center border border-border/50'>
|
||||
<span className='text-sm text-muted-foreground'>
|
||||
更多自定义选项正在开发中...
|
||||
{ta('comingSoonBody')}
|
||||
</span>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
'use client';
|
||||
|
||||
import Link from 'next/link';
|
||||
import { useTranslations } from 'next-intl';
|
||||
import { Bell } from 'lucide-react';
|
||||
import { Switch } from '@/components/ui/switch';
|
||||
import { Label } from '@/components/ui/label';
|
||||
@@ -16,6 +17,8 @@ import {
|
||||
|
||||
export function NotificationsMain() {
|
||||
const { showBell, setShowBell } = useNotificationSettings();
|
||||
const t = useTranslations('settings');
|
||||
const tn = useTranslations('settings.notifications');
|
||||
|
||||
return (
|
||||
<div className='py-6 space-y-6'>
|
||||
@@ -25,14 +28,14 @@ export function NotificationsMain() {
|
||||
<BreadcrumbItem>
|
||||
<BreadcrumbLink asChild>
|
||||
<Link href='/settings' className='text-base text-primary'>
|
||||
设置
|
||||
{t('title')}
|
||||
</Link>
|
||||
</BreadcrumbLink>
|
||||
</BreadcrumbItem>
|
||||
<BreadcrumbSeparator />
|
||||
<BreadcrumbItem>
|
||||
<BreadcrumbPage className='text-base font-semibold'>
|
||||
通知设置
|
||||
{tn('breadcrumb')}
|
||||
</BreadcrumbPage>
|
||||
</BreadcrumbItem>
|
||||
</BreadcrumbList>
|
||||
@@ -41,10 +44,10 @@ export function NotificationsMain() {
|
||||
|
||||
<div className='space-y-6'>
|
||||
<div>
|
||||
<h2 className='font-medium text-sm text-foreground'>通知显示</h2>
|
||||
<p className='text-xs text-muted-foreground'>
|
||||
设置通知相关的显示选项
|
||||
</p>
|
||||
<h2 className='font-medium text-sm text-foreground'>
|
||||
{tn('display')}
|
||||
</h2>
|
||||
<p className='text-xs text-muted-foreground'>{tn('displayDesc')}</p>
|
||||
</div>
|
||||
|
||||
<div className='flex items-center justify-between rounded-lg border p-4'>
|
||||
@@ -55,10 +58,10 @@ export function NotificationsMain() {
|
||||
htmlFor='show-bell'
|
||||
className='text-sm font-medium cursor-pointer'
|
||||
>
|
||||
显示通知铃铛
|
||||
{tn('showBell')}
|
||||
</Label>
|
||||
<p className='text-xs text-muted-foreground'>
|
||||
在顶部导航栏中显示通知铃铛图标
|
||||
{tn('showBellDesc')}
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
Reference in New Issue
Block a user