chore: add translations

This commit is contained in:
Ephraim Atta-Duncan
2025-06-05 12:31:53 +00:00
parent de45a63c97
commit 64695fad32
7 changed files with 68 additions and 46 deletions

View File

@ -91,7 +91,7 @@ export function DocumentsDataTable({
const columns = useMemo(() => { const columns = useMemo(() => {
return [ return [
{ {
header: 'Created', header: _(msg`Created`),
accessorKey: 'createdAt', accessorKey: 'createdAt',
cell: ({ row }) => cell: ({ row }) =>
i18n.date(row.original.createdAt, { ...DateTime.DATETIME_SHORT, hourCycle: 'h12' }), i18n.date(row.original.createdAt, { ...DateTime.DATETIME_SHORT, hourCycle: 'h12' }),

View File

@ -1,3 +1,5 @@
import { msg } from '@lingui/core/macro';
import { useLingui } from '@lingui/react';
import { Plural, Trans } from '@lingui/react/macro'; import { Plural, Trans } from '@lingui/react/macro';
import type { Table } from '@tanstack/react-table'; import type { Table } from '@tanstack/react-table';
import { ChevronLeft, ChevronRight, ChevronsLeft, ChevronsRight } from 'lucide-react'; import { ChevronLeft, ChevronRight, ChevronsLeft, ChevronsRight } from 'lucide-react';
@ -21,6 +23,8 @@ export function DataTablePagination<TData>({
table, table,
additionalInformation = 'VisibleCount', additionalInformation = 'VisibleCount',
}: DataTablePaginationProps<TData>) { }: DataTablePaginationProps<TData>) {
const { _ } = useLingui();
return ( return (
<div className="flex flex-wrap items-center justify-between gap-x-4 gap-y-4 px-2"> <div className="flex flex-wrap items-center justify-between gap-x-4 gap-y-4 px-2">
<div className="text-muted-foreground flex-1 text-sm"> <div className="text-muted-foreground flex-1 text-sm">
@ -86,7 +90,7 @@ export function DataTablePagination<TData>({
onClick={() => table.setPageIndex(0)} onClick={() => table.setPageIndex(0)}
disabled={!table.getCanPreviousPage()} disabled={!table.getCanPreviousPage()}
> >
<span className="sr-only">Go to first page</span> <span className="sr-only">{_(msg`Go to first page`)}</span>
<ChevronsLeft className="h-4 w-4" /> <ChevronsLeft className="h-4 w-4" />
</Button> </Button>
<Button <Button
@ -95,7 +99,7 @@ export function DataTablePagination<TData>({
onClick={() => table.previousPage()} onClick={() => table.previousPage()}
disabled={!table.getCanPreviousPage()} disabled={!table.getCanPreviousPage()}
> >
<span className="sr-only">Go to previous page</span> <span className="sr-only">{_(msg`Go to previous page`)}</span>
<ChevronLeft className="h-4 w-4" /> <ChevronLeft className="h-4 w-4" />
</Button> </Button>
<Button <Button
@ -104,7 +108,7 @@ export function DataTablePagination<TData>({
onClick={() => table.nextPage()} onClick={() => table.nextPage()}
disabled={!table.getCanNextPage()} disabled={!table.getCanNextPage()}
> >
<span className="sr-only">Go to next page</span> <span className="sr-only">{_(msg`Go to next page`)}</span>
<ChevronRight className="h-4 w-4" /> <ChevronRight className="h-4 w-4" />
</Button> </Button>
<Button <Button
@ -113,7 +117,7 @@ export function DataTablePagination<TData>({
onClick={() => table.setPageIndex(table.getPageCount() - 1)} onClick={() => table.setPageIndex(table.getPageCount() - 1)}
disabled={!table.getCanNextPage()} disabled={!table.getCanNextPage()}
> >
<span className="sr-only">Go to last page</span> <span className="sr-only">{_(msg`Go to last page`)} </span>
<ChevronsRight className="h-4 w-4" /> <ChevronsRight className="h-4 w-4" />
</Button> </Button>
</div> </div>

View File

@ -1,5 +1,8 @@
import * as React from 'react'; import * as React from 'react';
import type { MessageDescriptor } from '@lingui/core';
import { msg } from '@lingui/core/macro';
import { useLingui } from '@lingui/react';
import type { Column } from '@tanstack/react-table'; import type { Column } from '@tanstack/react-table';
import { Check } from 'lucide-react'; import { Check } from 'lucide-react';
@ -18,7 +21,7 @@ interface DataTableFacetedFilterProps<TData, TValue> {
onFilterChange?: (values: string[]) => void; onFilterChange?: (values: string[]) => void;
selectedValues?: string[]; selectedValues?: string[];
options: { options: {
label: string; label: MessageDescriptor;
value: string; value: string;
icon?: React.ComponentType<{ className?: string }>; icon?: React.ComponentType<{ className?: string }>;
color?: string; color?: string;
@ -35,6 +38,7 @@ export function DataTableFacetedFilter<TData, TValue>({
selectedValues, selectedValues,
options, options,
}: DataTableFacetedFilterProps<TData, TValue>) { }: DataTableFacetedFilterProps<TData, TValue>) {
const { _ } = useLingui();
const facets = column?.getFacetedUniqueValues(); const facets = column?.getFacetedUniqueValues();
const selectedValuesSet = new Set(selectedValues || (column?.getFilterValue() as string[])); const selectedValuesSet = new Set(selectedValues || (column?.getFilterValue() as string[]));
@ -53,7 +57,7 @@ export function DataTableFacetedFilter<TData, TValue>({
<div className="hidden gap-1 lg:flex"> <div className="hidden gap-1 lg:flex">
{selectedValuesSet.size > 2 ? ( {selectedValuesSet.size > 2 ? (
<Badge variant="neutral" className="rounded-sm px-2 py-0.5 font-normal"> <Badge variant="neutral" className="rounded-sm px-2 py-0.5 font-normal">
{selectedValuesSet.size} selected {selectedValuesSet.size} {_(msg`selected`)}
</Badge> </Badge>
) : ( ) : (
options options
@ -67,7 +71,7 @@ export function DataTableFacetedFilter<TData, TValue>({
option.bgColor ? option.bgColor : 'bg-secondary', option.bgColor ? option.bgColor : 'bg-secondary',
)} )}
> >
{option.label} {_(option.label)}
</Badge> </Badge>
)) ))
)} )}
@ -79,7 +83,7 @@ export function DataTableFacetedFilter<TData, TValue>({
<PopoverContent className="w-[200px] p-0" align="start"> <PopoverContent className="w-[200px] p-0" align="start">
<Command> <Command>
<CommandList> <CommandList>
<CommandEmpty>No results found.</CommandEmpty> <CommandEmpty>{_(msg`No results found.`)}</CommandEmpty>
<CommandGroup> <CommandGroup>
{options.map((option) => { {options.map((option) => {
const isSelected = selectedValuesSet.has(option.value); const isSelected = selectedValuesSet.has(option.value);
@ -120,7 +124,7 @@ export function DataTableFacetedFilter<TData, TValue>({
)} )}
/> />
)} )}
<span>{option.label}</span> <span>{_(option.label)}</span>
{(stats?.[option.value] || facets?.get(option.value)) && ( {(stats?.[option.value] || facets?.get(option.value)) && (
<span className="text-muted-foreground ml-auto flex size-4 items-center justify-center font-mono text-xs"> <span className="text-muted-foreground ml-auto flex size-4 items-center justify-center font-mono text-xs">
{stats?.[option.value] || facets?.get(option.value)} {stats?.[option.value] || facets?.get(option.value)}

View File

@ -1,5 +1,7 @@
import * as React from 'react'; import * as React from 'react';
import type { MessageDescriptor } from '@lingui/core';
import { useLingui } from '@lingui/react';
import type { Column } from '@tanstack/react-table'; import type { Column } from '@tanstack/react-table';
import { cn } from '../../lib/utils'; import { cn } from '../../lib/utils';
@ -22,14 +24,14 @@ interface DataTableSingleFilterProps<TData, TValue> {
onFilterChange?: (values: string[]) => void; onFilterChange?: (values: string[]) => void;
selectedValues?: string[]; selectedValues?: string[];
options: { options: {
label: string; label: MessageDescriptor;
value: string; value: string;
icon?: React.ComponentType<{ className?: string }>; icon?: React.ComponentType<{ className?: string }>;
color?: string; color?: string;
bgColor?: string; bgColor?: string;
}[]; }[];
groups?: { groups?: {
label: string; label: MessageDescriptor;
values: string[]; values: string[];
}[]; }[];
} }
@ -43,6 +45,7 @@ export function DataTableSingleFilter<TData, TValue>({
onFilterChange, onFilterChange,
selectedValues, selectedValues,
}: DataTableSingleFilterProps<TData, TValue>) { }: DataTableSingleFilterProps<TData, TValue>) {
const { _ } = useLingui();
const filterValue = column?.getFilterValue() as string[] | undefined; const filterValue = column?.getFilterValue() as string[] | undefined;
const selectedValue = selectedValues?.[0] || (filterValue?.[0] ?? undefined); const selectedValue = selectedValues?.[0] || (filterValue?.[0] ?? undefined);
const selectedOption = options.find((option) => option.value === selectedValue); const selectedOption = options.find((option) => option.value === selectedValue);
@ -66,9 +69,9 @@ export function DataTableSingleFilter<TData, TValue>({
const renderOptions = () => { const renderOptions = () => {
if (groups) { if (groups) {
return groups.map((group, groupIndex) => ( return groups.map((group, groupIndex) => (
<React.Fragment key={group.label}> <React.Fragment key={JSON.stringify(group.label)}>
<SelectGroup> <SelectGroup>
<SelectLabel>{group.label}</SelectLabel> <SelectLabel>{_(group.label)}</SelectLabel>
{options {options
.filter((option) => group.values.includes(option.value)) .filter((option) => group.values.includes(option.value))
.map((option) => ( .map((option) => (
@ -82,7 +85,7 @@ export function DataTableSingleFilter<TData, TValue>({
)} )}
/> />
)} )}
<span>{option.label}</span> <span>{_(option.label)}</span>
</div> </div>
</SelectItem> </SelectItem>
))} ))}
@ -102,7 +105,7 @@ export function DataTableSingleFilter<TData, TValue>({
className={cn('size-4', option.color ? option.color : 'text-muted-foreground')} className={cn('size-4', option.color ? option.color : 'text-muted-foreground')}
/> />
)} )}
<span>{option.label}</span> <span>{_(option.label)}</span>
</div> </div>
</SelectItem> </SelectItem>
))} ))}
@ -125,7 +128,7 @@ export function DataTableSingleFilter<TData, TValue>({
selectedOption.bgColor ? selectedOption.bgColor : 'variant-secondary', selectedOption.bgColor ? selectedOption.bgColor : 'variant-secondary',
)} )}
> >
{selectedOption.label} {_(selectedOption.label)}
</Badge> </Badge>
</> </>
)} )}

View File

@ -1,3 +1,5 @@
import { msg } from '@lingui/core/macro';
import { useLingui } from '@lingui/react';
import type { Table } from '@tanstack/react-table'; import type { Table } from '@tanstack/react-table';
import { Calendar, CircleDashedIcon, Globe, ListFilter, X, XCircle } from 'lucide-react'; import { Calendar, CircleDashedIcon, Globe, ListFilter, X, XCircle } from 'lucide-react';
@ -36,6 +38,7 @@ export function DataTableToolbar<TData>({
isTimePeriodFiltered, isTimePeriodFiltered,
isSourceFiltered, isSourceFiltered,
}: DataTableToolbarProps<TData>) { }: DataTableToolbarProps<TData>) {
const { _ } = useLingui();
const isFiltered = const isFiltered =
table.getState().columnFilters.length > 0 || table.getState().columnFilters.length > 0 ||
isStatusFiltered || isStatusFiltered ||
@ -58,7 +61,7 @@ export function DataTableToolbar<TData>({
<div className="relative"> <div className="relative">
<Input <Input
className="peer h-8 w-[150px] pe-9 ps-9 lg:w-[250px]" className="peer h-8 w-[150px] pe-9 ps-9 lg:w-[250px]"
placeholder="Search documents..." placeholder={_(msg`Search documents...`)}
value={searchValue} value={searchValue}
onChange={(event) => table.getColumn('title')?.setFilterValue(event.target.value)} onChange={(event) => table.getColumn('title')?.setFilterValue(event.target.value)}
/> />
@ -68,7 +71,7 @@ export function DataTableToolbar<TData>({
{searchValue && ( {searchValue && (
<button <button
className="text-muted-foreground/80 hover:text-foreground focus-visible:border-ring focus-visible:ring-ring/50 absolute inset-y-0 end-0 flex h-full w-9 items-center justify-center rounded-e-md outline-none transition-[color,box-shadow] focus:z-10 focus-visible:ring-[3px] disabled:pointer-events-none disabled:cursor-not-allowed disabled:opacity-50" className="text-muted-foreground/80 hover:text-foreground focus-visible:border-ring focus-visible:ring-ring/50 absolute inset-y-0 end-0 flex h-full w-9 items-center justify-center rounded-e-md outline-none transition-[color,box-shadow] focus:z-10 focus-visible:ring-[3px] disabled:pointer-events-none disabled:cursor-not-allowed disabled:opacity-50"
aria-label="Clear filter" aria-label={_(msg`Clear filter`)}
onClick={handleClearFilter} onClick={handleClearFilter}
> >
<XCircle className="size-3" aria-hidden="true" /> <XCircle className="size-3" aria-hidden="true" />
@ -79,7 +82,7 @@ export function DataTableToolbar<TData>({
{table.getColumn('status') && ( {table.getColumn('status') && (
<DataTableFacetedFilter <DataTableFacetedFilter
column={table.getColumn('status')} column={table.getColumn('status')}
title="Status" title={_(msg`Status`)}
options={statuses} options={statuses}
icon={CircleDashedIcon} icon={CircleDashedIcon}
stats={stats} stats={stats}
@ -91,7 +94,7 @@ export function DataTableToolbar<TData>({
{table.getColumn('createdAt') && ( {table.getColumn('createdAt') && (
<DataTableSingleFilter <DataTableSingleFilter
column={table.getColumn('createdAt')} column={table.getColumn('createdAt')}
title="Time Period" title={_(msg`Time Period`)}
options={timePeriods} options={timePeriods}
groups={timePeriodGroups} groups={timePeriodGroups}
icon={Calendar} icon={Calendar}
@ -103,7 +106,7 @@ export function DataTableToolbar<TData>({
{table.getColumn('source') && ( {table.getColumn('source') && (
<DataTableFacetedFilter <DataTableFacetedFilter
column={table.getColumn('source')} column={table.getColumn('source')}
title="Source" title={_(msg`Source`)}
options={sources} options={sources}
icon={Globe} icon={Globe}
onFilterChange={onSourceFilterChange} onFilterChange={onSourceFilterChange}
@ -113,7 +116,7 @@ export function DataTableToolbar<TData>({
{isFiltered && ( {isFiltered && (
<Button variant="ghost" className="h-8 gap-2" size="sm" onClick={handleReset}> <Button variant="ghost" className="h-8 gap-2" size="sm" onClick={handleReset}>
Reset {_(msg`Reset`)}
<X className="size-4" /> <X className="size-4" />
</Button> </Button>
)} )}

View File

@ -1,6 +1,8 @@
import * as React from 'react'; import * as React from 'react';
import { useMemo } from 'react'; import { useMemo } from 'react';
import { msg } from '@lingui/core/macro';
import { useLingui } from '@lingui/react';
import { Trans } from '@lingui/react/macro'; import { Trans } from '@lingui/react/macro';
import type { import type {
ColumnDef, ColumnDef,
@ -83,6 +85,7 @@ export function DataTable<TData, TValue>({
isSourceFiltered, isSourceFiltered,
emptyState, emptyState,
}: DataTableProps<TData, TValue>) { }: DataTableProps<TData, TValue>) {
const { _ } = useLingui();
const [rowSelection, setRowSelection] = React.useState({}); const [rowSelection, setRowSelection] = React.useState({});
const [columnVisibility, setColumnVisibility] = React.useState<VisibilityState>({}); const [columnVisibility, setColumnVisibility] = React.useState<VisibilityState>({});
const [columnFilters, setColumnFilters] = React.useState<ColumnFiltersState>([]); const [columnFilters, setColumnFilters] = React.useState<ColumnFiltersState>([]);
@ -211,10 +214,14 @@ export function DataTable<TData, TValue>({
</div> </div>
) : emptyState?.enable ? ( ) : emptyState?.enable ? (
(emptyState.component ?? ( (emptyState.component ?? (
<div className="flex h-24 items-center justify-center text-center">No results.</div> <div className="flex h-24 items-center justify-center text-center">
{_(msg`No results.`)}
</div>
)) ))
) : ( ) : (
<div className="flex h-24 items-center justify-center text-center">No results.</div> <div className="flex h-24 items-center justify-center text-center">
{_(msg`No results.`)}
</div>
)} )}
</div> </div>

View File

@ -1,37 +1,38 @@
import { msg } from '@lingui/core/macro';
import { CheckCircle2, Clock, File, FileText, Inbox, Link, XCircle } from 'lucide-react'; import { CheckCircle2, Clock, File, FileText, Inbox, Link, XCircle } from 'lucide-react';
export const statuses = [ export const statuses = [
{ {
value: 'INBOX', value: 'INBOX',
label: 'Inbox', label: msg`Inbox`,
icon: Inbox, icon: Inbox,
color: 'text-blue-700 dark:text-blue-300', color: 'text-blue-700 dark:text-blue-300',
bgColor: 'bg-blue-100 dark:bg-blue-100 text-blue-700 dark:text-blue-700', bgColor: 'bg-blue-100 dark:bg-blue-100 text-blue-700 dark:text-blue-700',
}, },
{ {
value: 'DRAFT', value: 'DRAFT',
label: 'Draft', label: msg`Draft`,
icon: File, icon: File,
color: 'text-yellow-500 dark:text-yellow-300', color: 'text-yellow-500 dark:text-yellow-300',
bgColor: 'bg-yellow-100 dark:bg-yellow-100 text-yellow-700 dark:text-yellow-700', bgColor: 'bg-yellow-100 dark:bg-yellow-100 text-yellow-700 dark:text-yellow-700',
}, },
{ {
value: 'PENDING', value: 'PENDING',
label: 'Pending', label: msg`Pending`,
icon: Clock, icon: Clock,
color: 'text-blue-700 dark:text-blue-300', color: 'text-blue-700 dark:text-blue-300',
bgColor: 'bg-blue-100 dark:bg-blue-100 text-blue-700 dark:text-blue-700', bgColor: 'bg-blue-100 dark:bg-blue-100 text-blue-700 dark:text-blue-700',
}, },
{ {
value: 'COMPLETED', value: 'COMPLETED',
label: 'Completed', label: msg`Completed`,
icon: CheckCircle2, icon: CheckCircle2,
color: 'text-documenso-700 dark:text-documenso-300', color: 'text-documenso-700 dark:text-documenso-300',
bgColor: 'bg-documenso-200 dark:bg-documenso-200 text-documenso-800 dark:text-documenso-800', bgColor: 'bg-documenso-200 dark:bg-documenso-200 text-documenso-800 dark:text-documenso-800',
}, },
{ {
value: 'REJECTED', value: 'REJECTED',
label: 'Rejected', label: msg`Rejected`,
icon: XCircle, icon: XCircle,
color: 'text-red-700 dark:text-red-300', color: 'text-red-700 dark:text-red-300',
bgColor: 'bg-red-100 dark:bg-red-100 text-red-500 dark:text-red-700', bgColor: 'bg-red-100 dark:bg-red-100 text-red-500 dark:text-red-700',
@ -41,14 +42,14 @@ export const statuses = [
export const sources = [ export const sources = [
{ {
value: 'TEMPLATE', value: 'TEMPLATE',
label: 'Template', label: msg`Template`,
icon: FileText, icon: FileText,
color: 'text-blue-700 dark:text-blue-300', color: 'text-blue-700 dark:text-blue-300',
bgColor: 'bg-blue-100 dark:bg-blue-100 text-blue-700 dark:text-blue-700', bgColor: 'bg-blue-100 dark:bg-blue-100 text-blue-700 dark:text-blue-700',
}, },
{ {
value: 'DIRECT_LINK', value: 'DIRECT_LINK',
label: 'Direct Link', label: msg`Direct Link`,
icon: Link, icon: Link,
color: 'text-green-700 dark:text-green-300', color: 'text-green-700 dark:text-green-300',
bgColor: 'bg-green-100 dark:bg-green-100 text-green-700 dark:text-green-700', bgColor: 'bg-green-100 dark:bg-green-100 text-green-700 dark:text-green-700',
@ -58,61 +59,61 @@ export const sources = [
export const timePeriods = [ export const timePeriods = [
{ {
value: 'today', value: 'today',
label: 'Today', label: msg`Today`,
}, },
{ {
value: 'this-week', value: 'this-week',
label: 'This Week', label: msg`This Week`,
}, },
{ {
value: 'this-month', value: 'this-month',
label: 'This Month', label: msg`This Month`,
}, },
{ {
value: 'this-quarter', value: 'this-quarter',
label: 'This Quarter', label: msg`This Quarter`,
}, },
{ {
value: 'this-year', value: 'this-year',
label: 'This Year', label: msg`This Year`,
}, },
{ {
value: 'yesterday', value: 'yesterday',
label: 'Yesterday', label: msg`Yesterday`,
}, },
{ {
value: 'last-week', value: 'last-week',
label: 'Last Week', label: msg`Last Week`,
}, },
{ {
value: 'last-month', value: 'last-month',
label: 'Last Month', label: msg`Last Month`,
}, },
{ {
value: 'last-quarter', value: 'last-quarter',
label: 'Last Quarter', label: msg`Last Quarter`,
}, },
{ {
value: 'last-year', value: 'last-year',
label: 'Last Year', label: msg`Last Year`,
}, },
{ {
value: 'all-time', value: 'all-time',
label: 'All Time', label: msg`All Time`,
}, },
]; ];
export const timePeriodGroups = [ export const timePeriodGroups = [
{ {
label: 'Present', label: msg`Present`,
values: ['today', 'this-week', 'this-month', 'this-quarter', 'this-year'], values: ['today', 'this-week', 'this-month', 'this-quarter', 'this-year'],
}, },
{ {
label: 'Past', label: msg`Past`,
values: ['yesterday', 'last-week', 'last-month', 'last-quarter', 'last-year'], values: ['yesterday', 'last-week', 'last-month', 'last-quarter', 'last-year'],
}, },
{ {
label: '', label: msg``,
values: ['all-time'], values: ['all-time'],
}, },
]; ];