mirror of
https://github.com/rustmailer/bichon.git
synced 2026-08-03 07:48:34 +02:00
feat(i18n): add language switcher in top-right corner for multi-language support #9
This commit is contained in:
@@ -46,6 +46,7 @@ import { ToastAction } from '@/components/ui/toast'
|
||||
import { useMutation, useQueryClient } from '@tanstack/react-query'
|
||||
import { Loader2 } from 'lucide-react'
|
||||
import { add_proxy, update_proxy } from '@/api/system/api'
|
||||
import { useTranslation } from 'react-i18next'
|
||||
|
||||
|
||||
const proxyFormSchema = z.object({
|
||||
@@ -127,6 +128,7 @@ const mapCurrentRowToFormValues = (currentRow: Proxy) => {
|
||||
};
|
||||
|
||||
export function ProxyActionDialog({ currentRow, open, onOpenChange }: Props) {
|
||||
const { t } = useTranslation()
|
||||
const isEdit = !!currentRow
|
||||
const queryClient = useQueryClient();
|
||||
const form = useForm<ProxyForm>({
|
||||
@@ -151,9 +153,9 @@ export function ProxyActionDialog({ currentRow, open, onOpenChange }: Props) {
|
||||
|
||||
function handleSuccess() {
|
||||
toast({
|
||||
title: `Proxy ${isEdit ? 'Updated' : 'Added'}`,
|
||||
description: `Your Proxy has been successfully ${isEdit ? 'updated' : 'added'}.`,
|
||||
action: <ToastAction altText="Close">Close</ToastAction>,
|
||||
title: `${t('settings.proxy')} ${isEdit ? t('common.update') : t('common.add')}`,
|
||||
description: t('settings.yourProxyHasBeenSuccessfully', { action: isEdit ? t('common.update').toLowerCase() : t('common.add').toLowerCase() }),
|
||||
action: <ToastAction altText={t('common.close')}>{t('common.close')}</ToastAction>,
|
||||
});
|
||||
|
||||
queryClient.invalidateQueries({ queryKey: ['proxy-list'] });
|
||||
@@ -164,13 +166,13 @@ export function ProxyActionDialog({ currentRow, open, onOpenChange }: Props) {
|
||||
function handleError(error: AxiosError) {
|
||||
const errorMessage = (error.response?.data as { message?: string })?.message ||
|
||||
error.message ||
|
||||
`${isEdit ? 'Update' : 'Add'} failed, please try again later`;
|
||||
t('settings.proxyUpdateOrAddFailed', { action: isEdit ? t('common.update') : t('common.add') });
|
||||
|
||||
toast({
|
||||
variant: "destructive",
|
||||
title: `Proxy ${isEdit ? 'Update' : 'Add'} Failed`,
|
||||
title: `${t('settings.proxy')} ${isEdit ? t('common.update') : t('common.add')} ${t('common.error')}`,
|
||||
description: errorMessage as string,
|
||||
action: <ToastAction altText="Try again">Try again</ToastAction>,
|
||||
action: <ToastAction altText={t('common.tryAgain')}>{t('common.tryAgain')}</ToastAction>,
|
||||
});
|
||||
console.error(error);
|
||||
}
|
||||
@@ -195,10 +197,10 @@ export function ProxyActionDialog({ currentRow, open, onOpenChange }: Props) {
|
||||
>
|
||||
<DialogContent className='max-w-xl'>
|
||||
<DialogHeader className='text-left mb-4'>
|
||||
<DialogTitle>{isEdit ? 'Edit Proxy' : 'Add New Proxy'}</DialogTitle>
|
||||
<DialogTitle>{isEdit ? t('settings.edit') + ' ' + t('settings.proxy') : t('common.add') + ' ' + t('settings.proxy')}</DialogTitle>
|
||||
<DialogDescription>
|
||||
{isEdit ? 'Update the Proxy here. ' : 'Add new Proxy here. '}
|
||||
Click save when you're done.
|
||||
{isEdit ? t('settings.updateTheProxyHere') : t('settings.addNewProxyHere')}
|
||||
{t('accounts.clickSaveWhenDone')}
|
||||
</DialogDescription>
|
||||
</DialogHeader>
|
||||
<Form {...form}>
|
||||
@@ -212,7 +214,7 @@ export function ProxyActionDialog({ currentRow, open, onOpenChange }: Props) {
|
||||
name="url"
|
||||
render={({ field }) => (
|
||||
<FormItem>
|
||||
<FormLabel>Proxy URL</FormLabel>
|
||||
<FormLabel>{t('settings.proxy')} URL</FormLabel>
|
||||
<FormControl>
|
||||
<Input
|
||||
placeholder="socks5://127.0.0.1:22308"
|
||||
@@ -221,7 +223,7 @@ export function ProxyActionDialog({ currentRow, open, onOpenChange }: Props) {
|
||||
</FormControl>
|
||||
<FormMessage />
|
||||
<FormDescription>
|
||||
Please use an IP address (e.g., 127.0.0.1) rather than a hostname or domain for better reliability.
|
||||
{t('settings.pleaseUseAnIpAddressForBetterReliability')}
|
||||
</FormDescription>
|
||||
</FormItem>
|
||||
)}
|
||||
@@ -242,11 +244,11 @@ export function ProxyActionDialog({ currentRow, open, onOpenChange }: Props) {
|
||||
<span>
|
||||
{isEdit
|
||||
? updateMutation.isPending
|
||||
? "Saving..."
|
||||
: "Save changes"
|
||||
? t('oauth2.saving')
|
||||
: t('accounts.saveChanges')
|
||||
: createMutation.isPending
|
||||
? "Adding..."
|
||||
: "Add"}
|
||||
? t('settings.adding')
|
||||
: t('common.add')}
|
||||
</span>
|
||||
</span>
|
||||
</Button>
|
||||
|
||||
@@ -25,11 +25,11 @@ import { DataTableColumnHeader } from './data-table-column-header'
|
||||
import { DataTableRowActions } from './data-table-row-actions'
|
||||
import { format } from 'date-fns'
|
||||
|
||||
export const columns: ColumnDef<Proxy>[] = [
|
||||
export const getColumns = (t: (key: string) => string): ColumnDef<Proxy>[] => [
|
||||
{
|
||||
accessorKey: 'id',
|
||||
header: ({ column }) => (
|
||||
<DataTableColumnHeader column={column} title='Id' />
|
||||
<DataTableColumnHeader column={column} title={t('settings.id')} />
|
||||
),
|
||||
cell: ({ row }) => (
|
||||
<LongText className='max-w-72'>{`${row.original.id}`}</LongText>
|
||||
@@ -41,7 +41,7 @@ export const columns: ColumnDef<Proxy>[] = [
|
||||
{
|
||||
accessorKey: "url",
|
||||
header: ({ column }) => (
|
||||
<DataTableColumnHeader column={column} title='Url' />
|
||||
<DataTableColumnHeader column={column} title={t('settings.url')} />
|
||||
),
|
||||
cell: ({ row }) => {
|
||||
return <LongText>{row.original.url}</LongText>
|
||||
@@ -51,7 +51,7 @@ export const columns: ColumnDef<Proxy>[] = [
|
||||
{
|
||||
accessorKey: 'created_at',
|
||||
header: ({ column }) => (
|
||||
<DataTableColumnHeader column={column} title='Created At' />
|
||||
<DataTableColumnHeader column={column} title={t('settings.createdAt')} />
|
||||
),
|
||||
cell: ({ row }) => {
|
||||
const created_at = row.original.created_at;
|
||||
@@ -63,7 +63,7 @@ export const columns: ColumnDef<Proxy>[] = [
|
||||
{
|
||||
accessorKey: 'updated_at',
|
||||
header: ({ column }) => (
|
||||
<DataTableColumnHeader column={column} title='Updated At' />
|
||||
<DataTableColumnHeader column={column} title={t('settings.updatedAt')} />
|
||||
),
|
||||
cell: ({ row }) => {
|
||||
const updated_at = row.original.updated_at;
|
||||
|
||||
@@ -21,7 +21,6 @@ import {
|
||||
ArrowDownIcon,
|
||||
ArrowUpIcon,
|
||||
CaretSortIcon,
|
||||
EyeNoneIcon,
|
||||
} from '@radix-ui/react-icons'
|
||||
import { Column } from '@tanstack/react-table'
|
||||
import { cn } from '@/lib/utils'
|
||||
@@ -30,9 +29,9 @@ import {
|
||||
DropdownMenu,
|
||||
DropdownMenuContent,
|
||||
DropdownMenuItem,
|
||||
DropdownMenuSeparator,
|
||||
DropdownMenuTrigger,
|
||||
} from '@/components/ui/dropdown-menu'
|
||||
import { useTranslation } from 'react-i18next'
|
||||
|
||||
interface DataTableColumnHeaderProps<TData, TValue>
|
||||
extends React.HTMLAttributes<HTMLDivElement> {
|
||||
@@ -48,7 +47,7 @@ export function DataTableColumnHeader<TData, TValue>({
|
||||
if (!column.getCanSort()) {
|
||||
return <div className={cn(className)}>{title}</div>
|
||||
}
|
||||
|
||||
const { t } = useTranslation()
|
||||
return (
|
||||
<div className={cn('flex items-center space-x-2', className)}>
|
||||
<DropdownMenu>
|
||||
@@ -71,16 +70,11 @@ export function DataTableColumnHeader<TData, TValue>({
|
||||
<DropdownMenuContent align='start'>
|
||||
<DropdownMenuItem onClick={() => column.toggleSorting(false)}>
|
||||
<ArrowUpIcon className='mr-2 h-3.5 w-3.5 text-muted-foreground/70' />
|
||||
Asc
|
||||
{t('table.asc')}
|
||||
</DropdownMenuItem>
|
||||
<DropdownMenuItem onClick={() => column.toggleSorting(true)}>
|
||||
<ArrowDownIcon className='mr-2 h-3.5 w-3.5 text-muted-foreground/70' />
|
||||
Desc
|
||||
</DropdownMenuItem>
|
||||
<DropdownMenuSeparator />
|
||||
<DropdownMenuItem onClick={() => column.toggleVisibility(false)}>
|
||||
<EyeNoneIcon className='mr-2 h-3.5 w-3.5 text-muted-foreground/70' />
|
||||
Hide
|
||||
{t('table.desc')}
|
||||
</DropdownMenuItem>
|
||||
</DropdownMenuContent>
|
||||
</DropdownMenu>
|
||||
|
||||
@@ -31,6 +31,7 @@ import {
|
||||
} from '@/components/ui/dropdown-menu'
|
||||
import { useProxyContext } from '../context'
|
||||
import { Proxy } from '../data/schema'
|
||||
import { useTranslation } from 'react-i18next'
|
||||
|
||||
interface DataTableRowActionsProps {
|
||||
row: Row<Proxy>
|
||||
@@ -38,6 +39,7 @@ interface DataTableRowActionsProps {
|
||||
|
||||
export function DataTableRowActions({ row }: DataTableRowActionsProps) {
|
||||
const { setOpen, setCurrentRow } = useProxyContext()
|
||||
const { t } = useTranslation()
|
||||
return (
|
||||
<>
|
||||
<DropdownMenu modal={false}>
|
||||
@@ -57,7 +59,7 @@ export function DataTableRowActions({ row }: DataTableRowActionsProps) {
|
||||
setOpen('edit')
|
||||
}}
|
||||
>
|
||||
Edit
|
||||
{t('table.edit')}
|
||||
<DropdownMenuShortcut>
|
||||
<IconEdit size={16} />
|
||||
</DropdownMenuShortcut>
|
||||
@@ -70,7 +72,7 @@ export function DataTableRowActions({ row }: DataTableRowActionsProps) {
|
||||
}}
|
||||
className='!text-red-500'
|
||||
>
|
||||
Delete
|
||||
{t('table.delete')}
|
||||
<DropdownMenuShortcut>
|
||||
<IconTrash size={16} />
|
||||
</DropdownMenuShortcut>
|
||||
|
||||
@@ -19,6 +19,7 @@
|
||||
|
||||
import { Table } from '@tanstack/react-table'
|
||||
import { Input } from '@/components/ui/input'
|
||||
import { useTranslation } from 'react-i18next'
|
||||
|
||||
interface DataTableToolbarProps<TData> {
|
||||
table: Table<TData>
|
||||
@@ -27,11 +28,12 @@ interface DataTableToolbarProps<TData> {
|
||||
export function DataTableToolbar<TData>({
|
||||
table,
|
||||
}: DataTableToolbarProps<TData>) {
|
||||
const { t } = useTranslation()
|
||||
return (
|
||||
<div className='flex items-center justify-between'>
|
||||
<div className='flex flex-1 flex-col-reverse items-start gap-y-2 sm:flex-row sm:items-center sm:space-x-2'>
|
||||
<Input
|
||||
placeholder='Filter Proxy...'
|
||||
placeholder={t('settings.filterProxy')}
|
||||
value={(table.getState().globalFilter as string) ?? ''}
|
||||
onChange={(event) => {
|
||||
table.setGlobalFilter(event.target.value);
|
||||
|
||||
@@ -29,6 +29,7 @@ import { useMutation, useQueryClient } from '@tanstack/react-query'
|
||||
import { ToastAction } from '@/components/ui/toast'
|
||||
import { AxiosError } from 'axios'
|
||||
import { delete_proxy } from '@/api/system/api'
|
||||
import { useTranslation } from 'react-i18next'
|
||||
|
||||
interface Props {
|
||||
open: boolean
|
||||
@@ -37,15 +38,16 @@ interface Props {
|
||||
}
|
||||
|
||||
export function ProxyDeleteDialog({ open, onOpenChange, currentRow }: Props) {
|
||||
const { t } = useTranslation()
|
||||
const [value, setValue] = useState(0)
|
||||
const queryClient = useQueryClient();
|
||||
|
||||
function handleSuccess() {
|
||||
toast({
|
||||
title: 'Delete Success',
|
||||
description: `Proxy has been successfully deleted.`,
|
||||
action: <ToastAction altText="Close">Close</ToastAction>,
|
||||
title: t('proxyDelete.successTitle'),
|
||||
description: t('proxyDelete.successDesc'),
|
||||
action: <ToastAction altText={t('proxyDelete.close')}>{t('proxyDelete.close')}</ToastAction>,
|
||||
});
|
||||
|
||||
queryClient.invalidateQueries({ queryKey: ['proxy-list'] });
|
||||
onOpenChange(false);
|
||||
}
|
||||
@@ -53,13 +55,13 @@ export function ProxyDeleteDialog({ open, onOpenChange, currentRow }: Props) {
|
||||
function handleError(error: AxiosError) {
|
||||
const errorMessage = error.response?.data ||
|
||||
error.message ||
|
||||
`Delete failed, please try again later`;
|
||||
t('proxyDelete.failedDesc');
|
||||
|
||||
toast({
|
||||
variant: "destructive",
|
||||
title: `Proxy delete Failed`,
|
||||
title: t('proxyDelete.failedTitle'),
|
||||
description: errorMessage as string,
|
||||
action: <ToastAction altText="Try again">Try again</ToastAction>,
|
||||
action: <ToastAction altText={t('proxyDelete.tryAgain')}>{t('proxyDelete.tryAgain')}</ToastAction>,
|
||||
});
|
||||
console.error(error);
|
||||
}
|
||||
@@ -88,39 +90,39 @@ export function ProxyDeleteDialog({ open, onOpenChange, currentRow }: Props) {
|
||||
className='mr-1 inline-block stroke-destructive'
|
||||
size={18}
|
||||
/>{' '}
|
||||
Delete Proxy
|
||||
{t('proxyDelete.title')}
|
||||
</span>
|
||||
}
|
||||
desc={
|
||||
<div className='space-y-4'>
|
||||
<p className='mb-2'>
|
||||
Are you sure you want to delete{' '}
|
||||
<span className='font-bold'>{`${currentRow.id}`}</span>?
|
||||
{t('proxyDelete.confirmText')} <span className='font-bold'>{`${currentRow.id}`}</span>?
|
||||
<br />
|
||||
This action will permanently remove the Proxy from the system. This cannot be undone.
|
||||
{t('proxyDelete.permanent')}
|
||||
</p>
|
||||
|
||||
<Label className='my-2'>
|
||||
Proxy Id:
|
||||
{t('proxyDelete.proxyIdLabel')}
|
||||
<Input
|
||||
type="number"
|
||||
value={`${value}`}
|
||||
onChange={(e) => setValue(parseInt(e.target.value, 10))}
|
||||
placeholder='Enter Proxy Id to confirm deletion.'
|
||||
placeholder={t('proxyDelete.proxyIdPlaceholder')}
|
||||
className="mt-2"
|
||||
/>
|
||||
</Label>
|
||||
|
||||
<Alert variant='destructive'>
|
||||
<AlertTitle>Warning!</AlertTitle>
|
||||
<AlertTitle>{t('proxyDelete.warningTitle', 'Warning!')}</AlertTitle>
|
||||
<AlertDescription>
|
||||
Please be carefull, this operation can not be rolled back.
|
||||
{t('proxyDelete.warningDesc')}
|
||||
</AlertDescription>
|
||||
</Alert>
|
||||
</div>
|
||||
}
|
||||
confirmText='Delete'
|
||||
confirmText={t('proxyDelete.deleteBtn')}
|
||||
destructive
|
||||
/>
|
||||
)
|
||||
}
|
||||
|
||||
|
||||
@@ -44,6 +44,7 @@ import {
|
||||
import { Proxy } from '../data/schema'
|
||||
import { DataTablePagination } from './data-table-pagination'
|
||||
import { DataTableToolbar } from './data-table-toolbar'
|
||||
import { useTranslation } from 'react-i18next'
|
||||
|
||||
declare module '@tanstack/react-table' {
|
||||
// eslint-disable-next-line @typescript-eslint/no-unused-vars
|
||||
@@ -59,6 +60,7 @@ interface DataTableProps {
|
||||
|
||||
|
||||
export function ProxyTable({ columns, data }: DataTableProps) {
|
||||
const { t } = useTranslation()
|
||||
const [rowSelection, setRowSelection] = useState({})
|
||||
const [columnVisibility, setColumnVisibility] = useState<VisibilityState>({})
|
||||
const [columnFilters, setColumnFilters] = useState<ColumnFiltersState>([])
|
||||
@@ -140,7 +142,7 @@ export function ProxyTable({ columns, data }: DataTableProps) {
|
||||
colSpan={columns.length}
|
||||
className='h-24 text-center'
|
||||
>
|
||||
No results.
|
||||
{t('common.table.noResults')}
|
||||
</TableCell>
|
||||
</TableRow>
|
||||
)}
|
||||
|
||||
Reference in New Issue
Block a user