Skip to content

Commit c7b08c4

Browse files
committed
feat: add translations to dialogs in playground
1 parent 879da83 commit c7b08c4

8 files changed

Lines changed: 166 additions & 74 deletions

File tree

apps/playground/src/components/Editor/DeleteFileDialog.tsx

Lines changed: 16 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,5 @@
11
import { Button, Dialog } from '@douglasneuroinformatics/libui/components';
2+
import { useTranslation } from '@douglasneuroinformatics/libui/hooks';
23

34
import { useAppStore } from '@/store';
45

@@ -10,12 +11,23 @@ export type DeleteFileDialogProps = {
1011

1112
export const DeleteFileDialog = ({ filename, isOpen, setIsOpen }: DeleteFileDialogProps) => {
1213
const deleteFile = useAppStore((store) => store.deleteFile);
14+
const { t } = useTranslation();
1315
return filename ? (
1416
<Dialog open={isOpen} onOpenChange={setIsOpen}>
1517
<Dialog.Content>
1618
<Dialog.Header>
17-
<Dialog.Title>{`Are you sure you want to delete "${filename}"?`}</Dialog.Title>
18-
<Dialog.Description>Once deleted, this file cannot be restored</Dialog.Description>
19+
<Dialog.Title>
20+
{t({
21+
en: `Are you sure you want to delete "${filename}"?`,
22+
fr: `Êtes-vous sûr de vouloir supprimer "${filename}" ?`
23+
})}
24+
</Dialog.Title>
25+
<Dialog.Description>
26+
{t({
27+
en: 'Once deleted, this file cannot be restored',
28+
fr: 'Une fois supprimé, ce fichier ne peut plus être restauré'
29+
})}
30+
</Dialog.Description>
1931
</Dialog.Header>
2032
<Dialog.Footer>
2133
<Button
@@ -25,10 +37,10 @@ export const DeleteFileDialog = ({ filename, isOpen, setIsOpen }: DeleteFileDial
2537
setIsOpen(false);
2638
}}
2739
>
28-
Delete
40+
{t({ en: 'Delete', fr: 'Supprimer' })}
2941
</Button>
3042
<Button type="button" variant="outline" onClick={() => setIsOpen(false)}>
31-
Cancel
43+
{t({ en: 'Cancel', fr: 'Annuler' })}
3244
</Button>
3345
</Dialog.Footer>
3446
</Dialog.Content>

apps/playground/src/components/FileUploadDialog/FileUploadDialog.tsx

Lines changed: 12 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
import { useCallback, useEffect, useState } from 'react';
22

33
import { Button, Dialog } from '@douglasneuroinformatics/libui/components';
4+
import { useTranslation } from '@douglasneuroinformatics/libui/hooks';
45
import { CloudUploadIcon } from 'lucide-react';
56
import { useDropzone } from 'react-dropzone';
67
import type { FileRejection } from 'react-dropzone';
@@ -20,11 +21,12 @@ export type FileUploadDialogProps = {
2021
export const FileUploadDialog = ({ accept, isOpen, onSubmit, onValidate, setIsOpen, title }: FileUploadDialogProps) => {
2122
const [files, setFiles] = useState<File[]>([]);
2223
const [errorMessage, setErrorMessage] = useState<null | string>(null);
24+
const { t } = useTranslation();
2325

2426
const handleDrop = useCallback(
2527
(acceptedFiles: File[], rejections: FileRejection[]) => {
2628
for (const { errors, file } of rejections) {
27-
setErrorMessage(`Invalid file type: ${file.name} `);
29+
setErrorMessage(t({ en: `Invalid file type: ${file.name} `, fr: `Type de fichier invalide : ${file.name} ` }));
2830
console.error(errors);
2931
return;
3032
}
@@ -65,9 +67,15 @@ export const FileUploadDialog = ({ accept, isOpen, onSubmit, onValidate, setIsOp
6567
} else if (files.length === 1) {
6668
dropzoneText = files[0]!.name;
6769
} else if (isDragActive) {
68-
dropzoneText = 'Release your cursor to upload file(s)';
70+
dropzoneText = t({
71+
en: 'Release your cursor to upload file(s)',
72+
fr: 'Relâchez votre curseur pour téléverser le(s) fichier(s)'
73+
});
6974
} else {
70-
dropzoneText = 'Click here to upload, or drag and drop files into this area';
75+
dropzoneText = t({
76+
en: 'Click here to upload, or drag and drop files into this area',
77+
fr: 'Cliquez ici pour téléverser, ou glissez et déposez des fichiers dans cette zone'
78+
});
7179
}
7280

7381
return (
@@ -94,7 +102,7 @@ export const FileUploadDialog = ({ accept, isOpen, onSubmit, onValidate, setIsOp
94102
type="button"
95103
onClick={() => void submitFiles(files)}
96104
>
97-
Submit
105+
{t({ en: 'Submit', fr: 'Soumettre' })}
98106
</Button>
99107
</Dialog.Footer>
100108
</Dialog.Content>

apps/playground/src/components/Header/ActionsDropdown/DeleteInstrumentDialog.tsx

Lines changed: 15 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
import { Button, Dialog } from '@douglasneuroinformatics/libui/components';
2-
import { useNotificationsStore } from '@douglasneuroinformatics/libui/hooks';
2+
import { useNotificationsStore, useTranslation } from '@douglasneuroinformatics/libui/hooks';
33

44
import { useAppStore } from '@/store';
55

@@ -12,27 +12,36 @@ export const DeleteInstrumentDialog = ({ isOpen, setIsOpen }: DeleteInstrumentDi
1212
const addNotification = useNotificationsStore((store) => store.addNotification);
1313
const removeInstrument = useAppStore((store) => store.removeInstrument);
1414
const selectedInstrument = useAppStore((store) => store.selectedInstrument);
15+
const { t } = useTranslation();
1516

1617
return (
1718
<Dialog open={isOpen} onOpenChange={setIsOpen}>
1819
<Dialog.Content>
1920
<Dialog.Header>
20-
<Dialog.Title>Are you absolutely sure?</Dialog.Title>
21-
<Dialog.Description>This instrument will be deleted from local storage.</Dialog.Description>
21+
<Dialog.Title>{t({ en: 'Are you absolutely sure?', fr: 'Êtes-vous absolument sûr ?' })}</Dialog.Title>
22+
<Dialog.Description>
23+
{t({
24+
en: 'This instrument will be deleted from local storage.',
25+
fr: 'Cet instrument sera supprimé du stockage local.'
26+
})}
27+
</Dialog.Description>
2228
</Dialog.Header>
2329
<Dialog.Footer>
2430
<Button
2531
variant="danger"
2632
onClick={() => {
2733
removeInstrument(selectedInstrument.id);
28-
addNotification({ message: 'This instrument has been deleted', type: 'success' });
34+
addNotification({
35+
message: t({ en: 'This instrument has been deleted', fr: 'Cet instrument a été supprimé' }),
36+
type: 'success'
37+
});
2938
setIsOpen(false);
3039
}}
3140
>
32-
Delete
41+
{t({ en: 'Delete', fr: 'Supprimer' })}
3342
</Button>
3443
<Button type="button" variant="outline" onClick={() => setIsOpen(false)}>
35-
Cancel
44+
{t({ en: 'Cancel', fr: 'Annuler' })}
3645
</Button>
3746
</Dialog.Footer>
3847
</Dialog.Content>

apps/playground/src/components/Header/ActionsDropdown/LoginDialog.tsx

Lines changed: 39 additions & 22 deletions
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,7 @@ import { useEffect } from 'react';
44

55
import { asyncResultify } from '@douglasneuroinformatics/libjs';
66
import { Dialog, Form } from '@douglasneuroinformatics/libui/components';
7-
import { useNotificationsStore } from '@douglasneuroinformatics/libui/hooks';
7+
import { useNotificationsStore, useTranslation } from '@douglasneuroinformatics/libui/hooks';
88
import type { $LoginCredentials } from '@opendatacapture/schemas/auth';
99
import axios from 'axios';
1010
import { CheckIcon, XIcon } from 'lucide-react';
@@ -35,6 +35,7 @@ export const LoginDialog = ({ isOpen, setIsOpen }: LoginDialogProps) => {
3535
const revalidateToken = useAppStore((store) => store.revalidateToken);
3636

3737
const addNotification = useNotificationsStore((store) => store.addNotification);
38+
const { t } = useTranslation();
3839

3940
useEffect(() => {
4041
revalidateToken();
@@ -88,7 +89,11 @@ export const LoginDialog = ({ isOpen, setIsOpen }: LoginDialogProps) => {
8889
updateSettings({ apiBaseUrl });
8990
const adminTokenResult = await getAdminToken(credentials, apiBaseUrl);
9091
if (adminTokenResult.isErr()) {
91-
addNotification({ type: 'error', title: 'Login Failed', message: adminTokenResult.error });
92+
addNotification({
93+
type: 'error',
94+
title: t({ en: 'Login Failed', fr: 'Échec de la connexion' }),
95+
message: adminTokenResult.error
96+
});
9297
return;
9398
}
9499

@@ -97,7 +102,11 @@ export const LoginDialog = ({ isOpen, setIsOpen }: LoginDialogProps) => {
97102
} else {
98103
const limitedTokenResult = await getLimitedToken(adminTokenResult.value.accessToken, apiBaseUrl);
99104
if (limitedTokenResult.isErr()) {
100-
addNotification({ type: 'error', title: 'Failed to Get Limited Token', message: limitedTokenResult.error });
105+
addNotification({
106+
type: 'error',
107+
title: t({ en: 'Failed to Get Limited Token', fr: "Échec de l'obtention d'un jeton limité" }),
108+
message: limitedTokenResult.error
109+
});
101110
return;
102111
}
103112
login(limitedTokenResult.value.accessToken);
@@ -110,25 +119,27 @@ export const LoginDialog = ({ isOpen, setIsOpen }: LoginDialogProps) => {
110119
<Dialog open={isOpen} onOpenChange={setIsOpen}>
111120
<Dialog.Content onOpenAutoFocus={(event) => event.preventDefault()}>
112121
<Dialog.Header>
113-
<Dialog.Title>Login</Dialog.Title>
122+
<Dialog.Title>{t({ en: 'Login', fr: 'Connexion' })}</Dialog.Title>
114123
<Dialog.Description>
115-
Login to your Open Data Capture instance. A special access token is used that grants permissions to create
116-
instruments only. You must have permission to create instruments to use this functionality.
124+
{t({
125+
en: 'Login to your Open Data Capture instance. A special access token is used that grants permissions to create instruments only. You must have permission to create instruments to use this functionality.',
126+
fr: "Connectez-vous à votre instance Open Data Capture. Un jeton d'accès spécial est utilisé pour accorder l'autorisation de créer uniquement des instruments. Vous devez avoir la permission de créer des instruments pour utiliser cette fonctionnalité."
127+
})}
117128
</Dialog.Description>
118129
<div className="mt-2 flex items-center gap-1 text-sm font-medium">
119130
{isAuthorized ? (
120131
<>
121132
<div className="flex h-4 w-4 items-center justify-center rounded-full bg-green-600">
122133
<CheckIcon className="text-white" style={{ height: '12px', width: '12px' }} />
123134
</div>
124-
<span>You are already logged in</span>
135+
<span>{t({ en: 'You are already logged in', fr: 'Vous êtes déjà connecté' })}</span>
125136
</>
126137
) : (
127138
<>
128139
<div className="flex h-4 w-4 items-center justify-center rounded-full bg-red-600">
129140
<XIcon className="text-white" style={{ height: '12px', width: '12px' }} />
130141
</div>
131-
<span>You are not currently logged in</span>
142+
<span>{t({ en: 'You are not currently logged in', fr: "Vous n'êtes actuellement pas connecté" })}</span>
132143
</>
133144
)}
134145
</div>
@@ -137,42 +148,48 @@ export const LoginDialog = ({ isOpen, setIsOpen }: LoginDialogProps) => {
137148
<Form
138149
content={[
139150
{
140-
title: 'Instance Settings',
151+
title: t({ en: 'Instance Settings', fr: "Paramètres de l'instance" }),
141152
fields: {
142153
apiBaseUrl: {
143-
description: 'The base path for your Open Data Capture REST API.',
154+
description: t({
155+
en: 'The base path for your Open Data Capture REST API.',
156+
fr: 'Le chemin de base de votre API REST Open Data Capture.'
157+
}),
144158
kind: 'string',
145159
placeholder: 'e.g., https://demo.opendatacapture.org/api',
146-
label: 'API Base URL',
160+
label: t({ en: 'API Base URL', fr: "URL de base de l'API" }),
147161
variant: 'input'
148162
},
149163
legacyLogin: {
150-
description: [
151-
"Use the user's full access token instead of a granular access token.",
152-
'Note that this can introduce security risks and should not be used on shared machines.',
153-
'It is required only for ODC versions prior to v1.12.0.'
154-
].join(''),
164+
description: t({
165+
en: [
166+
"Use the user's full access token instead of a granular access token.",
167+
'Note that this can introduce security risks and should not be used on shared machines.',
168+
'It is required only for ODC versions prior to v1.12.0.'
169+
].join(' '),
170+
fr: "Utilisez le jeton d'accès complet de l'utilisateur au lieu d'un jeton d'accès granulaire. Notez que cela peut introduire des risques de sécurité et ne doit pas être utilisé sur des machines partagées. Cela n'est requis que pour les versions d'ODC antérieures à v1.12.0."
171+
}),
155172
kind: 'boolean',
156-
label: 'Legacy Login Mode',
173+
label: t({ en: 'Legacy Login Mode', fr: 'Mode de connexion hérité' }),
157174
variant: 'radio',
158175
options: {
159-
false: 'No (Recommended)',
160-
true: 'Yes'
176+
false: t({ en: 'No (Recommended)', fr: 'Non (Recommandé)' }),
177+
true: t({ en: 'Yes', fr: 'Oui' })
161178
}
162179
}
163180
}
164181
},
165182
{
166-
title: 'Login Credentials',
183+
title: t({ en: 'Login Credentials', fr: 'Identifiants de connexion' }),
167184
fields: {
168185
username: {
169186
kind: 'string',
170-
label: 'Username',
187+
label: t({ en: 'Username', fr: "Nom d'utilisateur" }),
171188
variant: 'input'
172189
},
173190
password: {
174191
kind: 'string',
175-
label: 'Password',
192+
label: t({ en: 'Password', fr: 'Mot de passe' }),
176193
variant: 'password'
177194
}
178195
}

apps/playground/src/components/Header/ActionsDropdown/RestoreDefaultsDialog.tsx

Lines changed: 16 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
import { Button, Dialog } from '@douglasneuroinformatics/libui/components';
2-
import { useNotificationsStore } from '@douglasneuroinformatics/libui/hooks';
2+
import { useNotificationsStore, useTranslation } from '@douglasneuroinformatics/libui/hooks';
33

44
import { useAppStore } from '@/store';
55

@@ -12,15 +12,25 @@ export const RestoreDefaultsDialog = ({ isOpen, setIsOpen }: RestoreDefaultsDial
1212
const addNotification = useNotificationsStore((store) => store.addNotification);
1313
const resetInstruments = useAppStore((store) => store.resetInstruments);
1414
const resetSettings = useAppStore((store) => store.resetSettings);
15+
const { t } = useTranslation();
1516

1617
return (
1718
<Dialog open={isOpen} onOpenChange={setIsOpen}>
1819
<Dialog.Content>
1920
<Dialog.Header>
20-
<Dialog.Title>Are you absolutely sure?</Dialog.Title>
21+
<Dialog.Title>{t({ en: 'Are you absolutely sure?', fr: 'Êtes-vous absolument sûr ?' })}</Dialog.Title>
2122
<Dialog.Description>
22-
This action will <span className="font-bold uppercase">delete all user-defined instruments</span> in local
23-
storage and restore the default configuration.
23+
{t({ en: 'This action will ', fr: 'Cette action va ' })}
24+
<span className="font-bold uppercase">
25+
{t({
26+
en: 'delete all user-defined instruments',
27+
fr: "supprimer tous les instruments définis par l'utilisateur"
28+
})}
29+
</span>{' '}
30+
{t({
31+
en: 'in local storage and restore the default configuration.',
32+
fr: 'dans le stockage local et restaurer la configuration par défaut.'
33+
})}
2434
</Dialog.Description>
2535
</Dialog.Header>
2636
<Dialog.Footer>
@@ -33,10 +43,10 @@ export const RestoreDefaultsDialog = ({ isOpen, setIsOpen }: RestoreDefaultsDial
3343
setIsOpen(false);
3444
}}
3545
>
36-
Reset
46+
{t({ en: 'Reset', fr: 'Réinitialiser' })}
3747
</Button>
3848
<Button type="button" variant="outline" onClick={() => setIsOpen(false)}>
39-
Cancel
49+
{t({ en: 'Cancel', fr: 'Annuler' })}
4050
</Button>
4151
</Dialog.Footer>
4252
</Dialog.Content>

0 commit comments

Comments
 (0)