feat: 国际化处理

This commit is contained in:
2025-08-10 03:07:37 -07:00
parent 0d3b63c2fc
commit 01ac12c171
33 changed files with 268 additions and 123 deletions
@@ -17,6 +17,7 @@ import {
DialogTitle,
} from '@workspace/airo-ui/components/dialog';
import { Pagination } from '@workspace/airo-ui/custom-components/pro-table/pagination';
import { useTranslations } from 'next-intl';
import { useImperativeHandle, useRef, useState } from 'react';
import { Popup, PopupData, PopupRef } from './Popup';
@@ -37,6 +38,7 @@ interface DialogProps {
}
export const AnnouncementDialog = ({ ref }: DialogProps) => {
const t = useTranslations('dashboard');
const [open, setOpen] = useState(false);
const [pagination, setPagination] = useState<PaginationState>({
pageIndex: 0,
@@ -101,12 +103,14 @@ export const AnnouncementDialog = ({ ref }: DialogProps) => {
<Dialog open={open} onOpenChange={setOpen}>
<DialogContent className='flex flex-col sm:grid sm:max-w-[600px]'>
<DialogHeader>
<DialogTitle className={'text-left text-2xl sm:text-4xl'}></DialogTitle>
<DialogTitle className={'text-left text-2xl sm:text-4xl'}>
{t('announcementTitle')}
</DialogTitle>
</DialogHeader>
<div className='space-y-4'>
{isLoading ? (
<div className='py-8 text-center'>...</div>
<div className='py-8 text-center'>{t('loading')}</div>
) : (
<>
{announcementData?.list?.map((item: AnnouncementItem) => {
@@ -116,7 +120,7 @@ export const AnnouncementDialog = ({ ref }: DialogProps) => {
className='flex items-center rounded-[20px] bg-[#B5C9E2] px-4 py-2 sm:p-4'
>
<p className='line-clamp-2 flex-1 text-[10px] text-[#225BA9] sm:text-sm'>
{item.pinned && '【置顶公告】'}{' '}
{item.pinned && t('pinnedAnnouncement')}{' '}
<span className={`${item.pinned ? 'text-white' : 'text-[#4D4D4D]'}`}>
{item.content}
</span>
@@ -126,7 +130,7 @@ export const AnnouncementDialog = ({ ref }: DialogProps) => {
className='cursor-pointer text-xs text-[#225BA9] sm:text-sm'
onClick={() => handleOpenPopup(item)}
>
{t('viewDetails')}
</span>
</div>
</div>
@@ -139,9 +143,9 @@ export const AnnouncementDialog = ({ ref }: DialogProps) => {
<Pagination
table={table}
text={{
textRowsPerPage: '每页显示',
textRowsPerPage: t('rowsPerPage'),
textPageOf: (pageIndex, pageCount) =>
`${pageIndex} 页,共 ${pageCount}`,
t('pageOf', { pageIndex: pageIndex, pageCount: pageCount }),
}}
/>
</div>
@@ -111,7 +111,9 @@ export default function Content() {
if (data && userSubscribe?.length > 0 && !userSubscribeProtocol.length) {
const list = getUserSubscribe(userSubscribe[0]?.token, data.protocol);
setUserSubscribeProtocol(list);
setUserSubscribeProtocolCurrent(list[0]);
if (list.length > 0) {
setUserSubscribeProtocolCurrent(list[0]);
}
}
}, [data, userSubscribe, userSubscribeProtocol.length]);
@@ -129,7 +131,7 @@ export default function Content() {
const currentIndex = userSubscribeProtocol.findIndex(
(url) => url === userSubscribeProtocolCurrent,
);
return currentIndex !== -1 ? `${t('subscriptionUrl')}${currentIndex + 1}` : '地址1';
return currentIndex !== -1 ? `${t('subscriptionUrl')}${currentIndex + 1}` : t('address1');
};
const popupRef = useRef<PopupRef>(null);
@@ -140,19 +142,23 @@ export default function Content() {
{/* 账户概况 Card */}
<Card className='rounded-[20px] border border-[#D9D9D9] p-6 shadow-[0px_0px_52.6px_1px_rgba(15,44,83,0.05)]'>
<div className='mb-1 sm:mb-4'>
<h3 className='text-base font-medium text-[#666666] sm:text-xl'></h3>
<h3 className='text-base font-medium text-[#666666] sm:text-xl'>
{t('accountOverview')}
</h3>
<p className='mt-1 text-xs text-[#666666] sm:text-sm'>
{user?.auth_methods?.[0]?.auth_identifier}
</p>
</div>
<div className='mb-3 sm:mb-6'>
<span className='text-2xl font-medium text-[#091B33] sm:text-3xl'></span>
<span className='text-2xl font-medium text-[#091B33] sm:text-3xl'>
{t('annualPlanUser')}
</span>
</div>
<div className='rounded-[20px] bg-[#EAEAEA] px-4 py-[10px]'>
<div className='flex items-center justify-between'>
<span className='text-sm font-light text-[#666666]'></span>
<span className='text-sm font-light text-[#666666]'>{t('accountBalance')}</span>
<Recharge
className={
'border-0 bg-transparent p-0 text-sm font-normal text-[#225BA9] shadow-none outline-0 hover:bg-transparent'
@@ -170,21 +176,22 @@ export default function Content() {
<div className='mb-4'>
<h3 className='flex items-center justify-between text-[#666666]'>
<div className={'flex items-center justify-between'}>
<span className={'text-base font-medium sm:text-xl'}></span>
<span className={'text-base font-medium sm:text-xl'}>{t('planStatus')}</span>
<span className={'ml-2.5 rounded-full bg-[#A8D4ED] px-2 text-[8px] text-white'}>
{t('inEffect')}
</span>
</div>
<ResetTraffic
className={
'border-0 bg-transparent p-0 text-sm font-normal text-[#225BA9] shadow-none outline-0 hover:bg-transparent'
}
id={userSubscribe?.[0]?.id}
id={userSubscribe?.[0]?.id || 0}
replacement={userSubscribe?.[0]?.subscribe.replacement}
/>
</h3>
<div className='mb-2 text-sm text-[#666666] sm:mb-[22px] sm:mt-1'>
{formatDate(userSubscribe?.[0]?.expire_time, false)}
{t('planExpirationTime')}
{formatDate(userSubscribe?.[0]?.expire_time, false)}
</div>
<div className='mb-3 sm:mb-6'>
<span className='text-2xl font-medium text-[#091B33] sm:text-3xl'>
@@ -195,7 +202,7 @@ export default function Content() {
<div className='mb-4 flex items-center justify-between'>
<div className='flex items-center gap-2'>
<span className='text-xs sm:text-sm'></span>
<span className='text-xs sm:text-sm'>{t('availableDevices')}</span>
<div className='flex gap-2'>
<div className='h-4 w-4 rounded-full bg-[#225BA9]'></div>
<div className='h-4 w-4 rounded-full bg-[#D9D9D9]'></div>
@@ -206,13 +213,14 @@ export default function Content() {
</div>
</div>
<span className='text-xs sm:text-sm'>
线uu/{userSubscribe?.[0]?.subscribe.device_limit}
{t('online')}
{userSubscribe?.[0]?.subscribe.device_limit}
</span>
</div>
<div>
<div className='mb-1 flex items-center justify-between'>
<span className='text-xs sm:text-sm'>
使/
{t('usedTrafficTotalTraffic')}
<Display
type='traffic'
value={userSubscribe?.[0]?.upload + userSubscribe?.[0]?.download}
@@ -226,20 +234,21 @@ export default function Content() {
/>
</span>
<span className='text-xs sm:text-sm'>
{t('remaining')}
{100 -
(
(userSubscribe?.[0]?.upload + userSubscribe?.[0]?.download) /
userSubscribe?.[0]?.traffic
).toFixed(0)}
Math.round(
(((userSubscribe?.[0]?.upload || 0) + (userSubscribe?.[0]?.download || 0)) /
(userSubscribe?.[0]?.traffic || 1)) *
100,
)}
%
</span>
</div>
<div className='flex h-5 w-full items-center rounded-[20px] bg-[#EAEAEA] p-0.5'>
<div
className='h-full rounded-[20px] bg-[#225BA9]'
className={'h-full rounded-[20px] bg-[#225BA9]'}
style={{
width: `${((userSubscribe?.[0]?.upload + userSubscribe?.[0]?.download) / userSubscribe?.[0]?.traffic).toFixed(0)}%`,
width: `${Math.round((((userSubscribe?.[0]?.upload || 0) + (userSubscribe?.[0]?.download || 0)) / (userSubscribe?.[0]?.traffic || 1)) * 100)}%`,
}}
></div>
</div>
@@ -251,13 +260,15 @@ export default function Content() {
className={'absolute bottom-0 left-0 right-0 h-[60px] bg-white/30 backdrop-blur-[1px]'}
></div>
<div className='mb-3 flex items-center justify-between sm:mb-4'>
<h3 className='text-base font-medium text-[#666666] sm:text-xl'></h3>
<h3 className='text-base font-medium text-[#666666] sm:text-xl'>
{t('siteAnnouncements')}
</h3>
{announcementData?.length ? (
<Button
className='border-0 bg-transparent p-0 text-sm font-normal text-[#225BA9] shadow-none outline-0 hover:bg-transparent'
onClick={() => dialogRef.current.open()}
onClick={() => dialogRef.current?.open()}
>
{t('more')}
</Button>
) : null}
</div>
@@ -268,7 +279,7 @@ export default function Content() {
return (
<div className='flex items-center rounded-[20px] bg-[#B5C9E2] px-4 py-2 sm:p-4'>
<p className='line-clamp-2 flex-1 text-[10px] text-[#225BA9] sm:text-sm'>
{item.pinned && '【置顶公告】'}{' '}
{item.pinned && t('pinnedAnnouncement')}{' '}
<span className={`${item.pinned ? 'text-white' : 'text-[#4D4D4D]'}`}>
{item.content}
</span>
@@ -276,9 +287,9 @@ export default function Content() {
<div className='ml-2 w-[65px] text-right'>
<span
className='cursor-pointer text-xs text-[#225BA9] sm:text-sm'
onClick={() => popupRef.current.open(item)}
onClick={() => popupRef.current?.open(item)}
>
{t('viewDetails')}
</span>
</div>
</div>
@@ -292,26 +303,30 @@ export default function Content() {
{/* 我的订阅 Card */}
<Card className='rounded-[20px] border border-[#D9D9D9] p-6 shadow-[0px_0px_52.6px_1px_rgba(15,44,83,0.05)]'>
<div className='flex items-center justify-between sm:mb-4'>
<h3 className='text-base font-medium text-[#666666] sm:text-xl'></h3>
<h3 className='text-base font-medium text-[#666666] sm:text-xl'>
{t('mySubscription')}
</h3>
<Link
href={'/document'}
className='border-0 bg-transparent p-0 text-sm font-semibold text-[#225BA9] shadow-none outline-0 hover:bg-transparent sm:font-normal'
>
{t('beginnerTutorial')}
</Link>
</div>
{userSubscribe?.[0] && data.protocol ? (
<div className='space-y-2 sm:space-y-4'>
<p className='text-xs font-light text-[#666666] sm:text-sm sm:font-normal'>
{t('copySubscriptionLinkOrScanQrCode')}
</p>
{/* 统计信息 */}
<div className='rounded-[20px] bg-[#EAEAEA] p-4'>
<div className='grid grid-cols-3 gap-4 text-center'>
<div>
<p className='text-[10px] text-[rgba(132,132,132,0.7)] sm:text-xs'></p>
<p className='text-[10px] text-[rgba(132,132,132,0.7)] sm:text-xs'>
{t('totalTraffic')}
</p>
<p className='text-xs font-medium text-[#0F2C53] sm:text-lg'>
<Display
type='traffic'
@@ -430,7 +445,9 @@ export default function Content() {
</PopoverTrigger>
<PopoverContent className='w-auto rounded-xl p-4' align='end'>
<div className='flex flex-col items-center gap-2'>
<p className='text-muted-foreground text-center text-xs'></p>
<p className='text-muted-foreground text-center text-xs'>
{t('scanCodeToSubscribe')}
</p>
<QRCodeCanvas
value={userSubscribeProtocolCurrent}
size={120}
@@ -466,7 +483,7 @@ export default function Content() {
<AlertDialogAction
onClick={async () => {
await resetUserSubscribeToken({
user_subscribe_id: userSubscribe?.[0]?.id,
user_subscribe_id: userSubscribe?.[0]?.id || 0,
});
await refetch();
toast.success(t('resetSuccess'));
@@ -62,10 +62,8 @@ export default function Page() {
{TutorialList && TutorialList?.length > 0 && (
<div className='rounded-[46px] bg-[#EAEAEA] px-[23px] py-[19px] sm:px-[34px] sm:py-[28px]'>
<div className='font-semibold text-[#666]'></div>
<div className={'mb-2.5 text-xs text-[#666] sm:text-sm'}>
</div>
<div className='font-semibold text-[#666]'>{t('tutorialTitle')}</div>
<div className={'mb-2.5 text-xs text-[#666] sm:text-sm'}>{t('tutorialDescription')}</div>
<Tabs defaultValue={TutorialList?.[0]?.title}>
<TabsList className='h-full flex-wrap justify-start gap-1 bg-transparent'>
{TutorialList?.map((tutorial) => (
@@ -1,4 +1,5 @@
import Announcement from '@/components/announcement';
import LanguageSwitch from '@/components/language-switch';
import { SidebarInset, SidebarProvider } from '@workspace/airo-ui/components/sidebar';
import { cookies } from 'next/headers';
import { Header } from './Header';
@@ -12,8 +13,8 @@ export default async function DashboardLayout({ children }: { children: React.Re
<SidebarProvider className='' defaultOpen={defaultOpen}>
<SidebarLeft className='w-[288px] border-r-0 bg-transparent lg:flex' />
<SidebarInset className='relative flex-grow overflow-hidden'>
<LanguageSwitch />
<div className='h-[calc(100vh-56px)] flex-grow gap-4 overflow-auto p-4'>
{' '}
<Header />
{children}
</div>
@@ -15,13 +15,13 @@ interface OrderDetailDialogProps {
orderNo?: string;
}
interface OrderDetailDialogRef {
export interface OrderDetailDialogRef {
show: (orderNo: string) => void;
hide: () => void;
}
const OrderDetailDialog = forwardRef<OrderDetailDialogRef, OrderDetailDialogProps>((props, ref) => {
const t = useTranslations('subscribe');
const t = useTranslations('order');
const { getUserInfo } = useGlobalStore();
const router = useRouter();
const [open, setOpen] = useState(false);
@@ -65,17 +65,17 @@ const OrderDetailDialog = forwardRef<OrderDetailDialogRef, OrderDetailDialogProp
<Dialog open={open} onOpenChange={setOpen}>
<DialogContent className='sm:w-[675px]'>
<div className='text-4xl font-bold text-[#0F2C53] sm:mb-8 sm:text-center sm:text-4xl'>
{t('orderDetail')}
</div>
<div className='text-[16px] font-bold text-[#666]'>
<div></div>
<div>{t('orderNo')}</div>
<div className='text-[12px] font-light text-[#4D4D4D]'>{orderNo}</div>
</div>
<Separator className='mb-3 mt-2 h-[2px] bg-[#225BA9]' />
<div className='text-[15px] text-[#225BA9]'>
<div></div>
<div className='font-light text-[#666]'></div>
<div>{t('paymentMethod')}</div>
<div className='font-light text-[#666]'>{t('walletBalance')}</div>
</div>
<Separator className='mb-3 mt-4 bg-[#225BA9]' />
<div>
@@ -45,7 +45,7 @@ export default function Page() {
ref.current?.refresh();
}}
>
{t('cancelOrder')}
</Button>
<Button className='ml-3 min-w-[150px] rounded-full border-[#A8D4ED] bg-[#A8D4ED] px-[35px] py-[9px] text-center text-xl font-bold hover:border-[#225BA9] hover:bg-[#225BA9] hover:text-white'>
{t('payment')}
@@ -46,10 +46,10 @@ export default function ChangePassword() {
'h-[32px] w-[110px] rounded-[50px] border-0 border-[#0F2C53] bg-[#0F2C53] text-center text-base font-medium leading-[32px] text-white transition hover:bg-[#225BA9] hover:text-white sm:hidden'
}
>
{t('save')}
</Button>
</div>
<div className={'text-xs font-light sm:text-[15px]'}></div>
<div className={'text-xs font-light sm:text-[15px]'}>{t('description')}</div>
</div>
<Form {...form}>
<form id='password-form' onSubmit={form.handleSubmit(onSubmit)} className='space-y-4'>
@@ -102,7 +102,7 @@ export default function ChangePassword() {
'h-full rounded-[50px] border-0 border-[#0F2C53] bg-[#0F2C53] px-[55px] py-[9px] text-xl font-bold text-white transition hover:bg-[#225BA9] hover:text-white'
}
>
{t('save')}
</Button>
</div>
</Card>
@@ -51,10 +51,10 @@ export default function NotifySettings() {
'h-[32px] w-[110px] rounded-[50px] border-0 border-[#0F2C53] bg-[#0F2C53] text-center text-base font-medium leading-[32px] text-white transition hover:bg-[#225BA9] hover:text-white sm:hidden'
}
>
{t('notify.save')}
</Button>
</div>
<div className={'text-xs font-light sm:text-[15px]'}></div>
<div className={'text-xs font-light sm:text-[15px]'}>{t('notify.description')}</div>
</div>
<Form {...form}>
<form
@@ -104,7 +104,7 @@ export default function NotifySettings() {
'h-full rounded-[50px] border-0 border-[#0F2C53] bg-[#0F2C53] px-[55px] py-[9px] text-xl font-bold text-white transition hover:bg-[#225BA9] hover:text-white'
}
>
{t('notify.save')}
</Button>
</div>
</Card>
@@ -245,13 +245,13 @@ export default function ThirdPartyAccounts() {
'h-[32px] w-[110px] rounded-full bg-[#D9D9D9] text-center font-medium leading-[32px] text-white sm:hidden'
}
>
{t('save')}
</div>
</div>
<div className='mb-2 text-xs text-[#666666] sm:mb-4 sm:mt-1 sm:text-sm'>
{user?.auth_methods?.[0]?.auth_identifier}
</div>
<div className={'mb-1 sm:mb-3'}>Email</div>
<div className={'mb-1 sm:mb-3'}>{t('emailLabel')}</div>
<div className={'flex items-center gap-2'}>
<div
className={
@@ -265,7 +265,7 @@ export default function ThirdPartyAccounts() {
'hidden h-[32px] w-[110px] rounded-full bg-[#D9D9D9] text-center text-[16px] font-medium leading-[32px] text-white sm:block'
}
>
{t('save')}
</div>
</div>
</Card>
@@ -76,14 +76,14 @@ export default function Page() {
'hidden text-4xl font-bold text-[#0F2C53] sm:block md:mb-4 md:text-center md:text-5xl'
}
>
{t('title')}
</div>
<div
className={
'-mt-5 text-right text-lg font-bold text-[#666666] sm:mt-0 sm:text-center sm:font-medium'
}
>
{t('description')}
</div>
<div>
<Tabs
@@ -115,7 +115,7 @@ export default function Page() {
}
value='year'
>
{t('yearlyPlan')}
</TabsTrigger>
<TabsTrigger
className={
@@ -123,7 +123,7 @@ export default function Page() {
}
value='month'
>
{t('monthlyPlan')}
</TabsTrigger>
</TabsList>
</Tabs>
@@ -38,7 +38,7 @@ export default function Page() {
<div className='mb-4'>
<div className='flex items-center justify-between'>
<div>
<p className='text-sm font-light text-[#666]'></p>
<p className='text-sm font-light text-[#666]'>{t('totalAssets')}</p>
<p className='text-2xl font-bold sm:text-[32px]'>
<Display type='currency' value={totalAssets} />
</p>
@@ -47,7 +47,9 @@ export default function Page() {
</div>
<div className='grid grid-cols-2 gap-2 sm:grid-cols-2 sm:gap-6 md:grid-cols-4'>
<div className='col-span-2 rounded-[20px] bg-[#EAEAEA] p-4 shadow-sm transition-all duration-300 hover:shadow-md sm:col-span-1'>
<p className='text-sm font-medium text-[#666] opacity-80 sm:mb-3'></p>
<p className='text-sm font-medium text-[#666] opacity-80 sm:mb-3'>
{t('accountBalance')}
</p>
<p className='text-xl font-bold text-[#225BA9] sm:text-2xl'>
<Display type='currency' value={user?.balance} />
</p>
@@ -70,9 +72,9 @@ export default function Page() {
</div>
<div className='col-span-2 rounded-[20px] border-2 border-[#D9D9D9] p-4 shadow-sm transition-all duration-300 hover:shadow-md sm:col-span-1'>
<p className='mb-1 flex justify-between text-sm font-medium text-[#666] opacity-80 sm:mb-3'>
<span></span>
<span>{t('referralCode')}</span>
<Link href='/affiliate' className={'text-[#225BA9]'}>
{t('referralDetails')}
</Link>
</p>
<p className='flex justify-between text-base font-bold text-[#225BA9] sm:text-2xl'>