mirror of
https://github.com/Rain-kl/OpenFlare.git
synced 2026-10-14 10:56:37 +08:00
feat(frontend): 控制台 next-intl 中英双语
接入无 URL 前缀的 zh-CN/en,顶栏与外观设置可切换语言;选择写入 cookie 后刷新生效。
This commit is contained in:
@@ -19,6 +19,8 @@ import { LoadingStateWithBorder } from '@/components/layout/loading';
|
||||
import { TlsCertificateService } from '@/lib/services/openflare';
|
||||
import { formatDateTime } from '@/lib/utils';
|
||||
|
||||
import { useTranslations } from 'next-intl';
|
||||
|
||||
import { WebsiteStatusBadge } from './status-badge';
|
||||
import { getCertificateStatus, getErrorMessage } from './website-utils';
|
||||
|
||||
@@ -39,6 +41,7 @@ export function CertificateDetailDialog({
|
||||
onDelete,
|
||||
deleting = false,
|
||||
}: CertificateDetailDialogProps) {
|
||||
const t = useTranslations('certificates');
|
||||
const certificateQuery = useQuery({
|
||||
queryKey: ['openflare', 'tls-certificates', 'detail', certificateId],
|
||||
queryFn: () => TlsCertificateService.getById(certificateId as number),
|
||||
@@ -53,7 +56,7 @@ export function CertificateDetailDialog({
|
||||
|
||||
const certificate = certificateQuery.data;
|
||||
const content = contentQuery.data;
|
||||
const status = certificate ? getCertificateStatus(certificate) : null;
|
||||
const status = certificate ? getCertificateStatus(certificate, t) : null;
|
||||
const loading = certificateQuery.isLoading || contentQuery.isLoading;
|
||||
const hasError = certificateQuery.isError || contentQuery.isError;
|
||||
|
||||
@@ -62,7 +65,7 @@ export function CertificateDetailDialog({
|
||||
await navigator.clipboard.writeText(value);
|
||||
toast.success(message);
|
||||
} catch (error) {
|
||||
toast.error(getErrorMessage(error));
|
||||
toast.error(getErrorMessage(error, t('requestFailed')));
|
||||
}
|
||||
};
|
||||
|
||||
@@ -70,35 +73,34 @@ export function CertificateDetailDialog({
|
||||
<Dialog open={open} onOpenChange={onOpenChange}>
|
||||
<DialogContent className='max-w-3xl max-h-[90vh] overflow-y-auto'>
|
||||
<DialogHeader>
|
||||
<DialogTitle>证书详情</DialogTitle>
|
||||
<DialogDescription>
|
||||
查看证书元信息、备注以及当前保存的 PEM 内容。
|
||||
</DialogDescription>
|
||||
<DialogTitle>{t('detailTitle')}</DialogTitle>
|
||||
<DialogDescription>{t('detailDesc')}</DialogDescription>
|
||||
</DialogHeader>
|
||||
|
||||
{loading ? (
|
||||
<LoadingStateWithBorder description='加载证书详情中...' />
|
||||
<LoadingStateWithBorder description={t('loadingDetail')} />
|
||||
) : hasError ? (
|
||||
<ErrorInline
|
||||
message={getErrorMessage(
|
||||
certificateQuery.error ?? contentQuery.error,
|
||||
t('requestFailed'),
|
||||
)}
|
||||
className='justify-center'
|
||||
/>
|
||||
) : !certificate || !content ? (
|
||||
<EmptyStateWithBorder description='证书不存在,可能已被删除。' />
|
||||
<EmptyStateWithBorder description={t('notFound')} />
|
||||
) : (
|
||||
<div className='space-y-4'>
|
||||
<div className='grid gap-3 md:grid-cols-2'>
|
||||
<div className='rounded-lg border p-3'>
|
||||
<p className='text-[10px] uppercase tracking-wider text-muted-foreground'>
|
||||
证书名称
|
||||
{t('name')}
|
||||
</p>
|
||||
<p className='mt-1 text-sm'>{certificate.name}</p>
|
||||
</div>
|
||||
<div className='rounded-lg border p-3'>
|
||||
<p className='text-[10px] uppercase tracking-wider text-muted-foreground'>
|
||||
状态
|
||||
{t('status')}
|
||||
</p>
|
||||
<div className='mt-1'>
|
||||
{status ? (
|
||||
@@ -111,7 +113,7 @@ export function CertificateDetailDialog({
|
||||
</div>
|
||||
<div className='rounded-lg border p-3'>
|
||||
<p className='text-[10px] uppercase tracking-wider text-muted-foreground'>
|
||||
生效时间
|
||||
{t('notBefore')}
|
||||
</p>
|
||||
<p className='mt-1 text-sm'>
|
||||
{formatDateTime(certificate.not_before)}
|
||||
@@ -119,7 +121,7 @@ export function CertificateDetailDialog({
|
||||
</div>
|
||||
<div className='rounded-lg border p-3'>
|
||||
<p className='text-[10px] uppercase tracking-wider text-muted-foreground'>
|
||||
到期时间
|
||||
{t('notAfter')}
|
||||
</p>
|
||||
<p className='mt-1 text-sm'>
|
||||
{formatDateTime(certificate.not_after)}
|
||||
@@ -129,26 +131,28 @@ export function CertificateDetailDialog({
|
||||
|
||||
<div className='rounded-lg border p-3'>
|
||||
<p className='text-[10px] uppercase tracking-wider text-muted-foreground'>
|
||||
备注
|
||||
{t('remark')}
|
||||
</p>
|
||||
<p className='mt-1 text-sm'>
|
||||
{certificate.remark || t('noRemark')}
|
||||
</p>
|
||||
<p className='mt-1 text-sm'>{certificate.remark || '暂无备注'}</p>
|
||||
</div>
|
||||
|
||||
<div className='space-y-3'>
|
||||
<div>
|
||||
<div className='mb-2 flex items-center justify-between'>
|
||||
<p className='text-sm font-medium'>证书 PEM</p>
|
||||
<p className='text-sm font-medium'>{t('certPem')}</p>
|
||||
<Button
|
||||
type='button'
|
||||
variant='outline'
|
||||
size='sm'
|
||||
className='h-7 text-xs'
|
||||
onClick={() =>
|
||||
void handleCopy(content.cert_pem, '证书 PEM 已复制')
|
||||
void handleCopy(content.cert_pem, t('certPemCopied'))
|
||||
}
|
||||
>
|
||||
<Copy className='mr-1 size-3' />
|
||||
复制
|
||||
{t('copy')}
|
||||
</Button>
|
||||
</div>
|
||||
<pre className='max-h-48 overflow-auto rounded-lg border bg-muted/40 p-3 text-xs break-all whitespace-pre-wrap'>
|
||||
@@ -157,18 +161,18 @@ export function CertificateDetailDialog({
|
||||
</div>
|
||||
<div>
|
||||
<div className='mb-2 flex items-center justify-between'>
|
||||
<p className='text-sm font-medium'>私钥 PEM</p>
|
||||
<p className='text-sm font-medium'>{t('keyPem')}</p>
|
||||
<Button
|
||||
type='button'
|
||||
variant='outline'
|
||||
size='sm'
|
||||
className='h-7 text-xs'
|
||||
onClick={() =>
|
||||
void handleCopy(content.key_pem, '私钥 PEM 已复制')
|
||||
void handleCopy(content.key_pem, t('keyPemCopied'))
|
||||
}
|
||||
>
|
||||
<Copy className='mr-1 size-3' />
|
||||
复制
|
||||
{t('copy')}
|
||||
</Button>
|
||||
</div>
|
||||
<pre className='max-h-48 overflow-auto rounded-lg border bg-muted/40 p-3 text-xs break-all whitespace-pre-wrap'>
|
||||
@@ -185,10 +189,10 @@ export function CertificateDetailDialog({
|
||||
variant='outline'
|
||||
onClick={() => onOpenChange(false)}
|
||||
>
|
||||
关闭
|
||||
{t('close')}
|
||||
</Button>
|
||||
<Button type='button' onClick={onEdit} disabled={!certificate}>
|
||||
编辑证书
|
||||
{t('editCert')}
|
||||
</Button>
|
||||
<Button
|
||||
type='button'
|
||||
@@ -199,10 +203,10 @@ export function CertificateDetailDialog({
|
||||
{deleting ? (
|
||||
<>
|
||||
<Loader2 className='mr-1 size-3.5 animate-spin' />
|
||||
删除中...
|
||||
{t('deleting')}
|
||||
</>
|
||||
) : (
|
||||
'删除证书'
|
||||
t('deleteCert')
|
||||
)}
|
||||
</Button>
|
||||
</DialogFooter>
|
||||
|
||||
Reference in New Issue
Block a user