feat: internationalize button labels and UI components
- Updated CancelButton and SubmitButton to use i18next for localization. - Modified DataTableColumnHeader and DataTableFacetedFilter to include translations for sorting and filtering messages. - Enhanced DataTableSelectionColumn with localized aria-labels for accessibility. - Localized titles and messages in SimpleEmptyState and ErrorOverlay components. - Updated LayoutHeader to use i18next for navigation toggle label. - Integrated i18next into useLogout for error messages. - Localized alert dialog actions in useRemoveAlertDialog. - Added new translations in en.json and es.json for various UI components. - Updated breadcrumb and carousel components to use localized strings. - Enhanced pagination component with i18next for navigation labels. - Localized sheet component close button text.
This commit is contained in:
parent
988790f904
commit
c77a3a3af5
@ -73,7 +73,7 @@ export const LoginPageWithLanguageSelector = () => {
|
||||
},
|
||||
onError: (error) => {
|
||||
const { message } = error;
|
||||
toast({ description: message, title: "Error" });
|
||||
toast({ description: message, title: t("common.error") });
|
||||
console.log(error);
|
||||
},
|
||||
});
|
||||
|
||||
@ -100,7 +100,7 @@ export const CatalogDataTable = () => {
|
||||
if (data?.total_items === 0 && !isFiltered) {
|
||||
return (
|
||||
<SimpleEmptyState
|
||||
subtitle='Empieza cargando los artículos del catálogo'
|
||||
subtitle={t("catalog.list.empty_state.subtitle")}
|
||||
buttonText=''
|
||||
onButtonClick={() => navigate("/catalog/add")}
|
||||
/>
|
||||
|
||||
@ -80,10 +80,10 @@ export function CatalogDataTableFilter<TData>({
|
||||
<XIcon className='w-4 h-4' />
|
||||
</TooltipTrigger>
|
||||
<TooltipContent>
|
||||
<p>{t("catalog.filter.badget_remove_tooltip")}</p>
|
||||
<p>{t("common.filter.badget_remove_tooltip")}</p>
|
||||
</TooltipContent>
|
||||
</Tooltip>
|
||||
<span className='sr-only'>{t("catalog.filter.badget_remove_tooltip")}</span>
|
||||
<span className='sr-only'>{t("common.filter.badget_remove_tooltip")}</span>
|
||||
</Button>
|
||||
</Badge>
|
||||
))}
|
||||
|
||||
@ -7,6 +7,7 @@ import { DataTableToolbar } from "@/components/DataTable/DataTableToolbar";
|
||||
import { useDataTable, useDataTableContext } from "@/lib/hooks";
|
||||
import { IListArticles_Response_DTO } from "@shared/contexts";
|
||||
import { ColumnDef } from "@tanstack/react-table";
|
||||
import { t } from "i18next";
|
||||
import { useMemo } from "react";
|
||||
import { useNavigate } from "react-router-dom";
|
||||
import { useDealerList } from "../hooks";
|
||||
@ -70,7 +71,7 @@ export const DealerDataTable = () => {
|
||||
if (data?.total_items === 0 && !isFiltered) {
|
||||
return (
|
||||
<SimpleEmptyState
|
||||
subtitle='Empieza cargando los artículos del catálogo'
|
||||
subtitle={t("catalog.list.empty_state.subtitle")}
|
||||
buttonText=''
|
||||
onButtonClick={() => navigate("/catalog/add")}
|
||||
/>
|
||||
|
||||
@ -254,7 +254,7 @@ export const CatalogPickerDataTable = ({
|
||||
if (data?.total_items === 0 && !isFiltered) {
|
||||
return (
|
||||
<SimpleEmptyState
|
||||
subtitle='Empieza cargando los artículos del catálogo'
|
||||
subtitle={t("catalog.list.empty_state.subtitle")}
|
||||
buttonText=''
|
||||
onButtonClick={() => navigate("/catalog/add")}
|
||||
/>
|
||||
|
||||
@ -5,6 +5,7 @@ import { cn } from "@/lib/utils";
|
||||
import { Button, Card, CardContent, CardHeader, CardTitle, Progress } from "@/ui";
|
||||
import { FileTextIcon, Loader2Icon, XCircleIcon } from "lucide-react";
|
||||
import { useEffect, useState } from "react";
|
||||
import { t } from "i18next";
|
||||
|
||||
interface PDFGenerationLoadingProps {
|
||||
className?: string;
|
||||
@ -38,7 +39,7 @@ export const PDFGenerationLoading = ({
|
||||
<Card className={cn("overflow-hidden", className)}>
|
||||
<CardHeader>
|
||||
<CardTitle className='flex items-center justify-between'>
|
||||
{error?.errorMessage ? "Error en la generación del PDF" : "Generando PDF"}
|
||||
{error?.errorMessage ? t("quotes.pdf_generation.error") : t("quotes.pdf_generation.title")}
|
||||
{isInProgress && <Loader2Icon className='w-4 h-4 animate-spin' />}
|
||||
{error?.errorMessage && <XCircleIcon className='w-4 h-4 text-destructive' />}
|
||||
</CardTitle>
|
||||
@ -60,14 +61,14 @@ export const PDFGenerationLoading = ({
|
||||
</div>
|
||||
<Progress value={percentage} className='w-full' />
|
||||
<div className='text-sm text-center text-muted-foreground'>
|
||||
<p>Progreso: {percentage}%</p>
|
||||
<p>{t("quotes.pdf_generation.progress", { percentage })}</p>
|
||||
<div className='hidden'>
|
||||
<p>Tiempo transcurrido: {elapsed} segundos</p>
|
||||
{estimatedTime > 0 && <p>Tiempo estimado restante: {estimatedTime} segundos</p>}
|
||||
<p>{t("quotes.pdf_generation.elapsed", { elapsed })}</p>
|
||||
{estimatedTime > 0 && <p>{t("quotes.pdf_generation.remaining", { estimatedTime })}</p>}
|
||||
</div>
|
||||
</div>
|
||||
<Button variant='destructive' onClick={cancel} className='hidden'>
|
||||
Cancelar generación
|
||||
{t("quotes.pdf_generation.cancel")}
|
||||
</Button>
|
||||
</div>
|
||||
)}
|
||||
|
||||
@ -3,6 +3,7 @@ import { cn } from "@/lib/utils";
|
||||
import { Card, CardContent } from "@/ui";
|
||||
import { IListQuotes_Response_DTO } from "@shared/contexts";
|
||||
import { useCallback, useEffect, useState } from "react";
|
||||
import { t } from "i18next";
|
||||
import { useQuotes } from "../hooks";
|
||||
import { PDFGenerationLoading } from "./PDFGenerationLoading";
|
||||
|
||||
@ -60,7 +61,7 @@ const QuotePDFPreview = ({
|
||||
return (
|
||||
<Card className={cn("overflow-hidden", className)}>
|
||||
<CardContent className='px-4 py-6 text-center'>
|
||||
<p className='mx-auto'>Select a quote</p>
|
||||
<p className='mx-auto'>{t("quotes.list.resume.select_quote")}</p>
|
||||
</CardContent>
|
||||
</Card>
|
||||
);
|
||||
|
||||
@ -134,7 +134,7 @@ export const QuoteResume = ({ quoteId, className }: QuoteResumeProps) => {
|
||||
return (
|
||||
<Card className={cn("overflow-hidden", className)}>
|
||||
<CardContent className='px-4 py-6 text-center'>
|
||||
<p className='mx-auto'>Select a quote</p>
|
||||
<p className='mx-auto'>{t("quotes.list.resume.select_quote")}</p>
|
||||
</CardContent>
|
||||
</Card>
|
||||
);
|
||||
@ -204,11 +204,11 @@ export const QuoteResume = ({ quoteId, className }: QuoteResumeProps) => {
|
||||
}}
|
||||
>
|
||||
<EditIcon className='w-4 h-4' />
|
||||
<span className='sr-only'>Editar</span>
|
||||
<span className='sr-only'>{t("common.edit")}</span>
|
||||
</Button>
|
||||
</TooltipTrigger>
|
||||
<TooltipContent>
|
||||
<p>Editar</p>
|
||||
<p>{t("common.edit")}</p>
|
||||
</TooltipContent>
|
||||
</Tooltip>
|
||||
|
||||
@ -223,11 +223,11 @@ export const QuoteResume = ({ quoteId, className }: QuoteResumeProps) => {
|
||||
}}
|
||||
>
|
||||
<CopyIcon className='w-4 h-4' />
|
||||
<span className='sr-only'>Duplicar</span>
|
||||
<span className='sr-only'>{t("common.duplicate")}</span>
|
||||
</Button>
|
||||
</TooltipTrigger>
|
||||
<TooltipContent>
|
||||
<p>Duplicar</p>
|
||||
<p>{t("common.duplicate")}</p>
|
||||
</TooltipContent>
|
||||
</Tooltip>
|
||||
|
||||
@ -235,11 +235,11 @@ export const QuoteResume = ({ quoteId, className }: QuoteResumeProps) => {
|
||||
<TooltipTrigger asChild>
|
||||
<Button variant='outline' size='icon' onClick={handleDownload}>
|
||||
<DownloadIcon className='w-4 h-4' />
|
||||
<span className='sr-only'>Descargar</span>
|
||||
<span className='sr-only'>{t("common.download")}</span>
|
||||
</Button>
|
||||
</TooltipTrigger>
|
||||
<TooltipContent>
|
||||
<p>Descargar</p>
|
||||
<p>{t("common.download")}</p>
|
||||
</TooltipContent>
|
||||
</Tooltip>
|
||||
|
||||
@ -255,11 +255,11 @@ export const QuoteResume = ({ quoteId, className }: QuoteResumeProps) => {
|
||||
}}
|
||||
>
|
||||
<ArchiveIcon className='w-4 h-4' />
|
||||
<span className='sr-only'>Archivar</span>
|
||||
<span className='sr-only'>{t("common.archive")}</span>
|
||||
</Button>
|
||||
</TooltipTrigger>
|
||||
<TooltipContent>
|
||||
<p>Archivar</p>
|
||||
<p>{t("common.archive")}</p>
|
||||
</TooltipContent>
|
||||
</Tooltip>
|
||||
</ButtonGroup>
|
||||
|
||||
@ -277,11 +277,11 @@ export const QuotesDataTable = ({
|
||||
}}
|
||||
>
|
||||
<EditIcon className='w-4 h-4' />
|
||||
<span className='sr-only'>Editar</span>
|
||||
<span className='sr-only'>{t("common.edit")}</span>
|
||||
</Button>
|
||||
</TooltipTrigger>
|
||||
<TooltipContent>
|
||||
<p>Editar</p>
|
||||
<p>{t("common.edit")}</p>
|
||||
</TooltipContent>
|
||||
</Tooltip>
|
||||
|
||||
@ -296,11 +296,11 @@ export const QuotesDataTable = ({
|
||||
}}
|
||||
>
|
||||
<CopyIcon className='w-4 h-4' />
|
||||
<span className='sr-only'>Duplicar</span>
|
||||
<span className='sr-only'>{t("common.duplicate")}</span>
|
||||
</Button>
|
||||
</TooltipTrigger>
|
||||
<TooltipContent>
|
||||
<p>Duplicar</p>
|
||||
<p>{t("common.duplicate")}</p>
|
||||
</TooltipContent>
|
||||
</Tooltip>
|
||||
|
||||
@ -314,11 +314,11 @@ export const QuotesDataTable = ({
|
||||
}}
|
||||
>
|
||||
<DownloadIcon className='w-4 h-4' />
|
||||
<span className='sr-only'>Descargar</span>
|
||||
<span className='sr-only'>{t("common.download")}</span>
|
||||
</Button>
|
||||
</TooltipTrigger>
|
||||
<TooltipContent>
|
||||
<p>Descargar</p>
|
||||
<p>{t("common.download")}</p>
|
||||
</TooltipContent>
|
||||
</Tooltip>
|
||||
|
||||
@ -334,11 +334,11 @@ export const QuotesDataTable = ({
|
||||
}}
|
||||
>
|
||||
<ArchiveIcon className='w-4 h-4' />
|
||||
<span className='sr-only'>Archivar</span>
|
||||
<span className='sr-only'>{t("common.archive")}</span>
|
||||
</Button>
|
||||
</TooltipTrigger>
|
||||
<TooltipContent>
|
||||
<p>Archivar</p>
|
||||
<p>{t("common.archive")}</p>
|
||||
</TooltipContent>
|
||||
</Tooltip>
|
||||
</ButtonGroup>
|
||||
@ -398,7 +398,7 @@ export const QuotesDataTable = ({
|
||||
<Card>
|
||||
<CardContent>
|
||||
<SimpleEmptyState
|
||||
subtitle='Empieza creando alguna cotización'
|
||||
subtitle={t("quotes.list.empty_state.subtitle")}
|
||||
buttonText={t("quotes.create.title")}
|
||||
onButtonClick={() => navigate("add", { relative: "path" })}
|
||||
/>
|
||||
|
||||
@ -24,7 +24,7 @@ export const QuoteDocumentsCardEditor = () => {
|
||||
render={() => (
|
||||
<div className='space-y-6'>
|
||||
<FormItem className='w-full'>
|
||||
<FormLabel>Images</FormLabel>
|
||||
<FormLabel>{t("quotes.create.form_groups.documents.images")}</FormLabel>
|
||||
<FormControl></FormControl>
|
||||
<FormMessage />
|
||||
</FormItem>
|
||||
@ -32,7 +32,7 @@ export const QuoteDocumentsCardEditor = () => {
|
||||
)}
|
||||
/>
|
||||
<Button className='w-fit' disabled={formState.disabled}>
|
||||
Save
|
||||
{t("common.save")}
|
||||
</Button>
|
||||
</FormGroup>
|
||||
</div>
|
||||
|
||||
@ -77,15 +77,15 @@ export const QuoteGeneralCardEditor = () => {
|
||||
>
|
||||
<div className='grid gap-6'>
|
||||
<div className='grid gap-3'>
|
||||
<Label htmlFor='status'>Status</Label>
|
||||
<Label htmlFor='status'>{t("quotes.create.form_groups.status.title")}</Label>
|
||||
<Select>
|
||||
<SelectTrigger id='status' aria-label='Select status'>
|
||||
<SelectValue placeholder='Select status' />
|
||||
<SelectTrigger id='status' aria-label={t("quotes.list.select_status")}>
|
||||
<SelectValue placeholder={t("quotes.list.select_status")} />
|
||||
</SelectTrigger>
|
||||
<SelectContent>
|
||||
<SelectItem value='draft'>Draft</SelectItem>
|
||||
<SelectItem value='published'>Active</SelectItem>
|
||||
<SelectItem value='archived'>Archived</SelectItem>
|
||||
<SelectItem value='draft'>{t("quotes.status.draft")}</SelectItem>
|
||||
<SelectItem value='published'>{t("quotes.status.ready")}</SelectItem>
|
||||
<SelectItem value='archived'>{t("quotes.status.archived")}</SelectItem>
|
||||
</SelectContent>
|
||||
</Select>
|
||||
</div>
|
||||
|
||||
@ -69,7 +69,7 @@ export const QuoteCreate = () => {
|
||||
onError: (error) => {
|
||||
console.debug(error);
|
||||
toast({
|
||||
title: "Error",
|
||||
title: t("common.error"),
|
||||
description: error.message,
|
||||
variant: "destructive",
|
||||
});
|
||||
@ -77,7 +77,7 @@ export const QuoteCreate = () => {
|
||||
onSuccess: (data) => {
|
||||
reset(getValues());
|
||||
toast({
|
||||
title: "Cotización creada",
|
||||
title: t("quotes.notifications.created"),
|
||||
variant: "default",
|
||||
});
|
||||
navigate(`/quotes/edit/${data.id}`, { relative: "path" });
|
||||
@ -89,7 +89,7 @@ export const QuoteCreate = () => {
|
||||
};
|
||||
|
||||
if (isSubmitting) {
|
||||
return <LoadingOverlay title='Guardando ajustes' />;
|
||||
return <LoadingOverlay title={t("quotes.notifications.saving")} />;
|
||||
}
|
||||
|
||||
/*if (isError) {
|
||||
|
||||
@ -314,13 +314,13 @@ export const QuoteEdit = () => {
|
||||
mutate(data, {
|
||||
onError: (error) => {
|
||||
console.error(error);
|
||||
toast({ title: "Error", description: error.message, variant: "destructive" });
|
||||
toast({ title: t("common.error"), description: error.message, variant: "destructive" });
|
||||
//alert(error.message);
|
||||
},
|
||||
//onSettled: () => {},
|
||||
onSuccess: () => {
|
||||
reset(getValues());
|
||||
toast({ title: "Cotización guardada", variant: "success" });
|
||||
toast({ title: t("quotes.notifications.saved"), variant: "success" });
|
||||
if (shouldRedirect) {
|
||||
navigate("/quotes");
|
||||
}
|
||||
@ -354,7 +354,7 @@ export const QuoteEdit = () => {
|
||||
|
||||
return (
|
||||
<>
|
||||
{(isSubmitting || isPending) && <LoadingOverlay title='Guardando cotización' />}
|
||||
{(isSubmitting || isPending) && <LoadingOverlay title={t("quotes.notifications.saving")} />}
|
||||
<Form {...form}>
|
||||
<form onSubmit={handleSubmit((data) => onSubmit(data, false))}>
|
||||
<div className='mx-auto grid max-w-[90rem] flex-1 auto-rows-max gap-6'>
|
||||
|
||||
@ -6,6 +6,7 @@ import { Checkbox } from "@/ui";
|
||||
import { ColumnDef, Row } from "@tanstack/react-table";
|
||||
|
||||
import { useMemo } from "react";
|
||||
import { t } from "i18next";
|
||||
import { QuoteItemsSortableDragHandleCell } from "../components/QuoteItemsSortableDragHandleCell";
|
||||
|
||||
// eslint-disable-next-line @typescript-eslint/no-unused-vars
|
||||
@ -53,7 +54,7 @@ export function useDetailColumns<TData = unknown, TValue = unknown>(
|
||||
(table.getIsSomePageRowsSelected() && "indeterminate")
|
||||
}
|
||||
onCheckedChange={(value) => table.toggleAllPageRowsSelected(!!value)}
|
||||
aria-label='Seleccionar todo'
|
||||
aria-label={t("common.select_all")}
|
||||
className='translate-y-[0px]'
|
||||
/>
|
||||
),
|
||||
@ -64,7 +65,7 @@ export function useDetailColumns<TData = unknown, TValue = unknown>(
|
||||
checked={row.getIsSelected()}
|
||||
disabled={!row.getCanSelect()}
|
||||
onCheckedChange={row.getToggleSelectedHandler()}
|
||||
aria-label='Seleccionar fila'
|
||||
aria-label={t("common.select_row")}
|
||||
className='mt-2'
|
||||
/>
|
||||
),
|
||||
|
||||
@ -1,5 +1,5 @@
|
||||
import { DataTableProvider } from "@/lib/hooks";
|
||||
import { Trans } from "react-i18next";
|
||||
import { Trans, useTranslation } from "react-i18next";
|
||||
import { QuotesDataTable } from "./components";
|
||||
|
||||
import { HelpButton } from "@/components";
|
||||
@ -19,12 +19,12 @@ import {
|
||||
Toggle,
|
||||
} from "@/ui";
|
||||
import { useToggle } from "@wojtekmaj/react-hooks";
|
||||
import { t } from "i18next";
|
||||
import { EyeIcon, EyeOffIcon, PlusIcon } from "lucide-react";
|
||||
import { useState } from "react";
|
||||
import { useNavigate } from "react-router-dom";
|
||||
|
||||
export const QuotesList = () => {
|
||||
const { t } = useTranslation();
|
||||
const navigate = useNavigate();
|
||||
const [status, setStatus] = useState("all");
|
||||
const [enabledPreview, toggleEnabledPreview] = useToggle(true);
|
||||
@ -72,7 +72,7 @@ export const QuotesList = () => {
|
||||
<Label>{t("quotes.list.tabs_title")}</Label>
|
||||
<Select value={status} onValueChange={setStatus}>
|
||||
<SelectTrigger>
|
||||
<SelectValue placeholder='Seleccionar estado' />
|
||||
<SelectValue placeholder={t("quotes.list.select_status")} />
|
||||
</SelectTrigger>
|
||||
<SelectContent>
|
||||
{quoteStatuses.map((s) => (
|
||||
@ -86,14 +86,14 @@ export const QuotesList = () => {
|
||||
</div>
|
||||
<div className='flex items-baseline justify-center mr-4 font-medium'>
|
||||
<HelpButton
|
||||
buttonText='Ayuda'
|
||||
title='Ayuda sobre el Estado de Cotizaciones'
|
||||
content={HelpContent}
|
||||
buttonText={t("common.help")}
|
||||
title={t("quotes.list.status_help.title")}
|
||||
content={<HelpContent />}
|
||||
/>
|
||||
</div>
|
||||
<div className='flex items-center gap-2 ml-auto'>
|
||||
<Toggle
|
||||
aria-label='Show quote preview'
|
||||
aria-label={t("quotes.list.preview_aria")}
|
||||
variant={"outline"}
|
||||
defaultPressed={false}
|
||||
pressed={enabledPreview}
|
||||
@ -123,35 +123,32 @@ export const QuotesList = () => {
|
||||
);
|
||||
};
|
||||
|
||||
const HelpContent = (
|
||||
<ScrollArea className='grid gap-4 py-4'>
|
||||
<p>El estado de una cotización indica su posición actual en el proceso de negocio.</p>
|
||||
<p>Los estados posibles son:</p>
|
||||
const HelpContent = () => {
|
||||
const { t } = useTranslation();
|
||||
|
||||
return <ScrollArea className='grid gap-4 py-4'>
|
||||
<p>{t("quotes.list.status_help.intro")}</p>
|
||||
<p>{t("quotes.list.status_help.possible")}</p>
|
||||
<ul className='pl-6 space-y-2 list-disc'>
|
||||
<li>
|
||||
<strong>Borrador:</strong> La cotización está en proceso de creación o edición.
|
||||
<strong>{t("quotes.status.draft")}:</strong> {t("quotes.quote_status_editor.status.draft.description")}
|
||||
</li>
|
||||
<li>
|
||||
<strong>Preparado:</strong> La cotización está lista para ser enviada al cliente.
|
||||
<strong>{t("quotes.status.ready")}:</strong> {t("quotes.quote_status_editor.status.ready.description")}
|
||||
</li>
|
||||
<li>
|
||||
<strong>Entregado:</strong> La cotización ha sido enviada al cliente.
|
||||
<strong>{t("quotes.status.delivered")}:</strong> {t("quotes.quote_status_editor.status.delivered.description")}
|
||||
</li>
|
||||
<li>
|
||||
<strong>Aceptado:</strong> El cliente ha aprobado la cotización.
|
||||
<strong>{t("quotes.status.accepted")}:</strong> {t("quotes.quote_status_editor.status.accepted.description")}
|
||||
</li>
|
||||
<li>
|
||||
<strong>Rechazado:</strong> El cliente no ha aceptado la cotización.
|
||||
<strong>{t("quotes.status.rejected")}:</strong> {t("quotes.quote_status_editor.status.rejected.description")}
|
||||
</li>
|
||||
<li>
|
||||
<strong>Arcivado:</strong> La cotización ha sido guardada para referencia futura y ya no
|
||||
está activa.
|
||||
<strong>{t("quotes.status.archived")}:</strong> {t("quotes.quote_status_editor.status.archived.description")}
|
||||
</li>
|
||||
</ul>
|
||||
<p>
|
||||
Utiliza estos estados para hacer un seguimiento eficiente de tus cotizaciones y optimizar tu
|
||||
proceso de ventas. El estado <strong>Archivado</strong> es útil para mantener un historial de
|
||||
cotizaciones pasadas sin que interfieran con las cotizaciones activas.
|
||||
</p>
|
||||
</ScrollArea>
|
||||
);
|
||||
<p>{t("quotes.list.status_help.outro")}</p>
|
||||
</ScrollArea>;
|
||||
};
|
||||
|
||||
@ -133,7 +133,7 @@ export const SettingsEditor = () => {
|
||||
mutate(data, {
|
||||
onError: (error) => {
|
||||
console.debug(error);
|
||||
toast({ title: "Error", description: error.message, variant: "destructive" });
|
||||
toast({ title: t("common.error"), description: error.message, variant: "destructive" });
|
||||
//alert(error.message);
|
||||
},
|
||||
//onSettled: () => {},
|
||||
@ -142,13 +142,13 @@ export const SettingsEditor = () => {
|
||||
void i18n.changeLanguage(data.lang_code ?? "es");
|
||||
}
|
||||
reset(getValues());
|
||||
toast({ description: "Ajustes guardados", variant: "success" });
|
||||
toast({ description: t("settings.notifications.saved"), variant: "success" });
|
||||
},
|
||||
});
|
||||
};
|
||||
|
||||
if (isSubmitting) {
|
||||
return <LoadingOverlay title='Guardando ajustes' />;
|
||||
return <LoadingOverlay title={t("settings.notifications.saving")} />;
|
||||
}
|
||||
|
||||
if (status === "error") {
|
||||
@ -219,8 +219,8 @@ export const SettingsEditor = () => {
|
||||
<SelectValue placeholder={t("settings.form_fields.language.placeholder")} />
|
||||
</SelectTrigger>
|
||||
<SelectContent>
|
||||
<SelectItem value='es'>Español</SelectItem>
|
||||
<SelectItem value='en'>English</SelectItem>
|
||||
<SelectItem value='es'>{t("settings.form_fields.language.options.es")}</SelectItem>
|
||||
<SelectItem value='en'>{t("settings.form_fields.language.options.en")}</SelectItem>
|
||||
</SelectContent>
|
||||
</Select>
|
||||
)}
|
||||
|
||||
@ -30,12 +30,12 @@ import Joi from "joi";
|
||||
import { HelpCircleIcon } from "lucide-react";
|
||||
import { useSupport } from "../hooks";
|
||||
|
||||
const formSchema = Joi.object({
|
||||
const createFormSchema = () => Joi.object({
|
||||
incidence: Joi.string().min(10).required().messages({
|
||||
"string.empty": "Debe escribir algo antes de enviar",
|
||||
"string.min": "El texto es demasiado corto. Debe tener al menos 10 caracteres",
|
||||
"string.max": "El texto es demasiado largo.",
|
||||
"any.required": "La descripción es requerida",
|
||||
"string.empty": t("support.modal.validation.empty"),
|
||||
"string.min": t("support.modal.validation.min"),
|
||||
"string.max": t("support.modal.validation.max"),
|
||||
"any.required": t("support.modal.validation.required"),
|
||||
}),
|
||||
});
|
||||
|
||||
@ -49,7 +49,7 @@ export default function SupportModal() {
|
||||
|
||||
const form = useForm<SupportDataForm>({
|
||||
mode: "onBlur",
|
||||
resolver: joiResolver(formSchema),
|
||||
resolver: joiResolver(createFormSchema()),
|
||||
defaultValues: {
|
||||
incidence: "",
|
||||
},
|
||||
@ -63,8 +63,8 @@ export default function SupportModal() {
|
||||
mutateOptions: {
|
||||
onSuccess: () => {
|
||||
toast({
|
||||
title: "Incidencia enviada",
|
||||
description: "La incidencia se ha enviado correctamente",
|
||||
title: t("support.modal.notifications.sent_title"),
|
||||
description: t("support.modal.notifications.sent_description"),
|
||||
variant: "success",
|
||||
});
|
||||
setIsOpen(false);
|
||||
@ -72,9 +72,8 @@ export default function SupportModal() {
|
||||
},
|
||||
onError: () => {
|
||||
toast({
|
||||
title: "Error en el envío",
|
||||
description:
|
||||
"No se ha podido enviar la incidencia correctamente. Por favor, inténtalo de nuevo.",
|
||||
title: t("support.modal.notifications.error_title"),
|
||||
description: t("support.modal.notifications.error_description"),
|
||||
variant: "destructive",
|
||||
});
|
||||
},
|
||||
@ -111,7 +110,7 @@ export default function SupportModal() {
|
||||
onClick={() => setIsOpen(true)}
|
||||
>
|
||||
<HelpCircleIcon className='w-5 h-5' />
|
||||
<span className='sr-only'>Abrir ventana de soporte</span>
|
||||
<span className='sr-only'>{t("support.modal.open")}</span>
|
||||
</Button>
|
||||
</DialogTrigger>
|
||||
<DialogContent className='sm:max-w-xl'>
|
||||
@ -123,14 +122,14 @@ export default function SupportModal() {
|
||||
<form onSubmit={handleSubmit(onSubmit)} className='space-y-4'>
|
||||
<FormTextAreaField
|
||||
name='incidence'
|
||||
placeholder='Describe la incidencia aquí...'
|
||||
placeholder={t("support.modal.placeholder")}
|
||||
className='min-h-96'
|
||||
/>
|
||||
<DialogFooter>
|
||||
<Button type='button' variant='outline' onClick={handleClose}>
|
||||
Cancelar
|
||||
{t("common.cancel")}
|
||||
</Button>
|
||||
<Button type='submit'>Enviar incidencia</Button>
|
||||
<Button type='submit'>{t("support.modal.submit")}</Button>
|
||||
</DialogFooter>
|
||||
</form>
|
||||
</Form>
|
||||
@ -140,17 +139,16 @@ export default function SupportModal() {
|
||||
<AlertDialog open={showConfirmDialog} onOpenChange={setShowConfirmDialog}>
|
||||
<AlertDialogContent>
|
||||
<AlertDialogHeader>
|
||||
<AlertDialogTitle>¿Estás seguro de que quieres cancelar?</AlertDialogTitle>
|
||||
<AlertDialogTitle>{t("support.modal.cancel_confirmation.title")}</AlertDialogTitle>
|
||||
<AlertDialogDescription>
|
||||
Has escrito texto en el campo de descripción. Si cierras la ventana, perderás los
|
||||
cambios no guardados.
|
||||
{t("support.modal.cancel_confirmation.description")}
|
||||
</AlertDialogDescription>
|
||||
</AlertDialogHeader>
|
||||
<AlertDialogFooter>
|
||||
<AlertDialogCancel onClick={() => setShowConfirmDialog(false)}>
|
||||
Volver al formulario
|
||||
{t("support.modal.cancel_confirmation.back")}
|
||||
</AlertDialogCancel>
|
||||
<AlertDialogAction onClick={confirmClose}>Sí, cerrar</AlertDialogAction>
|
||||
<AlertDialogAction onClick={confirmClose}>{t("support.modal.cancel_confirmation.confirm")}</AlertDialogAction>
|
||||
</AlertDialogFooter>
|
||||
</AlertDialogContent>
|
||||
</AlertDialog>
|
||||
|
||||
@ -1,10 +1,11 @@
|
||||
import { Button, ButtonProps } from "@/ui";
|
||||
import { t } from "i18next";
|
||||
|
||||
export interface CancelButtonProps extends ButtonProps {
|
||||
label?: string;
|
||||
}
|
||||
|
||||
export const CancelButton = ({ label = "Cancelar", ...props }: CancelButtonProps): JSX.Element => {
|
||||
export const CancelButton = ({ label = t("common.cancel"), ...props }: CancelButtonProps): JSX.Element => {
|
||||
return (
|
||||
<Button type='button' variant='secondary' {...props}>
|
||||
{label}
|
||||
|
||||
@ -1,11 +1,12 @@
|
||||
import { ButtonProps } from "@/ui";
|
||||
import { t } from "i18next";
|
||||
import { CustomButton } from "./CustomButton";
|
||||
|
||||
export interface SubmitButtonProps extends ButtonProps {
|
||||
label?: string;
|
||||
}
|
||||
|
||||
export const SubmitButton = ({ label = "Enviar", ...props }: SubmitButtonProps) => (
|
||||
export const SubmitButton = ({ label = t("common.submit"), ...props }: SubmitButtonProps) => (
|
||||
<CustomButton type='submit' label={label} variant='default' {...props} />
|
||||
);
|
||||
|
||||
|
||||
@ -70,7 +70,7 @@ export function DataTableColumnHeader<TData, TValue>({
|
||||
? t("common.sort_desc_description")
|
||||
: header.column.getIsSorted() === "asc"
|
||||
? t("common.sort_asc_description")
|
||||
: t("sort_none_description")
|
||||
: t("common.sort_none_description")
|
||||
}
|
||||
size='sm'
|
||||
variant='ghost'
|
||||
@ -121,13 +121,13 @@ export function DataTableColumnHeader<TData, TValue>({
|
||||
{header.column.getCanHide() && (
|
||||
<DropdownMenuItem
|
||||
onClick={() => header.column.toggleVisibility(false)}
|
||||
aria-label={t("Hide")}
|
||||
aria-label={t("common.hide")}
|
||||
>
|
||||
<EyeOffIcon
|
||||
className='mr-2 h-3.5 w-3.5 text-muted-foreground/70'
|
||||
aria-hidden='true'
|
||||
/>
|
||||
{t("Hide")}
|
||||
{t("common.hide")}
|
||||
</DropdownMenuItem>
|
||||
)}
|
||||
</DropdownMenuContent>
|
||||
|
||||
@ -22,6 +22,7 @@ import {
|
||||
|
||||
import { Separator } from "@/ui"
|
||||
import { CheckIcon, PlusCircleIcon } from 'lucide-react'
|
||||
import { t } from "i18next"
|
||||
|
||||
interface DataTableFacetedFilterProps<TData, TValue> {
|
||||
column?: Column<TData, TValue>
|
||||
@ -62,7 +63,7 @@ export function DataTableFacetedFilter<TData, TValue>({
|
||||
variant="secondary"
|
||||
className="px-1 font-normal rounded-sm"
|
||||
>
|
||||
{selectedValues.size} selected
|
||||
{t("components.data_table.selected_count", { count: selectedValues.size })}
|
||||
</Badge>
|
||||
) : (
|
||||
options
|
||||
@ -86,7 +87,7 @@ export function DataTableFacetedFilter<TData, TValue>({
|
||||
<Command>
|
||||
<CommandInput placeholder={title} />
|
||||
<CommandList>
|
||||
<CommandEmpty>No results found.</CommandEmpty>
|
||||
<CommandEmpty>{t("components.data_table.no_results")}</CommandEmpty>
|
||||
<CommandGroup>
|
||||
{options.map((option) => {
|
||||
const isSelected = selectedValues.has(option.value)
|
||||
@ -136,7 +137,7 @@ export function DataTableFacetedFilter<TData, TValue>({
|
||||
onSelect={() => column?.setFilterValue(undefined)}
|
||||
className="justify-center text-center"
|
||||
>
|
||||
Clear filters
|
||||
{t("components.data_table.clear_filters")}
|
||||
</CommandItem>
|
||||
</CommandGroup>
|
||||
</>
|
||||
|
||||
@ -1,4 +1,5 @@
|
||||
import { Checkbox } from "@/ui";
|
||||
import { t } from "i18next";
|
||||
import { DataTableColumnProps } from "./DataTable";
|
||||
|
||||
export function getDataTableSelectionColumn<TData, TError>(): DataTableColumnProps<TData, TError> {
|
||||
@ -11,7 +12,7 @@ export function getDataTableSelectionColumn<TData, TError>(): DataTableColumnPro
|
||||
table.getIsAllPageRowsSelected() || (table.getIsSomePageRowsSelected() && "indeterminate")
|
||||
}
|
||||
onCheckedChange={(value) => table.toggleAllPageRowsSelected(!!value)}
|
||||
aria-label='Seleccionar todo'
|
||||
aria-label={t("common.select_all")}
|
||||
className='translate-y-[2px]'
|
||||
/>
|
||||
),
|
||||
@ -23,7 +24,7 @@ export function getDataTableSelectionColumn<TData, TError>(): DataTableColumnPro
|
||||
onCheckedChange={(value) => {
|
||||
row.toggleSelected(!!value);
|
||||
}}
|
||||
aria-label='Seleccionar file'
|
||||
aria-label={t("common.select_row")}
|
||||
className='translate-y-[2px]'
|
||||
/>
|
||||
),
|
||||
|
||||
@ -10,6 +10,7 @@ import {
|
||||
DropdownMenuTrigger,
|
||||
} from "@/ui";
|
||||
import { Settings2Icon } from "lucide-react";
|
||||
import { t } from "i18next";
|
||||
|
||||
interface DataTableColumnOptionsProps<TData> {
|
||||
table: Table<TData>;
|
||||
@ -27,11 +28,11 @@ export function DataTableColumnOptions<TData>({
|
||||
className="hidden h-8 ml-auto lg:flex"
|
||||
>
|
||||
<Settings2Icon className="w-4 h-4 mr-2" />
|
||||
Columnas
|
||||
{t("components.data_table.columns")}
|
||||
</Button>
|
||||
</DropdownMenuTrigger>
|
||||
<DropdownMenuContent align="end" className="w-[150px]">
|
||||
<DropdownMenuLabel>Columnas</DropdownMenuLabel>
|
||||
<DropdownMenuLabel>{t("components.data_table.columns")}</DropdownMenuLabel>
|
||||
<DropdownMenuSeparator />
|
||||
{table
|
||||
.getAllColumns()
|
||||
|
||||
@ -1,10 +1,11 @@
|
||||
import { Button } from "@/ui";
|
||||
import { PlusIcon } from "lucide-react";
|
||||
import { t } from "i18next";
|
||||
|
||||
export const SimpleEmptyState = ({
|
||||
title = "Esto está muy vacío",
|
||||
subtitle = "Empieza solicitando la carga del catálogo",
|
||||
buttonText = "Solicitar carga del catálogo",
|
||||
title = t("components.empty_state.title"),
|
||||
subtitle = t("components.empty_state.subtitle"),
|
||||
buttonText = t("components.empty_state.button"),
|
||||
onButtonClick = () => {},
|
||||
actions = () => <></>,
|
||||
}) => {
|
||||
|
||||
@ -1,5 +1,6 @@
|
||||
import { Button } from "@/ui";
|
||||
import { HomeIcon } from "lucide-react";
|
||||
import { t } from "i18next";
|
||||
import { useNavigate } from "react-router-dom";
|
||||
import { BackHistoryButton } from "../CustomButtons";
|
||||
|
||||
@ -18,7 +19,7 @@ interface ErrorOverlayProps {
|
||||
};*/
|
||||
|
||||
export const ErrorOverlay = ({
|
||||
title = "Se ha producido un error",
|
||||
title = t("components.error_overlay.title"),
|
||||
subtitle = undefined,
|
||||
description = undefined,
|
||||
errorMessage = undefined,
|
||||
@ -42,7 +43,7 @@ ErrorOverlayProps): JSX.Element => {
|
||||
<div className='mt-6 space-x-8 md:ml-4'>
|
||||
<BackHistoryButton />
|
||||
<Button onClick={() => navigate("/")}>
|
||||
<HomeIcon className='w-4 h-4 mr-2' /> Ir al inicio
|
||||
<HomeIcon className='w-4 h-4 mr-2' /> {t("components.error_overlay.go_home")}
|
||||
</Button>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@ -3,6 +3,7 @@ import { Button, Sheet, SheetContent, SheetTrigger } from "@/ui";
|
||||
import SupportModal from "@/app/support/components/SupportModal";
|
||||
import { cn } from "@/lib/utils";
|
||||
import { MenuIcon, Package2Icon } from "lucide-react";
|
||||
import { t } from "i18next";
|
||||
import { useCallback } from "react";
|
||||
import { Trans } from "react-i18next";
|
||||
import { Link, useLocation } from "react-router-dom";
|
||||
@ -58,7 +59,7 @@ export const LayoutHeader = () => {
|
||||
<SheetTrigger asChild>
|
||||
<Button variant='outline' size='icon' className='shrink-0 md:hidden'>
|
||||
<MenuIcon className='w-5 h-5' />
|
||||
<span className='sr-only'>Toggle navigation menu</span>
|
||||
<span className='sr-only'>{t("main_menu.toggle_navigation")}</span>
|
||||
</Button>
|
||||
</SheetTrigger>
|
||||
<SheetContent side='left'>
|
||||
|
||||
@ -4,6 +4,7 @@ import { useNavigate } from "react-router-dom";
|
||||
|
||||
import { useToast } from "@/ui/use-toast";
|
||||
import { useQueryKey } from "../useQueryKey";
|
||||
import { t } from "i18next";
|
||||
|
||||
export const useLogout = (params?: UseMutationOptions<AuthActionResponse, Error>) => {
|
||||
const { onSuccess, onError, ...restParams } = params || {};
|
||||
@ -31,7 +32,7 @@ export const useLogout = (params?: UseMutationOptions<AuthActionResponse, Error>
|
||||
onError: (error, variables, context) => {
|
||||
const { message } = error;
|
||||
toast({
|
||||
title: "Error",
|
||||
title: t("common.error"),
|
||||
description: message,
|
||||
variant: "destructive",
|
||||
});
|
||||
|
||||
@ -12,6 +12,7 @@ import {
|
||||
} from "@/ui/alert-dialog";
|
||||
|
||||
import { useCallback, useState } from "react";
|
||||
import { t } from "i18next";
|
||||
|
||||
interface UseRemoveAlertDialogProps {
|
||||
isOpen?: boolean;
|
||||
@ -75,7 +76,7 @@ export const useRemoveAlertDialog = ({
|
||||
<AlertDialogDescription>{description}</AlertDialogDescription>
|
||||
</AlertDialogHeader>
|
||||
<AlertDialogFooter>
|
||||
<AlertDialogCancel onClick={onCancel}>Cancelar</AlertDialogCancel>
|
||||
<AlertDialogCancel onClick={onCancel}>{t("common.cancel")}</AlertDialogCancel>
|
||||
<AlertDialogAction
|
||||
className={cn(
|
||||
buttonVariants({ variant: "destructive" }),
|
||||
@ -83,7 +84,7 @@ export const useRemoveAlertDialog = ({
|
||||
)}
|
||||
onClick={(event) => (onRemove ? onRemove(payload, event) : null)}
|
||||
>
|
||||
Eliminar
|
||||
{t("common.remove")}
|
||||
</AlertDialogAction>
|
||||
</AlertDialogFooter>
|
||||
</AlertDialogContent>
|
||||
|
||||
@ -1,14 +1,12 @@
|
||||
{
|
||||
"translation": {
|
||||
"common": {
|
||||
"loading": "Loading...",
|
||||
"required": "required",
|
||||
"cancel": "Cancel",
|
||||
"no": "No",
|
||||
"yes": "Yes",
|
||||
"save": "Save",
|
||||
"save_close": "Save and Close",
|
||||
"accept": "OK",
|
||||
"discard": "Discard",
|
||||
"hide": "Hide",
|
||||
"back": "Back",
|
||||
@ -16,7 +14,19 @@
|
||||
"continue": "Continue",
|
||||
"close": "Close",
|
||||
"more": "More",
|
||||
"pagination": "Pagination",
|
||||
"breadcrumb": "Breadcrumb",
|
||||
"previous": "Previous",
|
||||
"next": "Next",
|
||||
"more_pages": "More pages",
|
||||
"previous_slide": "Previous slide",
|
||||
"next_slide": "Next slide",
|
||||
"add": "Add",
|
||||
"submit": "Submit",
|
||||
"download": "Download",
|
||||
"help": "Help",
|
||||
"select_all": "Select all",
|
||||
"select_row": "Select row",
|
||||
"sort_asc": "Asc",
|
||||
"sort_asc_description": "In ascending order. Click to sort descending order.",
|
||||
"sort_desc": "Desc",
|
||||
@ -52,21 +62,18 @@
|
||||
"insert_row_below": "Insert row below",
|
||||
"pick_date": "Select a date",
|
||||
"required_field": "This field is required",
|
||||
"unsaved_changes_prompt": "There are unsaved changes. If you leave, you'll lose your changes.",
|
||||
"edit": "Edit",
|
||||
"remove": "Remove",
|
||||
"archive": "Archive",
|
||||
"duplicate": "Duplicate",
|
||||
"print": "Print",
|
||||
"disable_preview": "Disable preview",
|
||||
"enable_preview": "Enable preview",
|
||||
"disable_preview": "Hide preview",
|
||||
"enable_preview": "Show preview",
|
||||
"filter": {
|
||||
"reset_filter": "Reset filter",
|
||||
"badget_remove_tooltip": "Remove this filter term",
|
||||
"input_placeholder": "Type here to filter...",
|
||||
"button_add_term": "Add filter term",
|
||||
"button_add_term_tooltip": "Add filter term (or press Enter)",
|
||||
"button_remove_all": "Remove all terms from filter",
|
||||
"help_text": "Press Enter or click the + button to add a term to the filter. Use multiple terms for a broader search."
|
||||
}
|
||||
},
|
||||
@ -77,6 +84,21 @@
|
||||
"loading_overlay": {
|
||||
"title": "Loading...",
|
||||
"subtitle": "This may take a few seconds. Please do not close this page."
|
||||
},
|
||||
"error_overlay": {
|
||||
"title": "An error occurred",
|
||||
"go_home": "Go home"
|
||||
},
|
||||
"empty_state": {
|
||||
"title": "There is nothing here yet",
|
||||
"subtitle": "Start by requesting the catalog upload",
|
||||
"button": "Request catalog upload"
|
||||
},
|
||||
"data_table": {
|
||||
"columns": "Columns",
|
||||
"no_results": "No results found.",
|
||||
"clear_filters": "Clear filters",
|
||||
"selected_count": "{{count}} selected"
|
||||
}
|
||||
},
|
||||
"hooks": {
|
||||
@ -88,13 +110,11 @@
|
||||
}
|
||||
},
|
||||
"main_menu": {
|
||||
"home": "Home",
|
||||
"toggle_navigation": "Toggle navigation menu",
|
||||
"settings": "Settings",
|
||||
"dealers": "Dealers",
|
||||
"catalog": "Catalog",
|
||||
"quotes": "Quotes",
|
||||
"orders": "Orders",
|
||||
"search_placeholder": "Type here for search quotes and articles",
|
||||
"search_placeholder": "Search products, quotes, etc.",
|
||||
"user": {
|
||||
"user_menu": "User menu",
|
||||
"my_account": "My account",
|
||||
@ -104,14 +124,14 @@
|
||||
"logout": "Logout"
|
||||
},
|
||||
"logout_dialog": {
|
||||
"title": "Confirm",
|
||||
"title": "Log out",
|
||||
"description": "Are you sure you want to log out?",
|
||||
"confirm_label": "Log out",
|
||||
"cancel_label": "Cancel"
|
||||
}
|
||||
},
|
||||
"login_page": {
|
||||
"title": "Partner intranet",
|
||||
"title": "Quoting tool for distributors",
|
||||
"description": "Enter your email address and password to login",
|
||||
"email_label": "Email",
|
||||
"email_placeholder": "myemail@sample.com",
|
||||
@ -127,11 +147,14 @@
|
||||
"catalog": {
|
||||
"list": {
|
||||
"title": "Catalog of articles",
|
||||
"empty_state": {
|
||||
"subtitle": "Start by loading catalog items"
|
||||
},
|
||||
"columns": {
|
||||
"description": "Description",
|
||||
"points": "Points",
|
||||
"retail_price": "Retail price",
|
||||
"quantity": "quantity"
|
||||
"quantity": "Quantity"
|
||||
}
|
||||
}
|
||||
},
|
||||
@ -139,7 +162,18 @@
|
||||
"list": {
|
||||
"title": "Quotes",
|
||||
"subtitle": "",
|
||||
"tabs_title": "Status",
|
||||
"tabs_title": "Statuses",
|
||||
"select_status": "Select status",
|
||||
"preview_aria": "Show quote preview",
|
||||
"empty_state": {
|
||||
"subtitle": "Start by creating a quote"
|
||||
},
|
||||
"status_help": {
|
||||
"title": "Quote status help",
|
||||
"intro": "A quote status indicates its current position in the business process.",
|
||||
"possible": "The possible statuses are:",
|
||||
"outro": "Use these statuses to track quotes efficiently and improve your sales process. The Archived status helps keep history without interfering with active quotes."
|
||||
},
|
||||
"tabs": {
|
||||
"all": "All",
|
||||
"draft": "Draft",
|
||||
@ -156,58 +190,49 @@
|
||||
"status": "Status",
|
||||
"customer_reference": "Customer Ref.",
|
||||
"customer_information": "Customer",
|
||||
"total_price": "Imp. total",
|
||||
"actions": {
|
||||
"edit": "Edit quote",
|
||||
"sent_to": "Send to Uecko"
|
||||
}
|
||||
"total_price": "Total amount"
|
||||
},
|
||||
"resume": {
|
||||
"title": "Quote",
|
||||
"download_quote": "Download quote",
|
||||
"tabs": {
|
||||
"resume": "Resume",
|
||||
"resume": "Summary",
|
||||
"preview": "Preview"
|
||||
},
|
||||
"quote_information": "Quote Information",
|
||||
"customer_information": "Customer Information",
|
||||
"payment_information": "Payment Information",
|
||||
"price_information": "Quote totals"
|
||||
"price_information": "Quote totals",
|
||||
"select_quote": "Select a quote"
|
||||
}
|
||||
},
|
||||
"notifications": {
|
||||
"created": "Quote created",
|
||||
"saved": "Quote saved",
|
||||
"saving": "Saving quote"
|
||||
},
|
||||
"pdf_generation": {
|
||||
"title": "Generating PDF",
|
||||
"error": "Error generating PDF",
|
||||
"progress": "Progress: {{percentage}}%",
|
||||
"elapsed": "Elapsed time: {{elapsed}} seconds",
|
||||
"remaining": "Estimated time remaining: {{estimatedTime}} seconds",
|
||||
"cancel": "Cancel generation"
|
||||
},
|
||||
"create": {
|
||||
"title": "New quote",
|
||||
"tabs": {
|
||||
"general": "General data",
|
||||
"items": "Quote items",
|
||||
"preview": "Quote preview",
|
||||
"documents": "Documents",
|
||||
"history": "History"
|
||||
},
|
||||
"form_groups": {
|
||||
"general": {
|
||||
"title": "General Data",
|
||||
"desc": "General data and quote customer"
|
||||
"desc": "General information and the quote's customer"
|
||||
},
|
||||
"status": {
|
||||
"title": "Status",
|
||||
"desc": "Quote status"
|
||||
},
|
||||
"items": {
|
||||
"title": "Quote Items",
|
||||
"desc": "Quote detail lines. Use the catalog to make it easier to fill in the content."
|
||||
},
|
||||
"documents": {
|
||||
"title": "Attached Documents",
|
||||
"desc": "Attach documents such as photos, drawings, sketches, etc. to your quotation."
|
||||
},
|
||||
"history": {
|
||||
"title": "History",
|
||||
"desc": "Quote history"
|
||||
"desc": "Attach documents such as photos, drawings, sketches, etc. to your quotation.",
|
||||
"images": "Images"
|
||||
}
|
||||
},
|
||||
"edit": {
|
||||
"title": "Quote"
|
||||
}
|
||||
},
|
||||
"edit": {
|
||||
@ -225,8 +250,7 @@
|
||||
"progress": {
|
||||
"label": "Download is in {{status}}",
|
||||
"status_in_progress": "in progress",
|
||||
"status_stopped": "stopped",
|
||||
"value_label": "Elapsed time in seconds {{elapsed}}"
|
||||
"status_stopped": "stopped"
|
||||
},
|
||||
"cancel_button": "Cancel the download",
|
||||
"toast_success": "Quote downloaded"
|
||||
@ -238,9 +262,15 @@
|
||||
},
|
||||
"blocks_picker_dialog": {
|
||||
"title": "Select a text block",
|
||||
"description": "To complete your quote, you can default text blocks.",
|
||||
"description": "To help complete your quote, you can add predefined text blocks from this list.",
|
||||
"toast_article_added": "Text block added:"
|
||||
},
|
||||
"quote_sent_to_editor": {
|
||||
"trigger_button": "Send to Uecko",
|
||||
"title": "Send quote to Uecko",
|
||||
"description": "Do you want to send this quote to Uecko? This action cannot be undone.",
|
||||
"toast_status_changed": "Quote sent to Uecko"
|
||||
},
|
||||
"quote_status_editor": {
|
||||
"trigger_button": "Change quote status",
|
||||
"title": "Change quote status",
|
||||
@ -267,7 +297,7 @@
|
||||
},
|
||||
"archived": {
|
||||
"title": "Archived",
|
||||
"description": "The quote is archived."
|
||||
"description": "The quote has been saved for future reference."
|
||||
}
|
||||
},
|
||||
"submit_button": "Change status",
|
||||
@ -308,11 +338,6 @@
|
||||
"desc": "Quote reference",
|
||||
"placeholder": ""
|
||||
},
|
||||
"status": {
|
||||
"label": "Status",
|
||||
"desc": "Quote status",
|
||||
"placeholder": ""
|
||||
},
|
||||
"lang_code": {
|
||||
"label": "Language",
|
||||
"desc": "Quote language",
|
||||
@ -329,13 +354,13 @@
|
||||
"placeholder": ""
|
||||
},
|
||||
"customer_information": {
|
||||
"label": "Customer's contact data",
|
||||
"label": "Customer details",
|
||||
"desc": "Recommendation: enter the customer's name on the first line, the address on the second line, and the zip code and city/state on the third line.",
|
||||
"placeholder": "Name and surname\nStreet and number\nzip code and city or state..."
|
||||
},
|
||||
"payment_method": {
|
||||
"label": "Payment method",
|
||||
"placeholder": "",
|
||||
"placeholder": "placeholder",
|
||||
"desc": "Method of payment for this quote"
|
||||
},
|
||||
"notes": {
|
||||
@ -364,14 +389,14 @@
|
||||
"desc": "Percentage discount"
|
||||
},
|
||||
"discount_price": {
|
||||
"label": "Discount price",
|
||||
"label": "Discount amount",
|
||||
"placeholder": "",
|
||||
"desc": "Percentage discount price"
|
||||
"desc": "Discount amount"
|
||||
},
|
||||
"before_tax_price": {
|
||||
"label": "Before tax price",
|
||||
"label": "Taxable amount",
|
||||
"placeholder": "",
|
||||
"desc": "Before tax price"
|
||||
"desc": "Taxable amount"
|
||||
},
|
||||
"tax": {
|
||||
"label": "Tax (%)",
|
||||
@ -386,7 +411,7 @@
|
||||
"tax_price": {
|
||||
"label": "Tax amount",
|
||||
"placeholder": "",
|
||||
"desc": "Percentage tax price"
|
||||
"desc": "Tax amount"
|
||||
},
|
||||
"total_price": {
|
||||
"label": "Total price",
|
||||
@ -415,7 +440,7 @@
|
||||
"desc": ""
|
||||
},
|
||||
"discount": {
|
||||
"label": "Dto (%)",
|
||||
"label": "Discount (%)",
|
||||
"placeholder": "",
|
||||
"desc": "Percentage discount"
|
||||
},
|
||||
@ -430,7 +455,6 @@
|
||||
"settings": {
|
||||
"edit": {
|
||||
"title": "Settings",
|
||||
"subtitle": "",
|
||||
"tabs": {
|
||||
"profile": "Profile settings",
|
||||
"quotes": "Quote settings",
|
||||
@ -441,12 +465,19 @@
|
||||
"language": {
|
||||
"label": "Language",
|
||||
"placeholder": "Select a language",
|
||||
"desc": "Preferred language for the interface"
|
||||
"desc": "Preferred language for the interface",
|
||||
"options": {
|
||||
"es": "Spanish",
|
||||
"en": "English"
|
||||
}
|
||||
},
|
||||
"image": {
|
||||
"label": "Logotype",
|
||||
"placeholder": "",
|
||||
"desc": ""
|
||||
"logo": {
|
||||
"label": "Logo",
|
||||
"desc": "This logo will appear in PDF-exported proposals",
|
||||
"requirements": "JPG or PNG files are allowed. The logo dimensions must be 200x100 pixels or proportional.",
|
||||
"upload": "Upload logo",
|
||||
"invalid_format": "Only JPG or PNG files are allowed",
|
||||
"invalid_dimensions": "The logo dimensions must be 200x100 pixels or proportional."
|
||||
},
|
||||
"contact_information": {
|
||||
"label": "Your contact information",
|
||||
@ -459,7 +490,6 @@
|
||||
"desc": "Default tax rate for your quotes"
|
||||
},
|
||||
"default_tax_label": {
|
||||
"label": "Tax label",
|
||||
"placeholder": "% VAT",
|
||||
"desc": "Name or label shown alongside the tax"
|
||||
},
|
||||
@ -470,7 +500,7 @@
|
||||
"default_legal_terms": {
|
||||
"label": "Legal terms",
|
||||
"placeholder": "",
|
||||
"desc": "Legal information to be included at the end of your quotes"
|
||||
"desc": "Legal text to include in all new quotes"
|
||||
},
|
||||
"default_payment_method": {
|
||||
"label": "Payment method",
|
||||
@ -487,7 +517,39 @@
|
||||
"placeholder": "",
|
||||
"desc": "Default validity time to be used for new quotes"
|
||||
}
|
||||
}
|
||||
},
|
||||
"notifications": {
|
||||
"saved": "Settings saved",
|
||||
"saving": "Saving settings"
|
||||
}
|
||||
},
|
||||
"support": {
|
||||
"modal": {
|
||||
"title": "Send an issue",
|
||||
"subtitle": "Use this form to report any problem you encountered while using the application. Our development team will review your issue and try to resolve it.",
|
||||
"open": "Open support window",
|
||||
"placeholder": "Describe the issue here...",
|
||||
"submit": "Send issue",
|
||||
"validation": {
|
||||
"empty": "Write something before sending",
|
||||
"min": "The text is too short. It must contain at least 10 characters",
|
||||
"max": "The text is too long.",
|
||||
"required": "A description is required"
|
||||
},
|
||||
"notifications": {
|
||||
"sent_title": "Issue sent",
|
||||
"sent_description": "The issue was sent successfully",
|
||||
"error_title": "Sending error",
|
||||
"error_description": "The issue could not be sent. Please try again."
|
||||
},
|
||||
"cancel_confirmation": {
|
||||
"title": "Are you sure you want to cancel?",
|
||||
"description": "You have entered text in the description field. Closing the window will discard your unsaved changes.",
|
||||
"back": "Return to form",
|
||||
"confirm": "Yes, close"
|
||||
}
|
||||
},
|
||||
"form_fields": {}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@ -1,14 +1,12 @@
|
||||
{
|
||||
"translation": {
|
||||
"common": {
|
||||
"loading": "Cargando...",
|
||||
"required": "obligatorio",
|
||||
"cancel": "Cancelar",
|
||||
"no": "No",
|
||||
"yes": "Sí",
|
||||
"save": "Guardar",
|
||||
"save_close": "Guardar y Cerrar",
|
||||
"accept": "Aceptar",
|
||||
"discard": "Descartar",
|
||||
"hide": "Ocultar",
|
||||
"back": "Volver",
|
||||
@ -16,7 +14,19 @@
|
||||
"continue": "Continuar",
|
||||
"close": "Cerrar",
|
||||
"more": "More",
|
||||
"pagination": "Paginación",
|
||||
"breadcrumb": "Migas de pan",
|
||||
"previous": "Anterior",
|
||||
"next": "Siguiente",
|
||||
"more_pages": "Más páginas",
|
||||
"previous_slide": "Diapositiva anterior",
|
||||
"next_slide": "Diapositiva siguiente",
|
||||
"add": "Añadir",
|
||||
"submit": "Enviar",
|
||||
"download": "Descargar",
|
||||
"help": "Ayuda",
|
||||
"select_all": "Seleccionar todo",
|
||||
"select_row": "Seleccionar fila",
|
||||
"sort_asc": "Asc",
|
||||
"sort_asc_description": "En order ascendente. Click para ordenar descendentemente.",
|
||||
"sort_desc": "Desc",
|
||||
@ -52,12 +62,10 @@
|
||||
"insert_row_below": "Insertar fila debajo",
|
||||
"pick_date": "Elige una fecha",
|
||||
"required_field": "Este campo es obligatorio",
|
||||
"unsaved_changes_prompt": "Los últimos cambios no se han guardado. Si continúas, se perderán.",
|
||||
"edit": "Editar",
|
||||
"remove": "Eliminar",
|
||||
"archive": "Archivar",
|
||||
"duplicate": "Duplicar",
|
||||
"print": "Imprimir",
|
||||
"disable_preview": "Ocultar vista previa",
|
||||
"enable_preview": "Mostrar vista previa",
|
||||
"filter": {
|
||||
@ -66,17 +74,31 @@
|
||||
"input_placeholder": "Escribe aquí para filtrar...",
|
||||
"button_add_term": "Añadir término al filtro",
|
||||
"button_add_term_tooltip": "Añadir término al filtro (o pulsa Enter)",
|
||||
"button_remove_all": "Quitar todos los términos del filtro",
|
||||
"help_text": "Presiona Enter o haz clic en el botón + para añadir un término al filtro. Usa múltiples términos para una búsqueda más amplia."
|
||||
}
|
||||
},
|
||||
"components": {
|
||||
"LoadingIndicator": {
|
||||
"loading_indicator": {
|
||||
"title": "Cargando..."
|
||||
},
|
||||
"loading_overlay": {
|
||||
"title": "Cargando...",
|
||||
"subtitle": "Esto puede tardar unos segundos. Por favor, no cierre esta página."
|
||||
},
|
||||
"error_overlay": {
|
||||
"title": "Se ha producido un error",
|
||||
"go_home": "Ir al inicio"
|
||||
},
|
||||
"empty_state": {
|
||||
"title": "Esto está muy vacío",
|
||||
"subtitle": "Empieza solicitando la carga del catálogo",
|
||||
"button": "Solicitar carga del catálogo"
|
||||
},
|
||||
"data_table": {
|
||||
"columns": "Columnas",
|
||||
"no_results": "No se han encontrado resultados.",
|
||||
"clear_filters": "Limpiar filtros",
|
||||
"selected_count": "{{count}} seleccionados"
|
||||
}
|
||||
},
|
||||
"hooks": {
|
||||
@ -88,12 +110,10 @@
|
||||
}
|
||||
},
|
||||
"main_menu": {
|
||||
"home": "Inicio",
|
||||
"toggle_navigation": "Alternar el menú de navegación",
|
||||
"settings": "Ajustes",
|
||||
"dealers": "Distribuidores",
|
||||
"catalog": "Catálogo",
|
||||
"quotes": "Cotizaciones",
|
||||
"orders": "Pedidos",
|
||||
"search_placeholder": "Buscar productos, cotizaciones, etc...",
|
||||
"user": {
|
||||
"user_menu": "Menú del usuario",
|
||||
@ -127,6 +147,9 @@
|
||||
"catalog": {
|
||||
"list": {
|
||||
"title": "Catálogo de artículos",
|
||||
"empty_state": {
|
||||
"subtitle": "Empieza cargando los artículos del catálogo"
|
||||
},
|
||||
"columns": {
|
||||
"description": "Descripción",
|
||||
"points": "Puntos",
|
||||
@ -140,6 +163,17 @@
|
||||
"title": "Cotizaciones",
|
||||
"subtitle": "",
|
||||
"tabs_title": "Estados",
|
||||
"select_status": "Seleccionar estado",
|
||||
"preview_aria": "Mostrar vista previa de la cotización",
|
||||
"empty_state": {
|
||||
"subtitle": "Empieza creando alguna cotización"
|
||||
},
|
||||
"status_help": {
|
||||
"title": "Ayuda sobre el estado de las cotizaciones",
|
||||
"intro": "El estado de una cotización indica su posición actual en el proceso de negocio.",
|
||||
"possible": "Los estados posibles son:",
|
||||
"outro": "Utiliza estos estados para hacer un seguimiento eficiente de tus cotizaciones y optimizar tu proceso de ventas. El estado Archivado ayuda a conservar el historial sin interferir con las cotizaciones activas."
|
||||
},
|
||||
"tabs": {
|
||||
"all": "Todas",
|
||||
"draft": "Borradores",
|
||||
@ -156,34 +190,35 @@
|
||||
"status": "Estado",
|
||||
"customer_reference": "Ref. cliente",
|
||||
"customer_information": "Cliente",
|
||||
"total_price": "Imp. total",
|
||||
"actions": {
|
||||
"edit": "Editar",
|
||||
"sent_to": "Enviar a Uecko"
|
||||
}
|
||||
"total_price": "Imp. total"
|
||||
},
|
||||
"resume": {
|
||||
"title": "Cotización",
|
||||
"download_quote": "Descargar",
|
||||
"tabs": {
|
||||
"resume": "Resumen",
|
||||
"preview": "Previsualización"
|
||||
},
|
||||
"quote_information": "Información",
|
||||
"customer_information": "Cliente",
|
||||
"payment_information": "Pago",
|
||||
"price_information": "Importes"
|
||||
"price_information": "Importes",
|
||||
"select_quote": "Selecciona una cotización"
|
||||
}
|
||||
},
|
||||
"notifications": {
|
||||
"created": "Cotización creada",
|
||||
"saved": "Cotización guardada",
|
||||
"saving": "Guardando cotización"
|
||||
},
|
||||
"pdf_generation": {
|
||||
"title": "Generando PDF",
|
||||
"error": "Error al generar el PDF",
|
||||
"progress": "Progreso: {{percentage}}%",
|
||||
"elapsed": "Tiempo transcurrido: {{elapsed}} segundos",
|
||||
"remaining": "Tiempo estimado restante: {{estimatedTime}} segundos",
|
||||
"cancel": "Cancelar generación"
|
||||
},
|
||||
"create": {
|
||||
"title": "Nueva cotización",
|
||||
"tabs": {
|
||||
"general": "Datos generales",
|
||||
"items": "Contenido",
|
||||
"preview": "Vista previa",
|
||||
"documents": "Documentos",
|
||||
"history": "Historial"
|
||||
},
|
||||
"form_groups": {
|
||||
"general": {
|
||||
"title": "Datos generales",
|
||||
@ -193,21 +228,11 @@
|
||||
"title": "Estado",
|
||||
"desc": "Estado de la cotización"
|
||||
},
|
||||
"items": {
|
||||
"title": "Contenido de la cotización",
|
||||
"desc": "Líneas de detalle de la cotización. Ayúdese del catálogo para rellenar más fácilmente el contenido."
|
||||
},
|
||||
"documents": {
|
||||
"title": "Documentos",
|
||||
"desc": "Añada adjuntar con su cotización documentos como fotos, planos, croquis, etc."
|
||||
},
|
||||
"history": {
|
||||
"title": "Historial",
|
||||
"desc": "Historial de la cotización"
|
||||
"desc": "Añada adjuntar con su cotización documentos como fotos, planos, croquis, etc.",
|
||||
"images": "Imágenes"
|
||||
}
|
||||
},
|
||||
"edit": {
|
||||
"title": "Cotización"
|
||||
}
|
||||
},
|
||||
"edit": {
|
||||
@ -225,8 +250,7 @@
|
||||
"progress": {
|
||||
"label": "La descarga está {{status}}",
|
||||
"status_in_progress": "en marcha",
|
||||
"status_stopped": "parada",
|
||||
"value_label": "Elapsed time in seconds {{elapsed}}"
|
||||
"status_stopped": "parada"
|
||||
},
|
||||
"cancel_button": "Cancelar la descarga",
|
||||
"toast_success": "Cotización descargada"
|
||||
@ -245,7 +269,6 @@
|
||||
"trigger_button": "Enviar a Uecko",
|
||||
"title": "Enviar la cotización a Uecko",
|
||||
"description": "¿Desea enviar esta cotización a Uecko? Esta acción no se puede deshacer.",
|
||||
"submit_button": "Enviar",
|
||||
"toast_status_changed": "Cotización enviada a Uecko"
|
||||
},
|
||||
"quote_status_editor": {
|
||||
@ -432,7 +455,6 @@
|
||||
"settings": {
|
||||
"edit": {
|
||||
"title": "Ajustes",
|
||||
"subtitle": "",
|
||||
"tabs": {
|
||||
"profile": "Ajustes de perfil",
|
||||
"quotes": "Ajustes para cotizaciones",
|
||||
@ -443,11 +465,14 @@
|
||||
"language": {
|
||||
"label": "Idioma",
|
||||
"placeholder": "Selecciona un idioma",
|
||||
"desc": "Idioma preferido para la interfaz"
|
||||
"desc": "Idioma preferido para la interfaz",
|
||||
"options": {
|
||||
"es": "Español",
|
||||
"en": "Inglés"
|
||||
}
|
||||
},
|
||||
"logo": {
|
||||
"label": "Logotipo",
|
||||
"placeholder": "",
|
||||
"desc": "Este logotipo aparecerá en las propuestas exportadas en PDF",
|
||||
"requirements": "Se permiten ficheros JPG o PNG. Las dimensiones del logotipo deben ser 200x100 píxeles o proporcionales.",
|
||||
"upload": "Subir logotipo",
|
||||
@ -465,7 +490,6 @@
|
||||
"desc": "Porcentaje de impuesto por defecto en las cotizaciones"
|
||||
},
|
||||
"default_tax_label": {
|
||||
"label": "Etiqueta del impuesto",
|
||||
"placeholder": "% IVA",
|
||||
"desc": "Nombre o etiqueta que se mostrará junto al impuesto"
|
||||
},
|
||||
@ -493,12 +517,37 @@
|
||||
"placeholder": "",
|
||||
"desc": "Texto para indicar el tiempo de validez de la cotización"
|
||||
}
|
||||
},
|
||||
"notifications": {
|
||||
"saved": "Ajustes guardados",
|
||||
"saving": "Guardando ajustes"
|
||||
}
|
||||
},
|
||||
"support": {
|
||||
"modal": {
|
||||
"title": "Enviar una incidencia",
|
||||
"subtitle": "Utiliza este formulario para informar sobre cualquier problema que hayas encontrado mientras usabas la aplicación. Nuestro equipo de desarrollo revisará tu incidencia y tratará de resolverla."
|
||||
"subtitle": "Utiliza este formulario para informar sobre cualquier problema que hayas encontrado mientras usabas la aplicación. Nuestro equipo de desarrollo revisará tu incidencia y tratará de resolverla.",
|
||||
"open": "Abrir ventana de soporte",
|
||||
"placeholder": "Describe la incidencia aquí...",
|
||||
"submit": "Enviar incidencia",
|
||||
"validation": {
|
||||
"empty": "Debe escribir algo antes de enviar",
|
||||
"min": "El texto es demasiado corto. Debe tener al menos 10 caracteres",
|
||||
"max": "El texto es demasiado largo.",
|
||||
"required": "La descripción es requerida"
|
||||
},
|
||||
"notifications": {
|
||||
"sent_title": "Incidencia enviada",
|
||||
"sent_description": "La incidencia se ha enviado correctamente",
|
||||
"error_title": "Error en el envío",
|
||||
"error_description": "No se ha podido enviar la incidencia correctamente. Por favor, inténtalo de nuevo."
|
||||
},
|
||||
"cancel_confirmation": {
|
||||
"title": "¿Estás seguro de que quieres cancelar?",
|
||||
"description": "Has escrito texto en el campo de descripción. Si cierras la ventana, perderás los cambios no guardados.",
|
||||
"back": "Volver al formulario",
|
||||
"confirm": "Sí, cerrar"
|
||||
}
|
||||
},
|
||||
"form_fields": {}
|
||||
}
|
||||
|
||||
@ -1,6 +1,7 @@
|
||||
import * as React from "react"
|
||||
import { Slot } from "@radix-ui/react-slot"
|
||||
import { ChevronRight, MoreHorizontal } from "lucide-react"
|
||||
import { t } from "i18next"
|
||||
|
||||
import { cn } from "@/lib/utils"
|
||||
|
||||
@ -9,7 +10,7 @@ const Breadcrumb = React.forwardRef<
|
||||
React.ComponentPropsWithoutRef<"nav"> & {
|
||||
separator?: React.ReactNode
|
||||
}
|
||||
>(({ ...props }, ref) => <nav ref={ref} aria-label="breadcrumb" {...props} />)
|
||||
>(({ ...props }, ref) => <nav ref={ref} aria-label={t("common.breadcrumb")} {...props} />)
|
||||
Breadcrumb.displayName = "Breadcrumb"
|
||||
|
||||
const BreadcrumbList = React.forwardRef<
|
||||
@ -99,7 +100,7 @@ const BreadcrumbEllipsis = ({
|
||||
{...props}
|
||||
>
|
||||
<MoreHorizontal className="h-4 w-4" />
|
||||
<span className="sr-only">More</span>
|
||||
<span className="sr-only">{t("common.more")}</span>
|
||||
</span>
|
||||
)
|
||||
BreadcrumbEllipsis.displayName = "BreadcrumbElipssis"
|
||||
|
||||
@ -3,6 +3,7 @@ import useEmblaCarousel, {
|
||||
} from "embla-carousel-react";
|
||||
import { ArrowLeft, ArrowRight } from "lucide-react";
|
||||
import * as React from "react";
|
||||
import { t } from "i18next";
|
||||
|
||||
import { cn } from "@/lib/utils";
|
||||
import { Button } from "./button";
|
||||
@ -215,7 +216,7 @@ const CarouselPrevious = React.forwardRef<
|
||||
{...props}
|
||||
>
|
||||
<ArrowLeft className="w-4 h-4" />
|
||||
<span className="sr-only">Previous slide</span>
|
||||
<span className="sr-only">{t("common.previous_slide")}</span>
|
||||
</Button>
|
||||
);
|
||||
});
|
||||
@ -244,7 +245,7 @@ const CarouselNext = React.forwardRef<
|
||||
{...props}
|
||||
>
|
||||
<ArrowRight className="w-4 h-4" />
|
||||
<span className="sr-only">Next slide</span>
|
||||
<span className="sr-only">{t("common.next_slide")}</span>
|
||||
</Button>
|
||||
);
|
||||
});
|
||||
|
||||
@ -1,5 +1,6 @@
|
||||
import * as React from "react"
|
||||
import * as DialogPrimitive from "@radix-ui/react-dialog"
|
||||
import { t } from "i18next"
|
||||
import { X } from "lucide-react"
|
||||
|
||||
import { cn } from "@/lib/utils"
|
||||
@ -44,7 +45,7 @@ const DialogContent = React.forwardRef<
|
||||
{children}
|
||||
<DialogPrimitive.Close className="absolute right-4 top-4 rounded-sm opacity-70 ring-offset-background transition-opacity hover:opacity-100 focus:outline-none focus:ring-2 focus:ring-ring focus:ring-offset-2 disabled:pointer-events-none data-[state=open]:bg-accent data-[state=open]:text-muted-foreground">
|
||||
<X className="h-4 w-4" />
|
||||
<span className="sr-only">Close</span>
|
||||
<span className="sr-only">{t("common.close")}</span>
|
||||
</DialogPrimitive.Close>
|
||||
</DialogPrimitive.Content>
|
||||
</DialogPortal>
|
||||
|
||||
@ -1,4 +1,5 @@
|
||||
import { ChevronLeft, ChevronRight, MoreHorizontal } from "lucide-react";
|
||||
import { t } from "i18next";
|
||||
import * as React from "react";
|
||||
|
||||
import { cn } from "@/lib/utils";
|
||||
@ -7,7 +8,7 @@ import { ButtonProps, buttonVariants } from "./button";
|
||||
const Pagination = ({ className, ...props }: React.ComponentProps<"nav">) => (
|
||||
<nav
|
||||
role="navigation"
|
||||
aria-label="pagination"
|
||||
aria-label={t("common.pagination")}
|
||||
className={cn("mx-auto flex w-full justify-center", className)}
|
||||
{...props}
|
||||
/>
|
||||
@ -64,13 +65,13 @@ const PaginationPrevious = ({
|
||||
...props
|
||||
}: React.ComponentProps<typeof PaginationLink>) => (
|
||||
<PaginationLink
|
||||
aria-label="Go to previous page"
|
||||
aria-label={t("common.go_to_prev_page")}
|
||||
size="default"
|
||||
className={cn("gap-1 pl-2.5", className)}
|
||||
{...props}
|
||||
>
|
||||
<ChevronLeft className="w-4 h-4" />
|
||||
<span>Previous</span>
|
||||
<span>{t("common.previous")}</span>
|
||||
</PaginationLink>
|
||||
);
|
||||
PaginationPrevious.displayName = "PaginationPrevious";
|
||||
@ -80,12 +81,12 @@ const PaginationNext = ({
|
||||
...props
|
||||
}: React.ComponentProps<typeof PaginationLink>) => (
|
||||
<PaginationLink
|
||||
aria-label="Go to next page"
|
||||
aria-label={t("common.go_to_next_page")}
|
||||
size="default"
|
||||
className={cn("gap-1 pr-2.5", className)}
|
||||
{...props}
|
||||
>
|
||||
<span>Next</span>
|
||||
<span>{t("common.next")}</span>
|
||||
<ChevronRight className="w-4 h-4" />
|
||||
</PaginationLink>
|
||||
);
|
||||
@ -101,7 +102,7 @@ const PaginationEllipsis = ({
|
||||
{...props}
|
||||
>
|
||||
<MoreHorizontal className="w-4 h-4" />
|
||||
<span className="sr-only">More pages</span>
|
||||
<span className="sr-only">{t("common.more_pages")}</span>
|
||||
</span>
|
||||
);
|
||||
PaginationEllipsis.displayName = "PaginationEllipsis";
|
||||
|
||||
@ -1,5 +1,6 @@
|
||||
import * as React from "react"
|
||||
import * as SheetPrimitive from "@radix-ui/react-dialog"
|
||||
import { t } from "i18next"
|
||||
import { cva, type VariantProps } from "class-variance-authority"
|
||||
import { X } from "lucide-react"
|
||||
|
||||
@ -65,7 +66,7 @@ const SheetContent = React.forwardRef<
|
||||
{children}
|
||||
<SheetPrimitive.Close className="absolute right-4 top-4 rounded-sm opacity-70 ring-offset-background transition-opacity hover:opacity-100 focus:outline-none focus:ring-2 focus:ring-ring focus:ring-offset-2 disabled:pointer-events-none data-[state=open]:bg-secondary">
|
||||
<X className="h-4 w-4" />
|
||||
<span className="sr-only">Close</span>
|
||||
<span className="sr-only">{t("common.close")}</span>
|
||||
</SheetPrimitive.Close>
|
||||
</SheetPrimitive.Content>
|
||||
</SheetPortal>
|
||||
|
||||
Loading…
Reference in New Issue
Block a user