From 82155eedee12f30de1bd4047122a7460ddbf941d Mon Sep 17 00:00:00 2001 From: Akosah Date: Mon, 5 Feb 2024 16:48:09 -0500 Subject: [PATCH 1/6] setup for localization --- packages/client/package.json | 5 +++ .../client/public/locales/en/translation.json | 24 +++++++++++ .../client/public/locales/es/translation.json | 25 +++++++++++ .../src/components/LanguageSelector.tsx | 43 +++++++++++++++++++ .../src/components/SideBar.component.tsx | 37 +++++++++------- packages/client/src/i18n.js | 9 ++++ packages/client/src/main.tsx | 7 ++- packages/client/src/pages/Home.tsx | 6 ++- 8 files changed, 136 insertions(+), 20 deletions(-) create mode 100644 packages/client/public/locales/en/translation.json create mode 100644 packages/client/public/locales/es/translation.json create mode 100644 packages/client/src/components/LanguageSelector.tsx create mode 100644 packages/client/src/i18n.js diff --git a/packages/client/package.json b/packages/client/package.json index 3e6af3cd..ef35e637 100644 --- a/packages/client/package.json +++ b/packages/client/package.json @@ -32,11 +32,16 @@ "esbuild": "^0.19.0", "graphql": "^16.8.0", "injection-js": "^2.4.0", + "i18next": "^23.8.2", + "i18next-browser-languagedetector": "^7.2.0", + "i18next-http-backend": "^2.4.3", + "react-i18next": "^14.0.1", "jwt-decode": "^3.1.2", "react": "^18.2.0", "react-dom": "^18.2.0", "react-router-dom": "^6.12.1", "styled-components": "^5.3.10" + }, "devDependencies": { "@graphql-codegen/cli": "^5.0.0", diff --git a/packages/client/public/locales/en/translation.json b/packages/client/public/locales/en/translation.json new file mode 100644 index 00000000..681fcbd5 --- /dev/null +++ b/packages/client/public/locales/en/translation.json @@ -0,0 +1,24 @@ +{ + "home": { + "welcome": "Welcome to SignLab", + "signedIn": "You are signed in", + "logIn": "Please login to continue" + }, + "menu": { + "projects": "Projects", + "newProject": "New Project", + "projectControl": "Project Control", + "userPermissions": "User Permissions", + "studies": "Studies", + "newStudy": "New Study", + "studyControl": "Study Control", + "entryControls": "Entry Controls", + "downloadTags": "Download Tags", + "datasets": "Datasets", + "datasetControl": "Dataset Control", + "projectAccess": "Project Access", + "contribute": "Contribute", + "tagInStudy": "Tag in Study", + "logout": "Logout" + } +} diff --git a/packages/client/public/locales/es/translation.json b/packages/client/public/locales/es/translation.json new file mode 100644 index 00000000..f2febf24 --- /dev/null +++ b/packages/client/public/locales/es/translation.json @@ -0,0 +1,25 @@ +{ + "home": { + "welcome": "Bienvenido a SignLab", + "signedIn": "Has iniciado sesión", + "logIn": "Por favor inicie sesión para continuar" + }, + "menu": { + "projects": "Proyectos", + "newProject": "Nuevo proyecto", + "projectControl": "Control de Proyecto", + "userPermissions": "Permisos de usuario", + "studies": "Estudios", + "newStudy": "Nuevo estudio", + "studyControl": "Control del estudio", + "entryControls": "Controles de entrada", + "downloadTags": "Descargar Etiquetas", + "datasets": "Conjuntos de datos", + "datasetControl": "Control de conjunto de datos", + "projectAccess": "Acceso al proyecto", + "contribute": "Contribuir", + "tagInStudy": "Etiqueta en estudio", + "logout": "Cerrar sesión" + } + } + \ No newline at end of file diff --git a/packages/client/src/components/LanguageSelector.tsx b/packages/client/src/components/LanguageSelector.tsx new file mode 100644 index 00000000..61522836 --- /dev/null +++ b/packages/client/src/components/LanguageSelector.tsx @@ -0,0 +1,43 @@ +import * as React from 'react'; +import InputLabel from '@mui/material/InputLabel'; +import MenuItem from '@mui/material/MenuItem'; +import FormControl from '@mui/material/FormControl'; +import Select, { SelectChangeEvent } from '@mui/material/Select'; +import '../i18n'; +import { useTranslation } from 'react-i18next'; +import { Paper } from '@mui/material'; + +const languages = { + en: 'English', + es: 'Spanish' +}; + +export default function LanguageSelector() { + const { i18n } = useTranslation(); + const [language, setLanguage] = React.useState(i18n.resolvedLanguage); + + const handleChange = (event: SelectChangeEvent) => { + const newLang = event.target.value as string; + i18n.changeLanguage(newLang); + setLanguage(newLang); + }; + + return ( + + + Select Language + + + + ); +} diff --git a/packages/client/src/components/SideBar.component.tsx b/packages/client/src/components/SideBar.component.tsx index c43eb55f..da72af2a 100644 --- a/packages/client/src/components/SideBar.component.tsx +++ b/packages/client/src/components/SideBar.component.tsx @@ -8,6 +8,8 @@ import { Permission } from '../graphql/graphql'; import { useGetRolesQuery } from '../graphql/permission/permission'; import { useProject } from '../context/Project.context'; import { useStudy } from '../context/Study.context'; +import LanguageSelector from './LanguageSelector'; +import { useTranslation } from 'react-i18next'; interface SideBarProps { open: boolean; @@ -21,6 +23,7 @@ export const SideBar: FC = ({ open, drawerWidth }) => { const { project } = useProject(); const { study } = useStudy(); const rolesQueryResults = useGetRolesQuery({ variables: { project: project?._id, study: study?._id } }); + const { t } = useTranslation(); useEffect(() => { if (rolesQueryResults.data) { @@ -30,54 +33,54 @@ export const SideBar: FC = ({ open, drawerWidth }) => { const navItems: NavItemProps[] = [ { - name: 'Projects', + name: t('menu.projects'), icon: , action: () => {}, visible: (p) => p!.owner || p!.projectAdmin, permission, subItems: [ - { name: 'New Project', action: () => navigate('/project/new'), visible: (p) => p!.owner }, - { name: 'Project Control', action: () => navigate('/project/controls'), visible: (p) => p!.owner }, - { name: 'User Permissions', action: () => navigate('/project/permissions'), visible: (p) => p!.projectAdmin } + { name: t('menu.newProject'), action: () => navigate('/project/new'), visible: (p) => p!.owner }, + { name: t('menu.projectControl'), action: () => navigate('/project/controls'), visible: (p) => p!.owner }, + { name: t('menu.userPermissions'), action: () => navigate('/project/permissions'), visible: (p) => p!.projectAdmin } ] }, { - name: 'Studies', + name: t('menu.studies'), action: () => {}, icon: , visible: (p) => p!.projectAdmin || p!.studyAdmin, permission, subItems: [ - { name: 'New Study', action: () => navigate('/study/new'), visible: (p) => p!.projectAdmin }, - { name: 'Study Control', action: () => navigate('/study/controls'), visible: (p) => p!.projectAdmin }, - { name: 'User Permissions', action: () => navigate('/study/permissions'), visible: (p) => p!.studyAdmin }, - { name: 'Entry Controls', action: () => navigate('/study/entries'), visible: (p) => p!.studyAdmin }, - { name: 'Download Tags', action: () => navigate('/study/tags'), visible: (p) => p!.studyAdmin } + { name: t('menu.newStudy'), action: () => navigate('/study/new'), visible: (p) => p!.projectAdmin }, + { name: t('menu.studyControl'), action: () => navigate('/study/controls'), visible: (p) => p!.projectAdmin }, + { name: t('menu.userPermissions'), action: () => navigate('/study/permissions'), visible: (p) => p!.studyAdmin }, + { name: t('menu.entryControls'), action: () => navigate('/study/entries'), visible: (p) => p!.studyAdmin }, + { name: t('menu.downloadTags'), action: () => navigate('/study/tags'), visible: (p) => p!.studyAdmin } ] }, { - name: 'Datasets', + name: t('menu.datasets'), action: () => {}, icon: , visible: (p) => p!.owner, permission, subItems: [ - { name: 'Dataset Control', action: () => navigate('/dataset/controls'), visible: (p) => p!.owner }, - { name: 'Project Access', action: () => navigate('/dataset/projectaccess'), visible: (p) => p!.owner } + { name: t('menu.datasetControl'), action: () => navigate('/dataset/controls'), visible: (p) => p!.owner }, + { name: t('menu.projectAccess'), action: () => navigate('/dataset/projectaccess'), visible: (p) => p!.owner } ] }, { - name: 'Contribute', + name: t('menu.contribute'), action: () => {}, icon: , permission, visible: (p) => p!.contributor, subItems: [ - { name: 'Tag in Study', action: () => navigate('/contribute/landing'), visible: (p) => p!.contributor } + { name: t('menu.tagInStudy'), action: () => navigate('/contribute/landing'), visible: (p) => p!.contributor } ] }, { - name: 'Logout', + name: t('menu.logout'), action: logout, icon: , visible: () => true @@ -113,6 +116,8 @@ export const SideBar: FC = ({ open, drawerWidth }) => { )} + + ); }; diff --git a/packages/client/src/i18n.js b/packages/client/src/i18n.js new file mode 100644 index 00000000..38597090 --- /dev/null +++ b/packages/client/src/i18n.js @@ -0,0 +1,9 @@ +import i18next from 'i18next'; +import { initReactI18next } from 'react-i18next'; +import LanguageDetector from 'i18next-browser-languagedetector'; +import Backend from 'i18next-http-backend'; + +i18next.use(initReactI18next).use(LanguageDetector).use(Backend).init({ + fallbackLang: 'en', + debug: true +}); diff --git a/packages/client/src/main.tsx b/packages/client/src/main.tsx index 2bcefcc9..eb3afe4a 100644 --- a/packages/client/src/main.tsx +++ b/packages/client/src/main.tsx @@ -2,9 +2,12 @@ import * as ReactDOM from 'react-dom/client'; import App from './App.tsx'; import './index.css'; import { StrictMode } from 'react'; - +import './i18n'; +import * as React from 'react'; ReactDOM.createRoot(document.getElementById('root') as HTMLElement).render( - + + + ); diff --git a/packages/client/src/pages/Home.tsx b/packages/client/src/pages/Home.tsx index 05515dfa..7639bde0 100644 --- a/packages/client/src/pages/Home.tsx +++ b/packages/client/src/pages/Home.tsx @@ -1,14 +1,16 @@ import { keyframes } from 'styled-components'; import styled from 'styled-components'; import { useAuth } from '../context/Auth.context'; +import { useTranslation } from 'react-i18next'; export const HomePage: React.FC = () => { const { token, authenticated } = useAuth(); + const { t } = useTranslation(); return (
- Welcome to SignLab - {authenticated && token ?

You are signed in

:

Please login to continue

} + {t('home.welcome')} + {authenticated && token ?

{t('home.signedIn')}

:

{t('home.logIn')}

}
); }; From 47d7527d8a0df1e0bffe04c0aaaa513a2646a92e Mon Sep 17 00:00:00 2001 From: Akosah Date: Tue, 6 Feb 2024 10:52:44 -0500 Subject: [PATCH 2/6] minor changes --- .../client/src/components/LanguageSelector.tsx | 14 ++++---------- .../client/src/components/SideBar.component.tsx | 14 +++++++++++--- 2 files changed, 15 insertions(+), 13 deletions(-) diff --git a/packages/client/src/components/LanguageSelector.tsx b/packages/client/src/components/LanguageSelector.tsx index 61522836..e7033e95 100644 --- a/packages/client/src/components/LanguageSelector.tsx +++ b/packages/client/src/components/LanguageSelector.tsx @@ -12,7 +12,7 @@ const languages = { es: 'Spanish' }; -export default function LanguageSelector() { +export const LanguageSelector: React.FC = () => { const { i18n } = useTranslation(); const [language, setLanguage] = React.useState(i18n.resolvedLanguage); @@ -25,14 +25,8 @@ export default function LanguageSelector() { return ( - Select Language - {Object.entries(languages).map((lang) => ( {lang[1]} ))} @@ -40,4 +34,4 @@ export default function LanguageSelector() { ); -} +}; diff --git a/packages/client/src/components/SideBar.component.tsx b/packages/client/src/components/SideBar.component.tsx index da72af2a..98dde7ca 100644 --- a/packages/client/src/components/SideBar.component.tsx +++ b/packages/client/src/components/SideBar.component.tsx @@ -8,8 +8,8 @@ import { Permission } from '../graphql/graphql'; import { useGetRolesQuery } from '../graphql/permission/permission'; import { useProject } from '../context/Project.context'; import { useStudy } from '../context/Study.context'; -import LanguageSelector from './LanguageSelector'; import { useTranslation } from 'react-i18next'; +import { LanguageSelector } from './LanguageSelector'; interface SideBarProps { open: boolean; @@ -41,7 +41,11 @@ export const SideBar: FC = ({ open, drawerWidth }) => { subItems: [ { name: t('menu.newProject'), action: () => navigate('/project/new'), visible: (p) => p!.owner }, { name: t('menu.projectControl'), action: () => navigate('/project/controls'), visible: (p) => p!.owner }, - { name: t('menu.userPermissions'), action: () => navigate('/project/permissions'), visible: (p) => p!.projectAdmin } + { + name: t('menu.userPermissions'), + action: () => navigate('/project/permissions'), + visible: (p) => p!.projectAdmin + } ] }, { @@ -53,7 +57,11 @@ export const SideBar: FC = ({ open, drawerWidth }) => { subItems: [ { name: t('menu.newStudy'), action: () => navigate('/study/new'), visible: (p) => p!.projectAdmin }, { name: t('menu.studyControl'), action: () => navigate('/study/controls'), visible: (p) => p!.projectAdmin }, - { name: t('menu.userPermissions'), action: () => navigate('/study/permissions'), visible: (p) => p!.studyAdmin }, + { + name: t('menu.userPermissions'), + action: () => navigate('/study/permissions'), + visible: (p) => p!.studyAdmin + }, { name: t('menu.entryControls'), action: () => navigate('/study/entries'), visible: (p) => p!.studyAdmin }, { name: t('menu.downloadTags'), action: () => navigate('/study/tags'), visible: (p) => p!.studyAdmin } ] From e985897b4186a2a0e84b36b78ee5fd660fc183fb Mon Sep 17 00:00:00 2001 From: Akosah Date: Tue, 6 Feb 2024 12:13:49 -0500 Subject: [PATCH 3/6] setup component translation --- .../client/public/locales/en/translation.json | 26 ++++++ .../client/public/locales/es/translation.json | 52 +++++++----- .../src/components/Environment.component.tsx | 8 +- .../src/components/LanguageSelector.tsx | 13 ++- .../client/src/pages/projects/NewProject.tsx | 82 ++++++++++--------- 5 files changed, 108 insertions(+), 73 deletions(-) diff --git a/packages/client/public/locales/en/translation.json b/packages/client/public/locales/en/translation.json index 681fcbd5..ccc51111 100644 --- a/packages/client/public/locales/en/translation.json +++ b/packages/client/public/locales/en/translation.json @@ -1,4 +1,16 @@ { + "common": { + "name": "Name", + "description": "Description", + "instruction": "Instruction", + "project": "Project", + "study": "Study", + "submit": "Submit" + }, + "languages": { + "en": "English", + "es": "Spanish" + }, "home": { "welcome": "Welcome to SignLab", "signedIn": "You are signed in", @@ -20,5 +32,19 @@ "contribute": "Contribute", "tagInStudy": "Tag in Study", "logout": "Logout" + }, + "components": { + "environment": { + "title": "Environment" + }, + "languageSelector": { + "selectLanguage": "Select Language" + }, + "newProject": { + "formLabel": "Create New Project", + "nameDescription": "Please enter project name", + "descriptionDescription": "Please enter project description", + "failMessage": " Failed to create project! Try again." + } } } diff --git a/packages/client/public/locales/es/translation.json b/packages/client/public/locales/es/translation.json index f2febf24..fcd5d96e 100644 --- a/packages/client/public/locales/es/translation.json +++ b/packages/client/public/locales/es/translation.json @@ -1,25 +1,33 @@ { - "home": { - "welcome": "Bienvenido a SignLab", - "signedIn": "Has iniciado sesión", - "logIn": "Por favor inicie sesión para continuar" - }, - "menu": { - "projects": "Proyectos", - "newProject": "Nuevo proyecto", - "projectControl": "Control de Proyecto", - "userPermissions": "Permisos de usuario", - "studies": "Estudios", - "newStudy": "Nuevo estudio", - "studyControl": "Control del estudio", - "entryControls": "Controles de entrada", - "downloadTags": "Descargar Etiquetas", - "datasets": "Conjuntos de datos", - "datasetControl": "Control de conjunto de datos", - "projectAccess": "Acceso al proyecto", - "contribute": "Contribuir", - "tagInStudy": "Etiqueta en estudio", - "logout": "Cerrar sesión" + "home": { + "welcome": "Bienvenido a SignLab", + "signedIn": "Has iniciado sesión", + "logIn": "Por favor inicie sesión para continuar" + }, + "languages": { + "en": "Inglés", + "es": "Español" + }, + "menu": { + "projects": "Proyectos", + "newProject": "Nuevo proyecto", + "projectControl": "Control de Proyecto", + "userPermissions": "Permisos de usuario", + "studies": "Estudios", + "newStudy": "Nuevo estudio", + "studyControl": "Control del estudio", + "entryControls": "Controles de entrada", + "downloadTags": "Descargar Etiquetas", + "datasets": "Conjuntos de datos", + "datasetControl": "Control de conjunto de datos", + "projectAccess": "Acceso al proyecto", + "contribute": "Contribuir", + "tagInStudy": "Etiqueta en estudio", + "logout": "Cerrar sesión" + }, + "components": { + "languageSelector": { + "selectLanguage": "Seleccione el idioma" } } - \ No newline at end of file +} diff --git a/packages/client/src/components/Environment.component.tsx b/packages/client/src/components/Environment.component.tsx index 186a6204..053ee684 100644 --- a/packages/client/src/components/Environment.component.tsx +++ b/packages/client/src/components/Environment.component.tsx @@ -2,22 +2,24 @@ import { Select, MenuItem, FormControl, InputLabel, Stack, Paper, Typography } f import { useProject } from '../context/Project.context'; import { useStudy } from '../context/Study.context'; import { Dispatch, SetStateAction, FC } from 'react'; +import { useTranslation } from 'react-i18next'; export const Environment: FC = () => { const { project, projects, setProject } = useProject(); const { study, studies, setStudy } = useStudy(); + const { t } = useTranslation(); return ( - Environment + {t('components.environment.title')} {/* Project Selection */} option._id} display={(option) => option.name} @@ -26,7 +28,7 @@ export const Environment: FC = () => { option._id} display={(option) => option.name} diff --git a/packages/client/src/components/LanguageSelector.tsx b/packages/client/src/components/LanguageSelector.tsx index e7033e95..23434efa 100644 --- a/packages/client/src/components/LanguageSelector.tsx +++ b/packages/client/src/components/LanguageSelector.tsx @@ -7,13 +7,10 @@ import '../i18n'; import { useTranslation } from 'react-i18next'; import { Paper } from '@mui/material'; -const languages = { - en: 'English', - es: 'Spanish' -}; +const languages = ['en', 'es']; export const LanguageSelector: React.FC = () => { - const { i18n } = useTranslation(); + const { t, i18n } = useTranslation(); const [language, setLanguage] = React.useState(i18n.resolvedLanguage); const handleChange = (event: SelectChangeEvent) => { @@ -25,10 +22,10 @@ export const LanguageSelector: React.FC = () => { return ( - Select Language + {t('components.languageSelector.selectLanguage')} diff --git a/packages/client/src/pages/projects/NewProject.tsx b/packages/client/src/pages/projects/NewProject.tsx index ca515419..62d822ea 100644 --- a/packages/client/src/pages/projects/NewProject.tsx +++ b/packages/client/src/pages/projects/NewProject.tsx @@ -5,44 +5,7 @@ import { materialRenderers, materialCells } from '@jsonforms/material-renderers' import { JsonForms } from '@jsonforms/react'; import { useCreateProjectMutation, useProjectExistsLazyQuery } from '../../graphql/project/project'; import { ErrorObject } from 'ajv'; - -const schema = { - type: 'object', - properties: { - name: { - type: 'string', - pattern: '^[a-zA-Z 0-9]*$', - description: 'Please enter project name' - }, - description: { - type: 'string', - description: 'Please enter project description' - } - }, - required: ['name', 'description'], - errorMessage: { - type: 'data should be an object', - properties: { name: 'Project name should be ...' }, - _: 'data should ...' - } -}; - -const uischema = { - type: 'Group', - label: 'Create New Project', - elements: [ - { - type: 'Control', - label: 'Name', - scope: '#/properties/name' - }, - { - type: 'Control', - label: 'Description', - scope: '#/properties/description' - } - ] -}; +import { useTranslation } from 'react-i18next'; const initialData = { name: '', @@ -57,6 +20,45 @@ export const NewProject: React.FC = () => { }); const [projectExistsQuery, projectExistsResults] = useProjectExistsLazyQuery(); const [additionalErrors, setAdditionalErrors] = useState([]); + const { t } = useTranslation(); + + const schema = { + type: 'object', + properties: { + name: { + type: 'string', + pattern: '^[a-zA-Z 0-9]*$', + description: t('components.newProject.nameDescription') + }, + description: { + type: 'string', + description: t('components.newProject.descriptionDescription') + } + }, + required: ['name', 'description'], + errorMessage: { + type: 'data should be an object', + properties: { name: 'Project name should be ...' }, + _: 'data should ...' + } + }; + + const uischema = { + type: 'Group', + label: t('components.newProject.formLabel'), + elements: [ + { + type: 'Control', + label: t('common.name'), + scope: '#/properties/name' + }, + { + type: 'Control', + label: t('common.description'), + scope: '#/properties/description' + } + ] + }; useEffect(() => { if (projectExistsResults.data?.projectExists) { @@ -101,7 +103,7 @@ export const NewProject: React.FC = () => { <> {error && ( - Failed to create project! Try again. + {t('components.newProject.failMessage')} )} { additionalErrors={additionalErrors} /> ); From e34893335c739ffb04be3b02c6ec77364e3e425e Mon Sep 17 00:00:00 2001 From: Akosah Date: Wed, 7 Feb 2024 10:43:55 -0500 Subject: [PATCH 4/6] setup more components for translation --- .../client/public/locales/en/translation.json | 63 +++++++++- .../client/public/locales/es/translation.json | 2 +- .../src/components/DatasetTable.component.tsx | 7 +- .../components/NewStudyJsonForm.component.tsx | 108 +++++++++--------- .../src/components/SideBar.component.tsx | 2 +- .../src/context/Confirmation.context.tsx | 6 +- .../pages/contribute/ContributeLanding.tsx | 24 +++- .../src/pages/datasets/DatasetControls.tsx | 16 +-- .../src/pages/datasets/ProjectAccess.tsx | 19 ++- .../client/src/pages/projects/NewProject.tsx | 4 +- .../src/pages/projects/ProjectControl.tsx | 22 ++-- .../pages/projects/ProjectUserPermissions.tsx | 9 +- .../client/src/pages/studies/DownloadTags.tsx | 7 +- .../src/pages/studies/EntryControls.tsx | 6 +- .../client/src/pages/studies/NewStudy.tsx | 14 ++- .../src/pages/studies/UserPermissions.tsx | 13 ++- 16 files changed, 215 insertions(+), 107 deletions(-) diff --git a/packages/client/public/locales/en/translation.json b/packages/client/public/locales/en/translation.json index ccc51111..35bd45dc 100644 --- a/packages/client/public/locales/en/translation.json +++ b/packages/client/public/locales/en/translation.json @@ -5,7 +5,19 @@ "instruction": "Instruction", "project": "Project", "study": "Study", - "submit": "Submit" + "submit": "Submit", + "delete": "Delete", + "cancel": "Cancel", + "ok": "OK", + "email": "Email", + "back": "Back", + "finish": "Finish", + "next": "Next", + "required": "Required", + "preview": "Preview", + "enable": "Enable", + "view": "View", + "entryId": "Entry ID" }, "languages": { "en": "English", @@ -24,7 +36,7 @@ "studies": "Studies", "newStudy": "New Study", "studyControl": "Study Control", - "entryControls": "Entry Controls", + "entryControl": "Entry Control", "downloadTags": "Download Tags", "datasets": "Datasets", "datasetControl": "Dataset Control", @@ -44,7 +56,52 @@ "formLabel": "Create New Project", "nameDescription": "Please enter project name", "descriptionDescription": "Please enter project description", - "failMessage": " Failed to create project! Try again." + "failMessage": " Failed to create project! Try again.", + "projectExists": "A project with this name already exists" + }, + "projectControl": { + "deleteStudy": "Delete Study", + "deleteDescription": "Are you sure you want to delete this project? Doing so will delete all contained studies and tags" + }, + "datasetControl": { + "deleteEntry": "Delete Entry", + "deleteDescription": "Are you sure you want to delete this project? Doing so will delete all associated tags", + "addDataset": " Add New Dataset", + "uploadEntries": " Upload Entries" + }, + "projectAccess": { + "datasetName": "Dataset Name", + "projectHasAccess": "Project Has Access", + "accessFor": "Dataset Access for ", + "selectProject": "Select a Project to Continue" + }, + "userPermissions": { + "studyAdmin": "Study Admin", + "contributor": "Contributor", + "trained": "Trained" + }, + "projectUserPermissions": { + "projectAdmin": "Project Admin", + "contributor": "Contributor", + "trained": "Trained" + }, + "newStudy": { + "createStudy": "Create New Study", + "steps": { + "Study Identification": "Study Identification", + "Construct Tagging Interface": "Construct Tagging Interface", + "Select Tag Training Items": "Select Tag Training Items" + }, + "completed": "All steps completed - your new study is created", + "startOver": "Start Over", + "formTitle": "Study Information", + "tagsDescription": "Number of times each entry needs to be tagged (default 1)" + }, + "contribute": { + "enterTagging": "Enter tagging", + "noTaggingLeft": "No tagging left", + "studyTraining": "Study Training", + "studyTagging": "Study Tagging" } } } diff --git a/packages/client/public/locales/es/translation.json b/packages/client/public/locales/es/translation.json index fcd5d96e..02960f3a 100644 --- a/packages/client/public/locales/es/translation.json +++ b/packages/client/public/locales/es/translation.json @@ -16,7 +16,7 @@ "studies": "Estudios", "newStudy": "Nuevo estudio", "studyControl": "Control del estudio", - "entryControls": "Controles de entrada", + "entryControl": "Controles de entrada", "downloadTags": "Descargar Etiquetas", "datasets": "Conjuntos de datos", "datasetControl": "Control de conjunto de datos", diff --git a/packages/client/src/components/DatasetTable.component.tsx b/packages/client/src/components/DatasetTable.component.tsx index f2ef5c46..4fc93808 100644 --- a/packages/client/src/components/DatasetTable.component.tsx +++ b/packages/client/src/components/DatasetTable.component.tsx @@ -3,6 +3,7 @@ import { useState, useEffect } from 'react'; import { Dataset, Entry } from '../graphql/graphql'; import { useEntryForDatasetLazyQuery } from '../graphql/entry/entry'; import { EntryView } from './EntryView.component'; +import { useTranslation } from 'react-i18next'; export interface DatasetTableProps { dataset: Dataset; @@ -10,16 +11,18 @@ export interface DatasetTableProps { } export const DatasetTable: React.FC = (props) => { + const { t } = useTranslation(); + const defaultColumns: GridColDef[] = [ { field: 'view', - headerName: 'View', + headerName: t('common.view'), width: 300, renderCell: (params) => }, { field: 'entryID', - headerName: 'Entry ID', + headerName: t('common.entryId'), width: 150, editable: false } diff --git a/packages/client/src/components/NewStudyJsonForm.component.tsx b/packages/client/src/components/NewStudyJsonForm.component.tsx index c40460b5..c4895a89 100644 --- a/packages/client/src/components/NewStudyJsonForm.component.tsx +++ b/packages/client/src/components/NewStudyJsonForm.component.tsx @@ -5,6 +5,7 @@ import { PartialStudyCreate } from '../types/study'; import { ErrorObject } from 'ajv'; import { useStudyExistsLazyQuery } from '../graphql/study/study'; import { useProject } from '../context/Project.context'; +import { useTranslation } from 'react-i18next'; export interface NewStudyFormProps { newStudy: PartialStudyCreate | null; @@ -12,11 +13,6 @@ export interface NewStudyFormProps { } export const NewStudyJsonForm: React.FC = (props) => { - const initialData = { - tagsPerEntry: schema.properties.tagsPerEntry.default - }; - - const [data, setData] = useState(initialData); const [studyExistsQuery, studyExistsResults] = useStudyExistsLazyQuery(); const { project } = useProject(); const [additionalErrors, setAdditionalErrors] = useState([]); @@ -24,6 +20,60 @@ export const NewStudyJsonForm: React.FC = (props) => { // Keep track of the new study internally to check to make sure the name is // unique before submitting const [potentialNewStudy, setPotentialNewStudy] = useState(null); + const { t } = useTranslation(); + + const schema = { + type: 'object', + properties: { + name: { + type: 'string', + pattern: '^[a-zA-Z 0-9]*$' + }, + description: { + type: 'string' + }, + instructions: { + type: 'string' + }, + tagsPerEntry: { + type: 'number', + default: 1 + } + }, + required: ['name', 'description', 'instructions', 'tagsPerEntry'] + }; + + const uischema = { + type: 'Group', + label: t('components.newStudy.formTitle'), + elements: [ + { + type: 'Control', + label: t('common.name'), + scope: '#/properties/name' + }, + { + type: 'Control', + label: t('common.description'), + scope: '#/properties/description' + }, + { + type: 'Control', + label: t('common.instruction'), + scope: '#/properties/instructions' + }, + { + type: 'Control', + label: t('components.newStudy.tagsDescription'), + scope: '#/properties/tagsPerEntry' + } + ] + }; + + const initialData = { + tagsPerEntry: schema.properties.tagsPerEntry.default + }; + const [data, setData] = useState(initialData); const handleChange = (data: any, errors: ErrorObject[] | undefined) => { setData(data); @@ -68,51 +118,3 @@ export const NewStudyJsonForm: React.FC = (props) => { /> ); }; - -const schema = { - type: 'object', - properties: { - name: { - type: 'string', - pattern: '^[a-zA-Z 0-9]*$' - }, - description: { - type: 'string' - }, - instructions: { - type: 'string' - }, - tagsPerEntry: { - type: 'number', - default: 1 - } - }, - required: ['name', 'description', 'instructions', 'tagsPerEntry'] -}; - -const uischema = { - type: 'Group', - label: 'Study Information', - elements: [ - { - type: 'Control', - label: 'Name', - scope: '#/properties/name' - }, - { - type: 'Control', - label: 'Description', - scope: '#/properties/description' - }, - { - type: 'Control', - label: 'Instructions', - scope: '#/properties/instructions' - }, - { - type: 'Control', - label: 'Number of times each entry needs to be tagged (default 1)', - scope: '#/properties/tagsPerEntry' - } - ] -}; diff --git a/packages/client/src/components/SideBar.component.tsx b/packages/client/src/components/SideBar.component.tsx index 98dde7ca..5144739d 100644 --- a/packages/client/src/components/SideBar.component.tsx +++ b/packages/client/src/components/SideBar.component.tsx @@ -62,7 +62,7 @@ export const SideBar: FC = ({ open, drawerWidth }) => { action: () => navigate('/study/permissions'), visible: (p) => p!.studyAdmin }, - { name: t('menu.entryControls'), action: () => navigate('/study/entries'), visible: (p) => p!.studyAdmin }, + { name: t('menu.entryControl'), action: () => navigate('/study/entries'), visible: (p) => p!.studyAdmin }, { name: t('menu.downloadTags'), action: () => navigate('/study/tags'), visible: (p) => p!.studyAdmin } ] }, diff --git a/packages/client/src/context/Confirmation.context.tsx b/packages/client/src/context/Confirmation.context.tsx index 057099e1..8854ca22 100644 --- a/packages/client/src/context/Confirmation.context.tsx +++ b/packages/client/src/context/Confirmation.context.tsx @@ -1,5 +1,6 @@ import { createContext, useContext, useState } from 'react'; import { Dialog, DialogContent, DialogTitle, DialogActions, Button, Typography } from '@mui/material'; +import { useTranslation } from 'react-i18next'; export interface ConfirmationRequest { message: string; @@ -21,6 +22,7 @@ export interface ConfirmationProviderProps { export const ConfirmationProvider: React.FC = ({ children }) => { const [open, setOpen] = useState(false); const [confirmationRequest, setConfirmationRequest] = useState(null); + const { t } = useTranslation(); const pushConfirmationRequest = (confirmationRequest: ConfirmationRequest) => { setConfirmationRequest(confirmationRequest); @@ -50,9 +52,9 @@ export const ConfirmationProvider: React.FC = ({ chil - + {children} diff --git a/packages/client/src/pages/contribute/ContributeLanding.tsx b/packages/client/src/pages/contribute/ContributeLanding.tsx index 5272fd17..d57b7241 100644 --- a/packages/client/src/pages/contribute/ContributeLanding.tsx +++ b/packages/client/src/pages/contribute/ContributeLanding.tsx @@ -2,11 +2,13 @@ import { Typography, Box, Stack, Button } from '@mui/material'; import { useNavigate } from 'react-router-dom'; import { useStudy } from '../../context/Study.context'; import { TagProvider, useTag } from '../../context/Tag.context'; +import { useTranslation } from 'react-i18next'; const ContributeLandingInternal: React.FC = () => { const navigate = useNavigate(); const { study } = useStudy(); const { tag } = useTag(); + const { t } = useTranslation(); const enterTagging = () => { navigate('/contribute/tagging'); @@ -18,17 +20,27 @@ const ContributeLandingInternal: React.FC = () => { <> {study && ( - Study: {study.name} + + {t('common.study')}: {study.name} + - {false ? 'Study Training' : 'Study Tagging'} - Study: {study.name} - Description: {study.description} - Instructions: {study.instructions} + + {false ? t('components.contribute.studyTraining') : t('components.contribute.studyTagging')} + + + {t('common.study')}: {study.name} + + + {t('common.description')}: {study.description} + + + {t('common.instruction')}: {study.instructions} + )} diff --git a/packages/client/src/pages/datasets/DatasetControls.tsx b/packages/client/src/pages/datasets/DatasetControls.tsx index 44164ab5..a66f93c5 100644 --- a/packages/client/src/pages/datasets/DatasetControls.tsx +++ b/packages/client/src/pages/datasets/DatasetControls.tsx @@ -10,6 +10,7 @@ import { GridColDef, GridActionsCellItem, GridRowId } from '@mui/x-data-grid'; import DeleteIcon from '@mui/icons-material/DeleteOutlined'; import { useConfirmation } from '../../context/Confirmation.context'; import { useDeleteEntryMutation } from '../../graphql/entry/entry'; +import { useTranslation } from 'react-i18next'; export const DatasetControls: React.FC = () => { const [add, setAdd] = useState(false); @@ -17,6 +18,7 @@ export const DatasetControls: React.FC = () => { const [datasets, setDatasets] = useState([]); const [getDatasets, getDatasetsResults] = useGetDatasetsLazyQuery(); const [deleteEntryMutation] = useDeleteEntryMutation(); + const { t } = useTranslation(); const confirmation = useConfirmation(); @@ -52,8 +54,8 @@ export const DatasetControls: React.FC = () => { const handleDelete = async (id: GridRowId) => { // Execute delete mutation confirmation.pushConfirmationRequest({ - title: 'Delete Entry', - message: 'Are you sure you want to delete this project? Doing so will delete all associated tags', + title: t('components.datasetControl.deleteEntry'), + message: t('components.datasetControl.deleteDescription'), onConfirm: async () => { const res = await deleteEntryMutation({ variables: { entry: id.toString() } }); if (res.errors) { @@ -71,7 +73,7 @@ export const DatasetControls: React.FC = () => { { field: 'delete', type: 'actions', - headerName: 'Delete', + headerName: t('common.delete'), width: 120, maxWidth: 120, cellClassName: 'delete', @@ -79,7 +81,7 @@ export const DatasetControls: React.FC = () => { return [ } - label="Delete" + label={t('common.delete')} onClick={() => handleDelete(params.id)} /> ]; @@ -89,7 +91,7 @@ export const DatasetControls: React.FC = () => { return ( <> - Dataset Controls + {t('menu.datasetControl')} { - Add New Dataset + {t('components.datasetControl.addDataset')} { - Upload Entries + {t('components.datasetControl.uploadEntries')} diff --git a/packages/client/src/pages/datasets/ProjectAccess.tsx b/packages/client/src/pages/datasets/ProjectAccess.tsx index 9fb082c8..c0fc8a48 100644 --- a/packages/client/src/pages/datasets/ProjectAccess.tsx +++ b/packages/client/src/pages/datasets/ProjectAccess.tsx @@ -5,12 +5,14 @@ import { useGetDatasetProjectPermissionsLazyQuery } from '../../graphql/permissi import { useEffect, useState } from 'react'; import { DatasetProjectPermission, Project } from '../../graphql/graphql'; import { useGrantProjectDatasetAccessMutation } from '../../graphql/permission/permission'; +import { useTranslation } from 'react-i18next'; export const ProjectAccess: React.FC = () => { const { project } = useProject(); const [getDatasetProjectPermissions, datasetProjectPermissionResults] = useGetDatasetProjectPermissionsLazyQuery(); const [projectAccess, setProjectAccess] = useState([]); const [grantProjectDatasetAccess, grantProjectDatasetAccessResults] = useGrantProjectDatasetAccessMutation(); + const { t } = useTranslation(); // For querying for the permissions useEffect(() => { @@ -38,16 +40,21 @@ export const ProjectAccess: React.FC = () => { }; const columns: GridColDef[] = [ - { field: 'name', headerName: 'Dataset Name', width: 200, valueGetter: (params) => params.row.dataset.name }, + { + field: 'name', + headerName: t('components.projectAccess.datasetName'), + width: 200, + valueGetter: (params) => params.row.dataset.name + }, { field: 'description', - headerName: 'Description', + headerName: t('common.description'), width: 200, valueGetter: (params) => params.row.dataset.description }, { field: 'access', - headerName: 'Project Has Access', + headerName: t('components.projectAccess.projectHasAccess'), width: 200, renderCell: (params) => ( @@ -59,11 +66,13 @@ export const ProjectAccess: React.FC = () => { <> {project ? ( <> - Dataset Access for "{project.name}" + + {t('components.projectAccess.accessFor')}"{project.name}" + row.dataset._id} /> ) : ( - Select a Project to Continue + {t('components.projectAccess.selectProject')} )} ); diff --git a/packages/client/src/pages/projects/NewProject.tsx b/packages/client/src/pages/projects/NewProject.tsx index 62d822ea..abe14f94 100644 --- a/packages/client/src/pages/projects/NewProject.tsx +++ b/packages/client/src/pages/projects/NewProject.tsx @@ -66,7 +66,7 @@ export const NewProject: React.FC = () => { { instancePath: '/name', keyword: 'uniqueProjectName', - message: 'A project with this name already exists', + message: t('components.newProject.projectExists'), schemaPath: '#/properties/name/name', params: { keyword: 'uniqueProjectName' } } @@ -116,7 +116,7 @@ export const NewProject: React.FC = () => { additionalErrors={additionalErrors} /> ); diff --git a/packages/client/src/pages/projects/ProjectControl.tsx b/packages/client/src/pages/projects/ProjectControl.tsx index 62471768..894a84b2 100644 --- a/packages/client/src/pages/projects/ProjectControl.tsx +++ b/packages/client/src/pages/projects/ProjectControl.tsx @@ -7,18 +7,20 @@ import { Project } from '../../graphql/graphql'; import { useDeleteProjectMutation } from '../../graphql/project/project'; import { useConfirmation } from '../../context/Confirmation.context'; import { useEffect } from 'react'; +import { useTranslation } from 'react-i18next'; const ProjectControl: React.FC = () => { const { projects, updateProjectList } = useProject(); const [deleteProjectMutation, deleteProjectResults] = useDeleteProjectMutation(); const confirmation = useConfirmation(); + const { t } = useTranslation(); const handleDelete = async (id: GridRowId) => { // Execute delete mutation confirmation.pushConfirmationRequest({ - title: 'Delete Study', - message: 'Are you sure you want to delete this project? Doing so will delete all contained studies and tags', + title: t('components.projectControl.deleteStudy'), + message: t('components.projectControl.deleteDescription'), onConfirm: () => { deleteProjectMutation({ variables: { project: id.toString() } }); }, @@ -36,32 +38,38 @@ const ProjectControl: React.FC = () => { const columns: GridColDef[] = [ { field: 'name', - headerName: 'Name', + headerName: t('common.name'), width: 200, editable: false }, { field: 'description', - headerName: 'Description', + headerName: t('common.description'), width: 500, editable: false }, { field: 'delete', type: 'actions', - headerName: 'Delete', + headerName: t('common.delete'), width: 120, maxWidth: 120, cellClassName: 'delete', getActions: (params) => { - return [} label="Delete" onClick={() => handleDelete(params.id)} />]; + return [ + } + label={t('common.delete')} + onClick={() => handleDelete(params.id)} + /> + ]; } } ]; return ( <> - Project Control + {t('menu.projectControl')} row._id} /> diff --git a/packages/client/src/pages/projects/ProjectUserPermissions.tsx b/packages/client/src/pages/projects/ProjectUserPermissions.tsx index 4d3a9674..7d649fd4 100644 --- a/packages/client/src/pages/projects/ProjectUserPermissions.tsx +++ b/packages/client/src/pages/projects/ProjectUserPermissions.tsx @@ -6,13 +6,15 @@ import { ProjectPermissionModel, Project } from '../../graphql/graphql'; import { useGetProjectPermissionsQuery } from '../../graphql/permission/permission'; import { DecodedToken, useAuth } from '../../context/Auth.context'; import { useGrantProjectPermissionsMutation } from '../../graphql/permission/permission'; +import { useTranslation } from 'react-i18next'; export const ProjectUserPermissions: React.FC = () => { const { project } = useProject(); + const { t } = useTranslation(); return ( <> - User Permissions + {t('menu.userPermissions')} {project && } ); @@ -62,6 +64,7 @@ const UserPermissionTable: React.FC<{ project: Project }> = ({ project }) => { const [rows, setRows] = useState([]); const { decodedToken } = useAuth(); + const { t } = useTranslation(); useEffect(() => { if (data?.getProjectPermissions) { @@ -88,7 +91,7 @@ const UserPermissionTable: React.FC<{ project: Project }> = ({ project }) => { */ { field: 'email', - headerName: 'Email', + headerName: t('common.email'), valueGetter: (params) => params.row.user.email, flex: 1.75, editable: false @@ -96,7 +99,7 @@ const UserPermissionTable: React.FC<{ project: Project }> = ({ project }) => { { field: 'projectAdmin', type: 'boolean', - headerName: 'Project Admin', + headerName: t('components.projectUserPermissions.projectAdmin'), valueGetter: (params) => params.row.hasRole, renderCell: (params: GridRenderCellParams) => { return ( diff --git a/packages/client/src/pages/studies/DownloadTags.tsx b/packages/client/src/pages/studies/DownloadTags.tsx index 6b32995d..c3ff29dd 100644 --- a/packages/client/src/pages/studies/DownloadTags.tsx +++ b/packages/client/src/pages/studies/DownloadTags.tsx @@ -1,11 +1,14 @@ import { Button, Container, Typography } from '@mui/material'; +import { useTranslation } from 'react-i18next'; export const DownloadTags: React.FC = () => { + const { t } = useTranslation(); + return ( - Download Tags + {t('menu.downloadTags')} ); diff --git a/packages/client/src/pages/studies/EntryControls.tsx b/packages/client/src/pages/studies/EntryControls.tsx index f99688f4..428238c7 100644 --- a/packages/client/src/pages/studies/EntryControls.tsx +++ b/packages/client/src/pages/studies/EntryControls.tsx @@ -6,6 +6,7 @@ import { DatasetsView } from '../../components/DatasetsView.component'; import { useGetDatasetsByProjectQuery } from '../../graphql/dataset/dataset'; import { useProject } from '../../context/Project.context'; import ToggleEntryEnabled from '../../components/ToggleEntryEnabled.component'; +import { useTranslation } from 'react-i18next'; export const EntryControls: React.FC = () => { const { project } = useProject(); @@ -15,6 +16,7 @@ export const EntryControls: React.FC = () => { project: project ? project._id : '' } }); + const { t } = useTranslation(); useEffect(() => { if (getDatasetsByProjectResults.data) { @@ -26,7 +28,7 @@ export const EntryControls: React.FC = () => { { field: 'enabled', type: 'actions', - headerName: 'Enable', + headerName: t('common.enable'), width: 120, maxWidth: 120, cellClassName: 'enabled', @@ -38,7 +40,7 @@ export const EntryControls: React.FC = () => { return ( <> - Entry Control + {t('menu.entryControl')} ); diff --git a/packages/client/src/pages/studies/NewStudy.tsx b/packages/client/src/pages/studies/NewStudy.tsx index 44bf3f77..8e269e54 100644 --- a/packages/client/src/pages/studies/NewStudy.tsx +++ b/packages/client/src/pages/studies/NewStudy.tsx @@ -10,6 +10,7 @@ import { useProject } from '../../context/Project.context'; import { useStudy } from '../../context/Study.context'; import { useApolloClient } from '@apollo/client'; import { CreateTagsDocument } from '../../graphql/tag/tag'; +import { useTranslation } from 'react-i18next'; export const NewStudy: React.FC = () => { const [activeStep, setActiveStep] = useState(0); @@ -21,6 +22,7 @@ export const NewStudy: React.FC = () => { const [_trainingSet, setTrainingSet] = useState([]); const [taggingSet, setTaggingSet] = useState([]); const apolloClient = useApolloClient(); + const { t } = useTranslation(); // Handles mantaining which step the user is on and the step limit useEffect(() => { @@ -109,22 +111,22 @@ export const NewStudy: React.FC = () => { return ( <> - Create New Study + {t('components.newStudy.createStudy')} {steps.map((label) => { return ( - {label} + {t('components.newStudy.steps.' + label)} ); })} {activeStep === steps.length ? ( <> - All steps completed - your new study is created + {t('components.newStudy.completed')} - + ) : ( @@ -132,10 +134,10 @@ export const NewStudy: React.FC = () => { {getSectionComponent()} diff --git a/packages/client/src/pages/studies/UserPermissions.tsx b/packages/client/src/pages/studies/UserPermissions.tsx index f752d4cc..3bdc59cf 100644 --- a/packages/client/src/pages/studies/UserPermissions.tsx +++ b/packages/client/src/pages/studies/UserPermissions.tsx @@ -10,13 +10,15 @@ import { useGrantTrainedContributorMutation } from '../../graphql/permission/permission'; import { useEffect, useState } from 'react'; +import { useTranslation } from 'react-i18next'; export const StudyUserPermissions: React.FC = () => { const { study } = useStudy(); + const { t } = useTranslation(); return ( <> - User Permissions + {t('menu.userPermissions')} {study && } ); @@ -122,6 +124,7 @@ const UserPermissionTable: React.FC<{ study: Study }> = ({ study }) => { }); const [permissions, setPermissions] = useState([]); + const { t } = useTranslation(); useEffect(() => { if (data) { @@ -132,7 +135,7 @@ const UserPermissionTable: React.FC<{ study: Study }> = ({ study }) => { const columns: GridColDef[] = [ { field: 'email', - headerName: 'Email', + headerName: t('common.email'), valueGetter: (params) => params.row.user.email, flex: 1.75, editable: false @@ -140,7 +143,7 @@ const UserPermissionTable: React.FC<{ study: Study }> = ({ study }) => { { field: 'studyAdmin', type: 'boolean', - headerName: 'Study Admin', + headerName: t('components.userPermissions.studyAdmin'), valueGetter: (params) => params.row.isStudyAdmin, renderCell: (params: GridRenderCellParams) => { return ( @@ -152,7 +155,7 @@ const UserPermissionTable: React.FC<{ study: Study }> = ({ study }) => { }, { field: 'contributor', - headerName: 'Contributor', + headerName: t('components.userPermissions.contributor'), valueGetter: (params) => params.row.isContributor, renderCell: (params: GridRenderCellParams) => { return ( @@ -164,7 +167,7 @@ const UserPermissionTable: React.FC<{ study: Study }> = ({ study }) => { }, { field: 'trained', - headerName: 'Trained', + headerName: t('components.userPermissions.trained'), valueGetter: (params) => params.row.isTrained, renderCell: (params: GridRenderCellParams) => { return ( From 99da9be129fdac0d1cc5370ab280761a97020c46 Mon Sep 17 00:00:00 2001 From: Akosah Date: Thu, 8 Feb 2024 15:21:50 -0500 Subject: [PATCH 5/6] setup more components for transalations --- .../client/public/locales/en/translation.json | 16 +++++++++++++- .../components/DatasetAccess.component.tsx | 8 ++++--- .../contribute/TagForm.component.tsx | 6 +++-- packages/client/src/pages/LoginPage.tsx | 6 +++-- packages/client/src/pages/SuccessPage.tsx | 7 ++++-- .../client/src/pages/studies/StudyControl.tsx | 22 +++++++++++++------ 6 files changed, 48 insertions(+), 17 deletions(-) diff --git a/packages/client/public/locales/en/translation.json b/packages/client/public/locales/en/translation.json index fdcdbc61..1a4631d1 100644 --- a/packages/client/public/locales/en/translation.json +++ b/packages/client/public/locales/en/translation.json @@ -13,11 +13,14 @@ "back": "Back", "finish": "Finish", "next": "Next", + "continue": "Continue", "required": "Required", "preview": "Preview", "enable": "Enable", "view": "View", - "entryId": "Entry ID" + "entryId": "Entry ID", + "login": "Login", + "clear": "clear" }, "languages": { "en": "English", @@ -97,12 +100,23 @@ "formTitle": "Study Information", "tagsDescription": "Number of times each entry needs to be tagged (default 1)" }, + "studyControl": { + "deleteStudy": "Delete Study", + "deleteDescription": "Are you sure you want to delete this study? Doing so will delete all contained tags" + }, "contribute": { "enterTagging": "Enter tagging", "noTaggingLeft": "No tagging left", "studyTraining": "Study Training", "studyTagging": "Study Tagging", "failMessage": " Failed to create project! Try again." + }, + "successPage": { + "succefullyCreated": "Successfully created!" + }, + "login": { + "selectOrg": "Select an Organization to Login", + "redirectToOrg": "Redirect to Organization Login" } } } diff --git a/packages/client/src/components/DatasetAccess.component.tsx b/packages/client/src/components/DatasetAccess.component.tsx index b1f0ea36..cf6d5c00 100644 --- a/packages/client/src/components/DatasetAccess.component.tsx +++ b/packages/client/src/components/DatasetAccess.component.tsx @@ -3,6 +3,7 @@ import useEnhancedEffect from '@mui/material/utils/useEnhancedEffect'; import { DataGrid, GridColDef, GridRenderCellParams, useGridApiContext } from '@mui/x-data-grid'; import { GridRowModesModel } from '@mui/x-data-grid-pro'; import { useRef, useState } from 'react'; +import { useTranslation } from 'react-i18next'; interface Row { id: number; @@ -41,6 +42,7 @@ const renderSwitchEditInputCell: GridColDef['renderCell'] = (params) => { export const DatasetAccess: React.FC = ({ tableRows }: Table) => { const [rows] = useState(tableRows); const [rowModesModel, setRowModesModel] = useState({}); + const { t } = useTranslation(); const handleRowModesModelChange = (newRowModesModel: GridRowModesModel) => { setRowModesModel(newRowModesModel); @@ -50,20 +52,20 @@ export const DatasetAccess: React.FC
= ({ tableRows }: Table) => { { field: 'id', headerName: 'ID', width: 55 }, { field: 'name', - headerName: 'Name', + headerName: t('common.name'), width: 200, editable: true }, { field: 'description', - headerName: 'Description', + headerName: t('common.description'), width: 450, editable: true }, { field: 'switch', type: 'boolean', - headerName: 'Project Access', + headerName: t('menu.projectAccess'), renderCell: (params) => , renderEditCell: renderSwitchEditInputCell, editable: true, diff --git a/packages/client/src/components/contribute/TagForm.component.tsx b/packages/client/src/components/contribute/TagForm.component.tsx index f4c62151..98da1e16 100644 --- a/packages/client/src/components/contribute/TagForm.component.tsx +++ b/packages/client/src/components/contribute/TagForm.component.tsx @@ -8,6 +8,7 @@ import AslLexSearchControl from '../tag/asllex/AslLexSearchControl'; import AslLexSearchControlTester from '../tag/asllex/aslLexSearchControlTester'; import VideoRecordField, { videoFieldTester } from '../tag/videorecord/VideoRecordField.component'; import { JsonFormsRendererRegistryEntry } from '@jsonforms/core'; +import { useTranslation } from 'react-i18next'; export interface TagFormProps { study: Study; @@ -17,6 +18,7 @@ export interface TagFormProps { export const TagForm: React.FC = (props) => { const [data, setData] = useState(); const [dataValid, setDataValid] = useState(false); + const { t } = useTranslation(); const handleFormChange = (data: any, errors: ErrorObject[] | undefined) => { setData(data); @@ -62,10 +64,10 @@ export const TagForm: React.FC = (props) => { /> diff --git a/packages/client/src/pages/LoginPage.tsx b/packages/client/src/pages/LoginPage.tsx index 6abdbce3..79ebf88d 100644 --- a/packages/client/src/pages/LoginPage.tsx +++ b/packages/client/src/pages/LoginPage.tsx @@ -4,11 +4,13 @@ import { useAuth } from '../context/Auth.context'; import { useNavigate } from 'react-router-dom'; import { Organization } from '../graphql/graphql'; import { useGetOrganizationsQuery } from '../graphql/organization/organization'; +import { useTranslation } from 'react-i18next'; export const LoginPage: FC = () => { // Construct the Auth URL const { authenticated } = useAuth(); const navigate = useNavigate(); + const { t } = useTranslation(); const [organization, setOrganization] = useState(null); const [authURL, setAuthURL] = useState(null); @@ -62,7 +64,7 @@ export const LoginPage: FC = () => { alignItems: 'center' }} > - Login + {t('common.login')} diff --git a/packages/client/src/pages/SuccessPage.tsx b/packages/client/src/pages/SuccessPage.tsx index 3ac27e80..82076a3c 100644 --- a/packages/client/src/pages/SuccessPage.tsx +++ b/packages/client/src/pages/SuccessPage.tsx @@ -1,11 +1,14 @@ import { Typography, Button } from '@mui/material'; +import { useTranslation } from 'react-i18next'; export const SuccessPage = () => { + const { t } = useTranslation(); + return (
- Successfully created! + {t('components.successPage.succefullyCreated')}
); diff --git a/packages/client/src/pages/studies/StudyControl.tsx b/packages/client/src/pages/studies/StudyControl.tsx index 96e6ab08..bda2db98 100644 --- a/packages/client/src/pages/studies/StudyControl.tsx +++ b/packages/client/src/pages/studies/StudyControl.tsx @@ -7,18 +7,20 @@ import { Study } from '../../graphql/graphql'; import { useDeleteStudyMutation } from '../../graphql/study/study'; import { useEffect } from 'react'; import { useConfirmation } from '../../context/Confirmation.context'; +import { useTranslation } from 'react-i18next'; export const StudyControl: React.FC = () => { const { studies, updateStudies } = useStudy(); const [deleteStudyMutation, deleteStudyResults] = useDeleteStudyMutation(); const confirmation = useConfirmation(); + const { t } = useTranslation(); const handleDelete = async (id: GridRowId) => { // Execute delete mutation confirmation.pushConfirmationRequest({ - title: 'Delete Study', - message: 'Are you sure you want to delete this study? Doing so will delete all contained tags', + title: t('components.studyControl.deleteStudy'), + message: t('components.studyControl.deleteDescription'), onConfirm: () => { deleteStudyMutation({ variables: { study: id.toString() } }); }, @@ -36,32 +38,38 @@ export const StudyControl: React.FC = () => { const columns: GridColDef[] = [ { field: 'name', - headerName: 'Name', + headerName: t('common.name'), width: 200, editable: false }, { field: 'description', - headerName: 'Description', + headerName: t('common.description'), width: 500, editable: false }, { field: 'delete', type: 'actions', - headerName: 'Delete', + headerName: t('common.delete'), width: 120, maxWidth: 120, cellClassName: 'delete', getActions: (params) => { - return [} label="Delete" onClick={() => handleDelete(params.id)} />]; + return [ + } + label={t('common.delete')} + onClick={() => handleDelete(params.id)} + /> + ]; } } ]; return ( <> - Study Control + {t('menu.studyControl')} row._id} /> From acb952b5435014211849adf43495077f727fddeb Mon Sep 17 00:00:00 2001 From: Akosah Date: Fri, 9 Feb 2024 11:18:44 -0500 Subject: [PATCH 6/6] add key --- packages/client/src/components/LanguageSelector.tsx | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/packages/client/src/components/LanguageSelector.tsx b/packages/client/src/components/LanguageSelector.tsx index 23434efa..9e9b48b8 100644 --- a/packages/client/src/components/LanguageSelector.tsx +++ b/packages/client/src/components/LanguageSelector.tsx @@ -25,7 +25,9 @@ export const LanguageSelector: React.FC = () => { {t('components.languageSelector.selectLanguage')}