feat: improve translation coverage (#1427)
Improves translation coverage across the app.
This commit is contained in:
@@ -5,6 +5,7 @@ import { useCallback, useEffect, useMemo, useRef, useState } from 'react';
|
||||
import { Caveat } from 'next/font/google';
|
||||
|
||||
import { Trans, msg } from '@lingui/macro';
|
||||
import { useLingui } from '@lingui/react';
|
||||
import { Prisma } from '@prisma/client';
|
||||
import {
|
||||
CalendarDays,
|
||||
@@ -34,6 +35,7 @@ import {
|
||||
} from '@documenso/lib/types/field-meta';
|
||||
import { nanoid } from '@documenso/lib/universal/id';
|
||||
import { validateFieldsUninserted } from '@documenso/lib/utils/fields';
|
||||
import { parseMessageDescriptor } from '@documenso/lib/utils/i18n';
|
||||
import {
|
||||
canRecipientBeModified,
|
||||
canRecipientFieldsBeModified,
|
||||
@@ -114,6 +116,7 @@ export const AddFieldsFormPartial = ({
|
||||
teamId,
|
||||
}: AddFieldsFormProps) => {
|
||||
const { toast } = useToast();
|
||||
const { _ } = useLingui();
|
||||
|
||||
const [isMissingSignatureDialogVisible, setIsMissingSignatureDialogVisible] = useState(false);
|
||||
|
||||
@@ -568,7 +571,10 @@ export const AddFieldsFormPartial = ({
|
||||
{showAdvancedSettings && currentField ? (
|
||||
<FieldAdvancedSettings
|
||||
title={msg`Advanced settings`}
|
||||
description={msg`Configure the ${FRIENDLY_FIELD_TYPE[currentField.type]} field`}
|
||||
description={msg`Configure the ${parseMessageDescriptor(
|
||||
_,
|
||||
FRIENDLY_FIELD_TYPE[currentField.type],
|
||||
)} field`}
|
||||
field={currentField}
|
||||
fields={localFields}
|
||||
onAdvancedSettings={handleAdvancedSettings}
|
||||
@@ -603,7 +609,7 @@ export const AddFieldsFormPartial = ({
|
||||
width: fieldBounds.current.width,
|
||||
}}
|
||||
>
|
||||
{FRIENDLY_FIELD_TYPE[selectedField]}
|
||||
{parseMessageDescriptor(_, FRIENDLY_FIELD_TYPE[selectedField])}
|
||||
</div>
|
||||
)}
|
||||
|
||||
@@ -684,8 +690,7 @@ export const AddFieldsFormPartial = ({
|
||||
{recipientsByRoleToDisplay.map(([role, roleRecipients], roleIndex) => (
|
||||
<CommandGroup key={roleIndex}>
|
||||
<div className="text-muted-foreground mb-1 ml-2 mt-2 text-xs font-medium">
|
||||
{/* Todo: Translations - Add plural translations. */}
|
||||
{`${RECIPIENT_ROLES_DESCRIPTION_ENG[role].roleName}s`}
|
||||
{_(RECIPIENT_ROLES_DESCRIPTION_ENG[role].roleNamePlural)}
|
||||
</div>
|
||||
|
||||
{roleRecipients.length === 0 && (
|
||||
@@ -997,7 +1002,7 @@ export const AddFieldsFormPartial = ({
|
||||
)}
|
||||
>
|
||||
<Disc className="h-4 w-4" />
|
||||
<Trans>Radio</Trans>
|
||||
Radio
|
||||
</p>
|
||||
</CardContent>
|
||||
</Card>
|
||||
@@ -1023,7 +1028,8 @@ export const AddFieldsFormPartial = ({
|
||||
)}
|
||||
>
|
||||
<CheckSquare className="h-4 w-4" />
|
||||
<Trans>Checkbox</Trans>
|
||||
{/* Not translated on purpose. */}
|
||||
Checkbox
|
||||
</p>
|
||||
</CardContent>
|
||||
</Card>
|
||||
|
||||
@@ -1,10 +1,11 @@
|
||||
import { msg } from '@lingui/macro';
|
||||
import { z } from 'zod';
|
||||
|
||||
export const ZAddSignatureFormSchema = z.object({
|
||||
email: z
|
||||
.string()
|
||||
.min(1, { message: 'Email is required' })
|
||||
.email({ message: 'Invalid email address' }),
|
||||
.min(1, { message: msg`Email is required`.id })
|
||||
.email({ message: msg`Invalid email address`.id }),
|
||||
name: z.string(),
|
||||
customText: z.string(),
|
||||
number: z.number().optional(),
|
||||
|
||||
@@ -504,7 +504,7 @@ export const AddSignersFormPartial = ({
|
||||
<FormControl>
|
||||
<Input
|
||||
type="email"
|
||||
placeholder="Email"
|
||||
placeholder={_(msg`Email`)}
|
||||
{...field}
|
||||
disabled={
|
||||
snapshot.isDragging ||
|
||||
|
||||
@@ -1,3 +1,4 @@
|
||||
import { msg } from '@lingui/macro';
|
||||
import { z } from 'zod';
|
||||
|
||||
import { ZRecipientActionAuthTypesSchema } from '@documenso/lib/types/document-auth';
|
||||
@@ -11,7 +12,10 @@ export const ZAddSignersFormSchema = z
|
||||
z.object({
|
||||
formId: z.string().min(1),
|
||||
nativeId: z.number().optional(),
|
||||
email: z.string().email().min(1),
|
||||
email: z
|
||||
.string()
|
||||
.email({ message: msg`Invalid email`.id })
|
||||
.min(1),
|
||||
name: z.string(),
|
||||
role: z.nativeEnum(RecipientRole),
|
||||
signingOrder: z.number().optional(),
|
||||
@@ -29,7 +33,7 @@ export const ZAddSignersFormSchema = z
|
||||
return new Set(emails).size === emails.length;
|
||||
},
|
||||
// Dirty hack to handle errors when .root is populated for an array type
|
||||
{ message: 'Signers must have unique emails', path: ['signers__root'] },
|
||||
{ message: msg`Signers must have unique emails`.id, path: ['signers__root'] },
|
||||
);
|
||||
|
||||
export type TAddSignersFormSchema = z.infer<typeof ZAddSignersFormSchema>;
|
||||
|
||||
@@ -2,10 +2,12 @@
|
||||
|
||||
import { Caveat } from 'next/font/google';
|
||||
|
||||
import { useLingui } from '@lingui/react';
|
||||
import type { Prisma } from '@prisma/client';
|
||||
import { createPortal } from 'react-dom';
|
||||
|
||||
import { useFieldPageCoords } from '@documenso/lib/client-only/hooks/use-field-page-coords';
|
||||
import { parseMessageDescriptor } from '@documenso/lib/utils/i18n';
|
||||
import { FieldType } from '@documenso/prisma/client';
|
||||
|
||||
import { cn } from '../../lib/utils';
|
||||
@@ -25,6 +27,8 @@ export type ShowFieldItemProps = {
|
||||
};
|
||||
|
||||
export const ShowFieldItem = ({ field, recipients }: ShowFieldItemProps) => {
|
||||
const { _ } = useLingui();
|
||||
|
||||
const coords = useFieldPageCoords(field);
|
||||
|
||||
const signerEmail =
|
||||
@@ -47,7 +51,7 @@ export const ShowFieldItem = ({ field, recipients }: ShowFieldItemProps) => {
|
||||
field.type === FieldType.SIGNATURE && fontCaveat.className,
|
||||
)}
|
||||
>
|
||||
{FRIENDLY_FIELD_TYPE[field.type]}
|
||||
{parseMessageDescriptor(_, FRIENDLY_FIELD_TYPE[field.type])}
|
||||
|
||||
<p className="text-muted-foreground/50 w-full truncate text-center text-xs">
|
||||
{signerEmail}
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
import type { MessageDescriptor } from '@lingui/core';
|
||||
import { msg } from '@lingui/macro';
|
||||
import { z } from 'zod';
|
||||
|
||||
import { ZFieldMetaSchema } from '@documenso/lib/types/field-meta';
|
||||
@@ -44,18 +45,18 @@ export const ZDocumentFlowFormSchema = z.object({
|
||||
|
||||
export type TDocumentFlowFormSchema = z.infer<typeof ZDocumentFlowFormSchema>;
|
||||
|
||||
export const FRIENDLY_FIELD_TYPE: Record<FieldType, string> = {
|
||||
[FieldType.SIGNATURE]: 'Signature',
|
||||
[FieldType.FREE_SIGNATURE]: 'Free Signature',
|
||||
[FieldType.INITIALS]: 'Initials',
|
||||
[FieldType.TEXT]: 'Text',
|
||||
[FieldType.DATE]: 'Date',
|
||||
[FieldType.EMAIL]: 'Email',
|
||||
[FieldType.NAME]: 'Name',
|
||||
[FieldType.NUMBER]: 'Number',
|
||||
[FieldType.RADIO]: 'Radio',
|
||||
[FieldType.CHECKBOX]: 'Checkbox',
|
||||
[FieldType.DROPDOWN]: 'Select',
|
||||
export const FRIENDLY_FIELD_TYPE: Record<FieldType, MessageDescriptor | string> = {
|
||||
[FieldType.SIGNATURE]: msg`Signature`,
|
||||
[FieldType.FREE_SIGNATURE]: msg`Free Signature`,
|
||||
[FieldType.INITIALS]: msg`Initials`,
|
||||
[FieldType.TEXT]: msg`Text`,
|
||||
[FieldType.DATE]: msg`Date`,
|
||||
[FieldType.EMAIL]: msg`Email`,
|
||||
[FieldType.NAME]: msg`Name`,
|
||||
[FieldType.NUMBER]: msg`Number`,
|
||||
[FieldType.RADIO]: `Radio`,
|
||||
[FieldType.CHECKBOX]: `Checkbox`,
|
||||
[FieldType.DROPDOWN]: `Select`,
|
||||
};
|
||||
|
||||
export interface DocumentFlowStep {
|
||||
|
||||
@@ -1,3 +1,4 @@
|
||||
import { useLingui } from '@lingui/react';
|
||||
import { AnimatePresence, motion } from 'framer-motion';
|
||||
|
||||
import { cn } from '../../lib/utils';
|
||||
@@ -12,6 +13,15 @@ const isErrorWithMessage = (error: unknown): error is { message?: string } => {
|
||||
};
|
||||
|
||||
export const FormErrorMessage = ({ error, className }: FormErrorMessageProps) => {
|
||||
const { i18n } = useLingui();
|
||||
|
||||
let errorMessage = isErrorWithMessage(error) ? error.message : '';
|
||||
|
||||
// Checks to see if there's a translation for the string, since we're passing IDs for Zod errors.
|
||||
if (typeof errorMessage === 'string' && i18n.t(errorMessage)) {
|
||||
errorMessage = i18n.t(errorMessage);
|
||||
}
|
||||
|
||||
return (
|
||||
<AnimatePresence>
|
||||
{isErrorWithMessage(error) && (
|
||||
@@ -30,7 +40,7 @@ export const FormErrorMessage = ({ error, className }: FormErrorMessageProps) =>
|
||||
}}
|
||||
className={cn('text-xs text-red-500', className)}
|
||||
>
|
||||
{error.message}
|
||||
{errorMessage}
|
||||
</motion.p>
|
||||
)}
|
||||
</AnimatePresence>
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
import * as React from 'react';
|
||||
|
||||
import { useLingui } from '@lingui/react';
|
||||
import type * as LabelPrimitive from '@radix-ui/react-label';
|
||||
import { Slot } from '@radix-ui/react-slot';
|
||||
import { AnimatePresence, motion } from 'framer-motion';
|
||||
@@ -136,13 +137,21 @@ const FormMessage = React.forwardRef<
|
||||
HTMLParagraphElement,
|
||||
React.HTMLAttributes<HTMLParagraphElement>
|
||||
>(({ className, children, ...props }, ref) => {
|
||||
const { i18n } = useLingui();
|
||||
|
||||
const { error, formMessageId } = useFormField();
|
||||
const body = error ? String(error?.message) : children;
|
||||
|
||||
let body = error ? String(error?.message) : children;
|
||||
|
||||
if (!body) {
|
||||
return null;
|
||||
}
|
||||
|
||||
// Checks to see if there's a translation for the string, since we're passing IDs for Zod errors.
|
||||
if (typeof body === 'string' && i18n.t(body)) {
|
||||
body = i18n.t(body);
|
||||
}
|
||||
|
||||
return (
|
||||
<AnimatePresence>
|
||||
<motion.div
|
||||
|
||||
@@ -2,6 +2,7 @@
|
||||
|
||||
import dynamic from 'next/dynamic';
|
||||
|
||||
import { Trans } from '@lingui/macro';
|
||||
import { Loader } from 'lucide-react';
|
||||
|
||||
export const LazyPDFViewer = dynamic(async () => import('./pdf-viewer'), {
|
||||
@@ -10,7 +11,9 @@ export const LazyPDFViewer = dynamic(async () => import('./pdf-viewer'), {
|
||||
<div className="dark:bg-background flex h-[80vh] max-h-[60rem] flex-col items-center justify-center bg-white/50">
|
||||
<Loader className="text-documenso h-12 w-12 animate-spin" />
|
||||
|
||||
<p className="text-muted-foreground mt-4">Loading document...</p>
|
||||
<p className="text-muted-foreground mt-4">
|
||||
<Trans>Loading document...</Trans>
|
||||
</p>
|
||||
</div>
|
||||
),
|
||||
});
|
||||
|
||||
@@ -2,6 +2,8 @@
|
||||
|
||||
import React, { useEffect, useMemo, useRef, useState } from 'react';
|
||||
|
||||
import { Trans, msg } from '@lingui/macro';
|
||||
import { useLingui } from '@lingui/react';
|
||||
import { Loader } from 'lucide-react';
|
||||
import { type PDFDocumentProxy, PasswordResponses } from 'pdfjs-dist';
|
||||
import { Document as PDFDocument, Page as PDFPage, pdfjs } from 'react-pdf';
|
||||
@@ -38,7 +40,9 @@ const PDFLoader = () => (
|
||||
<>
|
||||
<Loader className="text-documenso h-12 w-12 animate-spin" />
|
||||
|
||||
<p className="text-muted-foreground mt-4">Loading document...</p>
|
||||
<p className="text-muted-foreground mt-4">
|
||||
<Trans>Loading document...</Trans>
|
||||
</p>
|
||||
</>
|
||||
);
|
||||
|
||||
@@ -61,6 +65,7 @@ export const PDFViewer = ({
|
||||
onPageClick,
|
||||
...props
|
||||
}: PDFViewerProps) => {
|
||||
const { _ } = useLingui();
|
||||
const { toast } = useToast();
|
||||
|
||||
const $el = useRef<HTMLDivElement>(null);
|
||||
@@ -158,8 +163,8 @@ export const PDFViewer = ({
|
||||
console.error(err);
|
||||
|
||||
toast({
|
||||
title: 'Error',
|
||||
description: 'An error occurred while loading the document.',
|
||||
title: _(msg`Error`),
|
||||
description: _(msg`An error occurred while loading the document.`),
|
||||
variant: 'destructive',
|
||||
});
|
||||
}
|
||||
@@ -211,8 +216,12 @@ export const PDFViewer = ({
|
||||
<div className="dark:bg-background flex h-[80vh] max-h-[60rem] flex-col items-center justify-center bg-white/50">
|
||||
{pdfError ? (
|
||||
<div className="text-muted-foreground text-center">
|
||||
<p>Something went wrong while loading the document.</p>
|
||||
<p className="mt-1 text-sm">Please try again or contact our support.</p>
|
||||
<p>
|
||||
<Trans>Something went wrong while loading the document.</Trans>
|
||||
</p>
|
||||
<p className="mt-1 text-sm">
|
||||
<Trans>Please try again or contact our support.</Trans>
|
||||
</p>
|
||||
</div>
|
||||
) : (
|
||||
<PDFLoader />
|
||||
@@ -222,8 +231,12 @@ export const PDFViewer = ({
|
||||
error={
|
||||
<div className="dark:bg-background flex h-[80vh] max-h-[60rem] flex-col items-center justify-center bg-white/50">
|
||||
<div className="text-muted-foreground text-center">
|
||||
<p>Something went wrong while loading the document.</p>
|
||||
<p className="mt-1 text-sm">Please try again or contact our support.</p>
|
||||
<p>
|
||||
<Trans>Something went wrong while loading the document.</Trans>
|
||||
</p>
|
||||
<p className="mt-1 text-sm">
|
||||
<Trans>Please try again or contact our support.</Trans>
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
}
|
||||
@@ -243,7 +256,9 @@ export const PDFViewer = ({
|
||||
/>
|
||||
</div>
|
||||
<p className="text-muted-foreground/80 my-2 text-center text-[11px]">
|
||||
Page {i + 1} of {numPages}
|
||||
<Trans>
|
||||
Page {i + 1} of {numPages}
|
||||
</Trans>
|
||||
</p>
|
||||
</div>
|
||||
))}
|
||||
|
||||
@@ -5,6 +5,7 @@ import { useCallback, useEffect, useMemo, useRef, useState } from 'react';
|
||||
import { Caveat } from 'next/font/google';
|
||||
|
||||
import { Trans, msg } from '@lingui/macro';
|
||||
import { useLingui } from '@lingui/react';
|
||||
import {
|
||||
CalendarDays,
|
||||
CheckSquare,
|
||||
@@ -28,6 +29,7 @@ import {
|
||||
ZFieldMetaSchema,
|
||||
} from '@documenso/lib/types/field-meta';
|
||||
import { nanoid } from '@documenso/lib/universal/id';
|
||||
import { parseMessageDescriptor } from '@documenso/lib/utils/i18n';
|
||||
import type { Field, Recipient } from '@documenso/prisma/client';
|
||||
import { FieldType, RecipientRole } from '@documenso/prisma/client';
|
||||
import { cn } from '@documenso/ui/lib/utils';
|
||||
@@ -85,6 +87,8 @@ export const AddTemplateFieldsFormPartial = ({
|
||||
onSubmit,
|
||||
teamId,
|
||||
}: AddTemplateFieldsFormProps) => {
|
||||
const { _ } = useLingui();
|
||||
|
||||
const { isWithinPageBounds, getFieldPosition, getPage } = useDocumentElement();
|
||||
const { currentStep, totalSteps, previousStep } = useStep();
|
||||
const [showAdvancedSettings, setShowAdvancedSettings] = useState(false);
|
||||
@@ -400,7 +404,10 @@ export const AddTemplateFieldsFormPartial = ({
|
||||
{showAdvancedSettings && currentField ? (
|
||||
<FieldAdvancedSettings
|
||||
title={msg`Advanced settings`}
|
||||
description={msg`Configure the ${FRIENDLY_FIELD_TYPE[currentField.type]} field`}
|
||||
description={msg`Configure the ${parseMessageDescriptor(
|
||||
_,
|
||||
FRIENDLY_FIELD_TYPE[currentField.type],
|
||||
)} field`}
|
||||
field={currentField}
|
||||
fields={localFields}
|
||||
onAdvancedSettings={handleAdvancedSettings}
|
||||
@@ -432,7 +439,7 @@ export const AddTemplateFieldsFormPartial = ({
|
||||
width: fieldBounds.current.width,
|
||||
}}
|
||||
>
|
||||
{FRIENDLY_FIELD_TYPE[selectedField]}
|
||||
{parseMessageDescriptor(_, FRIENDLY_FIELD_TYPE[selectedField])}
|
||||
</div>
|
||||
)}
|
||||
|
||||
@@ -501,8 +508,7 @@ export const AddTemplateFieldsFormPartial = ({
|
||||
{recipientsByRoleToDisplay.map(([role, roleRecipients], roleIndex) => (
|
||||
<CommandGroup key={roleIndex}>
|
||||
<div className="text-muted-foreground mb-1 ml-2 mt-2 text-xs font-medium">
|
||||
{/* Todo: Translations - Add plural translations. */}
|
||||
{`${RECIPIENT_ROLES_DESCRIPTION_ENG[role].roleName}s`}
|
||||
{_(RECIPIENT_ROLES_DESCRIPTION_ENG[role].roleNamePlural)}
|
||||
</div>
|
||||
|
||||
{roleRecipients.length === 0 && (
|
||||
@@ -785,7 +791,7 @@ export const AddTemplateFieldsFormPartial = ({
|
||||
)}
|
||||
>
|
||||
<CheckSquare className="h-4 w-4" />
|
||||
<Trans>Checkbox</Trans>
|
||||
Checkbox
|
||||
</p>
|
||||
</CardContent>
|
||||
</Card>
|
||||
|
||||
Reference in New Issue
Block a user