diff --git a/package.json b/package.json index bcb783e..1109b29 100644 --- a/package.json +++ b/package.json @@ -23,11 +23,14 @@ "clsx": "^2.1.0", "express": "^4.19.2", "html-to-image": "^1.11.11", + "i18next": "^23.11.1", + "i18next-browser-languagedetector": "^7.2.1", "lodash.throttle": "^4.1.1", "prettier": "^3.2.5", "react": "^18.2.0", "react-dom": "^18.2.0", "react-hook-form": "^7.51.2", + "react-i18next": "^14.1.0", "react-popper": "^2.3.0", "react-router-dom": "^6.22.3", "tailwind-merge": "^2.2.2", diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index 2c9b049..dd1e176 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -32,6 +32,12 @@ dependencies: html-to-image: specifier: ^1.11.11 version: 1.11.11 + i18next: + specifier: ^23.11.1 + version: 23.11.2 + i18next-browser-languagedetector: + specifier: ^7.2.1 + version: 7.2.1 lodash.throttle: specifier: ^4.1.1 version: 4.1.1 @@ -47,6 +53,9 @@ dependencies: react-hook-form: specifier: ^7.51.2 version: 7.51.2(react@18.2.0) + react-i18next: + specifier: ^14.1.0 + version: 14.1.0(i18next@23.11.2)(react-dom@18.2.0)(react@18.2.0) react-popper: specifier: ^2.3.0 version: 2.3.0(@popperjs/core@2.11.8)(react-dom@18.2.0)(react@18.2.0) @@ -2916,6 +2925,12 @@ packages: function-bind: 1.1.2 dev: false + /html-parse-stringify@3.0.1: + resolution: {integrity: sha512-KknJ50kTInJ7qIScF3jeaFRpMpE8/lfiTdzf/twXyPBLAGrLRTmkz3AdTnKeh40X8k9L2fdYwEp/42WGXIRGcg==} + dependencies: + void-elements: 3.1.0 + dev: false + /html-to-image@1.11.11: resolution: {integrity: sha512-9gux8QhvjRO/erSnDPv28noDZcPZmYE7e1vFsBLKLlRlKDSqNJYebj6Qz1TGd5lsRV+X+xYyjCKjuZdABinWjA==} dev: false @@ -2947,6 +2962,18 @@ packages: hasBin: true dev: true + /i18next-browser-languagedetector@7.2.1: + resolution: {integrity: sha512-h/pM34bcH6tbz8WgGXcmWauNpQupCGr25XPp9cZwZInR9XHSjIFDYp1SIok7zSPsTOMxdvuLyu86V+g2Kycnfw==} + dependencies: + '@babel/runtime': 7.24.1 + dev: false + + /i18next@23.11.2: + resolution: {integrity: sha512-qMBm7+qT8jdpmmDw/kQD16VpmkL9BdL+XNAK5MNbNFaf1iQQq35ZbPrSlqmnNPOSUY4m342+c0t0evinF5l7sA==} + dependencies: + '@babel/runtime': 7.24.1 + dev: false + /iconv-lite@0.4.24: resolution: {integrity: sha512-v3MXnZAcvnywkTUEZomIActle7RXXeedOR31wwl7VlyoXO4Qi9arvSenNQWne1TcRwhCL1HwLI21bEqdpj8/rA==} engines: {node: '>=0.10.0'} @@ -3682,6 +3709,26 @@ packages: react: 18.2.0 dev: false + /react-i18next@14.1.0(i18next@23.11.2)(react-dom@18.2.0)(react@18.2.0): + resolution: {integrity: sha512-3KwX6LHpbvGQ+sBEntjV4sYW3Zovjjl3fpoHbUwSgFHf0uRBcbeCBLR5al6ikncI5+W0EFb71QXZmfop+J6NrQ==} + peerDependencies: + i18next: '>= 23.2.3' + react: '>= 16.8.0' + react-dom: '*' + react-native: '*' + peerDependenciesMeta: + react-dom: + optional: true + react-native: + optional: true + dependencies: + '@babel/runtime': 7.24.1 + html-parse-stringify: 3.0.1 + i18next: 23.11.2 + react: 18.2.0 + react-dom: 18.2.0(react@18.2.0) + dev: false + /react-is@16.13.1: resolution: {integrity: sha512-24e6ynE2H+OKt4kqsOvNd8kBpV65zoxbA4BVsEOB3ARVWQki/DHzaUoC5KuON/BiccDaCCTZBuOcfZs70kR8bQ==} dev: false @@ -4287,6 +4334,11 @@ packages: optionalDependencies: fsevents: 2.3.3 + /void-elements@3.1.0: + resolution: {integrity: sha512-Dhxzh5HZuiHQhbvTW9AMetFfBHDMYpo23Uo9btPXgdYP+3T5S+p+jgNy7spra+veYhBP2dCSgxR/i2Y02h5/6w==} + engines: {node: '>=0.10.0'} + dev: false + /warning@4.0.3: resolution: {integrity: sha512-rpJyN222KWIvHJ/F53XSZv0Zl/accqHR8et1kpaMTD/fLCRxtV8iX8czMzY7sVZupTI3zcUTg8eycS2kNF9l6w==} dependencies: diff --git a/src/App.tsx b/src/App.tsx index f267d9a..e39d62e 100644 --- a/src/App.tsx +++ b/src/App.tsx @@ -8,6 +8,8 @@ import { apiClient } from '@utils'; import { Route, Routes } from 'react-router-dom'; import { useUserStore } from './store/useUserStore'; +import '@config/i18n'; + function App() { const setUser = useUserStore((state) => state.setUser); diff --git a/src/common/types/Language.ts b/src/common/types/Language.ts new file mode 100644 index 0000000..87da08c --- /dev/null +++ b/src/common/types/Language.ts @@ -0,0 +1,4 @@ +export enum Language { + ES = 'es', + EN = 'en' +} diff --git a/src/components/Buttons/ToggleButtonGroup/ToggleButtonGroup.tsx b/src/components/Buttons/ToggleButtonGroup/ToggleButtonGroup.tsx index 3d1150f..089096a 100644 --- a/src/components/Buttons/ToggleButtonGroup/ToggleButtonGroup.tsx +++ b/src/components/Buttons/ToggleButtonGroup/ToggleButtonGroup.tsx @@ -1,6 +1,7 @@ import { Dispatch } from 'react'; import { ButtonSize, cn, Variant } from '@common'; import { Button } from '@components'; +import { useTranslation } from 'react-i18next'; interface ToggleButtonGroupProps { /** @@ -25,6 +26,7 @@ interface ToggleButtonGroupProps { } export const ToggleButtonGroup = ({ className, isActive, setIsActive, setFilter }: ToggleButtonGroupProps) => { + const { t } = useTranslation(); const classes = { container: cn('max-sm:grid max-sm:gap-4 ', className) }; @@ -44,7 +46,7 @@ export const ToggleButtonGroup = ({ className, isActive, setIsActive, setFilter variant={handleButtonVariant(isActive)} className="sm:rounded-r-none max-sm:w-full" > - Activos + {t('common_active')} ); diff --git a/src/components/Cards/ProjectCard/ProjectCard.tsx b/src/components/Cards/ProjectCard/ProjectCard.tsx index dff81b7..58f6030 100644 --- a/src/components/Cards/ProjectCard/ProjectCard.tsx +++ b/src/components/Cards/ProjectCard/ProjectCard.tsx @@ -1,6 +1,7 @@ import { HTMLAttributes } from 'react'; import { cn, groupParticipantsByRole, Project } from '@common'; import { Button, CardWrapper, Popover, Tag } from '@components'; +import { useTranslation } from 'react-i18next'; interface ProjectCardProps extends Omit, HTMLAttributes { /** @@ -43,6 +44,7 @@ export const ProjectCard = ({ 'bg-gradient-to-rb from-primary-600 to-secondary-500 w-5 h-5 rounded-full text-xs flex items-center justify-center cursor-pointer select-none' ) }; + const { t } = useTranslation(); const handleDescription = () => { if (description.length > 175) return `${description.slice(0, 130)}...`; @@ -104,7 +106,7 @@ export const ProjectCard = ({ {/* Participants Section */}

- Participantes + {t('common_participants')}

    {renderParticipantsTag}
@@ -114,7 +116,7 @@ export const ProjectCard = ({ <>

- Estamos buscando + {t('project_card_look_for_role_section_title')}

    {renderRequiredRolesTag()}
diff --git a/src/components/Countdown/Countdown.tsx b/src/components/Countdown/Countdown.tsx index 4132306..ca2fc29 100644 --- a/src/components/Countdown/Countdown.tsx +++ b/src/components/Countdown/Countdown.tsx @@ -1,22 +1,24 @@ import { useEffect, useState } from 'react'; +import { useTranslation } from 'react-i18next'; export const Countdown = () => { + const { t } = useTranslation(); const time = [ { key: 'days', - label: 'Días' + label: t('common_days') }, { key: 'hours', - label: 'Horas' + label: t('common_hours') }, { key: 'minutes', - label: 'Minutos' + label: t('common_minutes') }, { key: 'seconds', - label: 'Segundos' + label: t('common_seconds') } ]; diff --git a/src/components/Footer/Footer.tsx b/src/components/Footer/Footer.tsx index db6c250..6450364 100644 --- a/src/components/Footer/Footer.tsx +++ b/src/components/Footer/Footer.tsx @@ -1,4 +1,5 @@ import { cn, ROUTE } from '@common'; +import { Trans, useTranslation } from 'react-i18next'; import { Link } from 'react-router-dom'; interface SocialIcon { @@ -21,6 +22,8 @@ const socialIcons: SocialIcon[] = [ ]; export const Footer = () => { + const { t } = useTranslation(); + const currentYear = new Date().getFullYear(); const classes = { container: cn('text-cWhite bg-gradient-to-r from-[#19101D] to-[#0D0D0E] py-5 w-full font-dmsans'), innerContainer: cn('max-w-7xl w-full mx-auto text-center mb-10 flex flex-col justify-between items-center'), @@ -53,7 +56,7 @@ export const Footer = () => {

- Más información del evento + {t('footer_social_networks_title')}

diff --git a/src/components/Navigation/LoggedUser.tsx b/src/components/Navigation/LoggedUser.tsx index e798ac7..9c56ea1 100644 --- a/src/components/Navigation/LoggedUser.tsx +++ b/src/components/Navigation/LoggedUser.tsx @@ -1,6 +1,7 @@ import { FC, useRef, useState } from 'react'; import { AvatarSize, cn, useAuth, useBreakpoint, useOnClickOutside } from '@common'; import { User } from '@supabase/supabase-js'; +import { useTranslation } from 'react-i18next'; import { Avatar } from '../Avatar/Avatar'; interface Props { @@ -21,6 +22,7 @@ export const LoggedUser: FC = ({ className, user, avatarSize = AvatarSize const { signOut } = useAuth(); const { isMobile } = useBreakpoint(); const [isOpen, setIsOpen] = useState(false); + const { t } = useTranslation(); const modalRef = useRef(null); @@ -54,7 +56,7 @@ export const LoggedUser: FC = ({ className, user, avatarSize = AvatarSize

e.stopPropagation()} onClick={signOut}> - Cerrar sesión + {t('common_log_out')}

)} diff --git a/src/components/Navigation/Nav.tsx b/src/components/Navigation/Nav.tsx index b3c9dfb..377dc77 100644 --- a/src/components/Navigation/Nav.tsx +++ b/src/components/Navigation/Nav.tsx @@ -2,6 +2,7 @@ import { useEffect, useState } from 'react'; import { AvatarSize, ButtonSize, cn, ROUTE, useAuth, useBreakpoint, useNavAnimation, Variant } from '@common'; import { BurgerButton, Button, Logo } from '@components'; import { useUserStore } from '@store'; +import { useTranslation } from 'react-i18next'; import { Link, NavLink } from 'react-router-dom'; import { LoggedUser } from './LoggedUser'; @@ -12,6 +13,7 @@ interface NavProps { className?: string; } export const Nav = ({ className }: NavProps) => { + const { t } = useTranslation(); const [isOpen, setIsOpen] = useState(false); const { isMobile } = useBreakpoint(); const { signInWithDiscord } = useAuth(); @@ -86,7 +88,7 @@ export const Nav = ({ className }: NavProps) => {
  • classes.listItem(isActive)} to={ROUTE.home}> - Inicio + {t('common_home')}
  • @@ -95,7 +97,7 @@ export const Nav = ({ className }: NavProps) => {
  • classes.listItem(isActive)}> - Proyectos + {t('common_projects')}
  • @@ -104,7 +106,7 @@ export const Nav = ({ className }: NavProps) => {
  • classes.listItem(isActive)}> - Registro + {t('common_register')}
  • @@ -124,7 +126,7 @@ export const Nav = ({ className }: NavProps) => { hasBorder size={handleButtonSize} > - Accede con Discord + {t('nav_log_in_button')} )} diff --git a/src/config/i18n.ts b/src/config/i18n.ts new file mode 100644 index 0000000..7465bb0 --- /dev/null +++ b/src/config/i18n.ts @@ -0,0 +1,26 @@ +import i18n from 'i18next'; +import LanguageDetector from 'i18next-browser-languagedetector'; +import { initReactI18next } from 'react-i18next'; +import { Language } from '../common/types/Language'; +import translation_en from '../locales/en.json'; +import translation_es from '../locales/es.json'; + +const DETECTION_OPTIONS = { + order: ['navigator'] +}; + +i18n + .use(LanguageDetector) + .use(initReactI18next) + .init({ + detection: DETECTION_OPTIONS, + resources: { + es: { + translation: translation_es + }, + en: { + translation: translation_en + } + }, + fallbackLng: Language.EN + }); diff --git a/src/locales/en.json b/src/locales/en.json new file mode 100644 index 0000000..3502f62 --- /dev/null +++ b/src/locales/en.json @@ -0,0 +1,77 @@ +{ + "common_home": "Home", + "common_projects": "Projects", + "common_register": "Register", + "common_log_out": "Log out", + "common_participants": "Participants", + "common_days": "Days", + "common_hours": "Hours", + "common_minutes": "Minutes", + "common_seconds": "Seconds", + "common_contributor": "Contributor", + "common_platinum": "Platinum", + "common_gold": "Gold", + "common_silver": "Silver", + "common_live": "Live", + "common_share": "Share", + "common_download_ticket": "Download ticket", + "common_contact": "Contact", + "common_front-end": "Front-end", + "common_back-end": "Back-end", + "common_full-stack": "Full-stack", + "common_designer": "Designer", + "common_active": "Active", + "common_closed": "Closed", + "common_admin": "Admin", + "common_any": "Any", + + "page_loading": "Loading...", + + "nav_log_in_button": "Log in with Discord", + "nav_who_has_contributed": "Who has contributed to the development", + + "project_card_look_for_role_section_title": "We are looking for", + "project_card_button_whithout_user": "Log in to contact", + + "footer_social_networks_title": "More information about the event", + "footer_title": "© <0>{{currentYear}} Designed by <1>Ana Rangel Developed by <2>Comuafor", + + "hero_title": "A programming hackathon", + + "information_what_is_this": "The hackafor is a programming event made for developers. You can show your talent by participating alone or in a team and win prizes by creating the apps of the future.", + "information_first_card_title": "Participants per team", + "information_second_card_title": "Thematic", + "information_second_card_description": "To be defined", + "information_third_card_title": "Date", + "information_third_card_description": "20 NOV", + + "home_ticket_title": "Download your ticket and share it on social networks", + + "home_hackathon_register_title": "Do you want to participate in the hackathon? Sign up!", + "home_hackathon_register_button": "Register now", + "home_hackathon_register_without_login_title": "To sign up log in with Discord", + "home_hackathon_register_without_login_button": "Log in with Discord", + + "feature_projects_button": "See all projects", + + "not_found_error_404": "404 error", + "not_found_title": "Page not found", + "not_found_subtitle": "Hi! We're sorry, but we couldn't find what you were looking for. Please check that the URL address is correct", + "not_found_button": "Go to home", + + "projects_not_found_title": "We are sorry...", + "projects_not_found_description": "No project has been found.", + + "registration_first_input_title": "Project title", + "registration_first_input_placeholder": "Enter the title of the project", + "registration_second_input_title": "Project description", + "registration_second_input_placeholder": "Enter a short description of the project", + "registration_third_input_title": "Introduce the participants", + "registration_participant_section_count_member": "Member {{count}}", + "registration_participant_section_admin_name": "Administrator's name", + "registration_participant_section_member_name": "Member's name", + "registration_participant_section_participant_role": "Participant's role", + "registration_first_button": "Add member", + "registration_second_button": "Register project", + "registration_checkbox_text": "I am looking for other people for my project" +} diff --git a/src/locales/es.json b/src/locales/es.json new file mode 100644 index 0000000..78fb845 --- /dev/null +++ b/src/locales/es.json @@ -0,0 +1,77 @@ +{ + "common_home": "Inicio", + "common_projects": "Proyectos", + "common_register": "Registro", + "common_log_out": "Cerrar sesión", + "common_participants": "Participantes", + "common_days": "Días", + "common_hours": "Horas", + "common_minutes": "Minutos", + "common_seconds": "Segundos", + "common_contributor": "Contribuidor", + "common_platinum": "Platino", + "common_gold": "Oro", + "common_silver": "Plata", + "common_live": "Directo", + "common_share": "Compartir", + "common_download_ticket": "Descargar Ticket", + "common_contact": "Contactar", + "common_front-end": "Front-end", + "common_back-end": "Back-end", + "common_full-stack": "Full-stack", + "common_designer": "Diseñador(a)", + "common_active": "Activos", + "common_closed": "Cerrados", + "common_admin": "Administrador", + "common_any": "Cualquiera", + + "page_loading": "Cargando...", + + "nav_log_in_button": "Accede con Discord", + "nav_who_has_contributed": "Quiénes han contribuido en el desarrollo", + + "project_card_look_for_role_section_title": "Estamos buscando", + "project_card_button_whithout_user": "Inicia sesión para contactar", + + "footer_social_networks_title": "Más información del evento", + "footer_title": "© <0>{{currentYear}} Diseñada por <1>Ana Rangel Desarrollada por <2>Comuafor", + + "hero_title": "Una hackaton de programación", + + "information_what_is_this": "La hackafor es un evento de programación hecha para desarrolladores. Puedes demostrar tu talento participado solo/a o en equipo y ganar premios creando las aplicaciones del futuro.", + "information_first_card_title": "Participantes por equipo", + "information_second_card_title": "Temática", + "information_second_card_description": "Por definir", + "information_third_card_title": "Fecha", + "information_third_card_description": "20 NOV", + + "home_ticket_title": "Descarga tu ticket y compártelo en redes sociales", + + "home_hackathon_register_title": "¿Quieres participar en la hackaton? ¡Inscríbete!", + "home_hackathon_register_button": "Inscribirme ahora", + "home_hackathon_register_without_login_title": "Para inscribirte inicia sesión con Discord", + "home_hackathon_register_without_login_button": "Accede con Discord", + + "feature_projects_button": "Ver todos los proyectos", + + "not_found_error_404": "Error 404", + "not_found_title": "Página no encontrada", + "not_found_subtitle": "¡Hola! Lo sentimos, pero no pudimos encontrar lo que buscabas. Verifica que la dirección URL sea correcta", + "not_found_button": "Ir a inicio", + + "projects_not_found_title": "Lo sentimos...", + "projects_not_found_description": "No se ha encontrado ningún proyecto.", + + "registration_first_input_title": "Título del proyecto", + "registration_first_input_placeholder": "Introduce el título del proyecto", + "registration_second_input_title": "Descripción del proyecto", + "registration_second_input_placeholder": "Introduce una breve descripción del proyecto", + "registration_third_input_title": "Introduce a los participantes", + "registration_participant_section_count_member": "Miembro {{count}}", + "registration_participant_section_admin_name": "Nombre del administrador", + "registration_participant_section_member_name": "Nombre del miembro", + "registration_participant_section_participant_role": "Rol del participante", + "registration_first_button": "Añadir miembro", + "registration_second_button": "Registrar proyecto", + "registration_checkbox_text": "Estoy buscando a otras personas para mi proyecto" +} diff --git a/src/pages/NotFound/NotFound.tsx b/src/pages/NotFound/NotFound.tsx index f73ed4c..65622df 100644 --- a/src/pages/NotFound/NotFound.tsx +++ b/src/pages/NotFound/NotFound.tsx @@ -1,23 +1,23 @@ import { ButtonSize, ROUTE, Variant } from '@common'; import { Background, Button } from '@components'; import { RootLayout } from '@layouts'; +import { useTranslation } from 'react-i18next'; import { Link } from 'react-router-dom'; export const NotFound = () => { + const { t } = useTranslation(); return (
    -

    Error 404

    -

    PÁGINA NO ENCONTRADA

    -

    - ¡Hola! Lo sentimos, pero no pudimos encontrar lo que buscabas. Verifica que la dirección URL sea correcta -

    +

    {t('not_found_error_404')}

    +

    {t('not_found_title').toUpperCase()}

    +

    {t('not_found_subtitle')}

    diff --git a/src/pages/Projects/Projects.tsx b/src/pages/Projects/Projects.tsx index 8ceedc0..43287cd 100644 --- a/src/pages/Projects/Projects.tsx +++ b/src/pages/Projects/Projects.tsx @@ -3,6 +3,7 @@ import { ButtonSize, cn, Project, ProjectRoles, ProjectStatus, useAuth, useProje import { Button, ProjectCard, Spinner, ToggleButtonGroup } from '@components'; import { RootLayout } from '@layouts'; import { useUserStore } from '@store'; +import { useTranslation } from 'react-i18next'; import { filterBy } from './utils/filterBy'; import { sendMessage } from './utils/sendMessage'; @@ -14,6 +15,7 @@ export const Projects = () => { const closedProjects = projects?.filter((project) => project.status === ProjectStatus.closed); const { signInWithDiscord } = useAuth(); const user = useUserStore((state) => state.user); + const { t } = useTranslation(); const classes = { tag: (role: ProjectRoles) => @@ -39,13 +41,13 @@ export const Projects = () => { className={`animate-fade-up-custom`} style={{ '--animate-delay': `${animateDelay}s` } as any} isActive={isActive} - buttonTitle={user == null ? 'Log in to contact' : 'Contact'} + buttonTitle={t(user ? 'common_contact' : 'project_card_button_whithout_user')} actionButton={ - user == null - ? signInWithDiscord - : () => { + user + ? () => { sendMessage(user, user); } + : signInWithDiscord } /> ); @@ -54,8 +56,8 @@ export const Projects = () => { const renderNoProjectsFound = () => { return (
    -

    Lo sentimos...

    -

    No se ha encontrado ningún proyecto.

    +

    {t('projects_not_found_title')}

    +

    {t('projects_not_found_description')}

    ); }; @@ -63,7 +65,7 @@ export const Projects = () => { const renderLoading = () => { return (
    -

    Cargando...

    +

    {t('page_loading')}

    ); @@ -111,7 +113,7 @@ export const Projects = () => { toggleFilterRole(ProjectRoles.frontEnd); }} > - Front-end + {t('common_front-end')}
    diff --git a/src/pages/Registration/components/RoleSelector.tsx b/src/pages/Registration/components/RoleSelector.tsx index 9356867..471cb35 100644 --- a/src/pages/Registration/components/RoleSelector.tsx +++ b/src/pages/Registration/components/RoleSelector.tsx @@ -1,5 +1,6 @@ import { cn, FormFieldState, Role, Variant } from '@common'; import { Button } from '@components'; +import { useTranslation } from 'react-i18next'; interface RoleSelectorProps { /** @@ -37,6 +38,7 @@ export const RoleSelector = ({ roles, selectedRole, onRoleChange, fieldState, as 'text-green-600': fieldState === FormFieldState.success }) }; + const { t } = useTranslation(); return (
    @@ -49,7 +51,7 @@ export const RoleSelector = ({ roles, selectedRole, onRoleChange, fieldState, as innerClassName="px-2 py-1" onClick={() => onRoleChange(role)} > - {role} + {t(`common_${role}`)} ))}
    diff --git a/src/pages/Registration/index.tsx b/src/pages/Registration/index.tsx index eeca129..350c775 100644 --- a/src/pages/Registration/index.tsx +++ b/src/pages/Registration/index.tsx @@ -6,6 +6,7 @@ import { RootLayout } from '@layouts'; import { useUserStore } from '@store'; import { apiClient, UpsertProjectSchema, UpsertProjectType } from '@utils'; import { Controller, useFieldArray, useForm } from 'react-hook-form'; +import { useTranslation } from 'react-i18next'; import { RoleSelector } from './components'; import { addWantedRole, removeWantedRole } from './utils'; @@ -47,6 +48,7 @@ export const Registration = ({ project = DEFAULT_PROJECT }: { project?: UpsertPr const [submitError, setSubmitError] = useState(''); const [wantsRoles, setWantsRoles] = useState(false); const [wantedRoles, setWantedRoles] = useState(DEFAULT_PROJECT.required_roles); + const { t } = useTranslation(); const { handleSubmit, @@ -113,7 +115,7 @@ export const Registration = ({ project = DEFAULT_PROJECT }: { project?: UpsertPr name={`members.${index}.name`} render={({ field: { name, value, onChange }, fieldState: { error } }) => (
    -

    Rol del participante*

    +

    {`${t('registration_participant_section_participant_role')}*`}

    ( onChange(role)} amount={amount} disabled={isRegistrationFull} @@ -182,11 +184,11 @@ export const Registration = ({ project = DEFAULT_PROJECT }: { project?: UpsertPr control={control} render={({ field: { name, value, onChange } }) => ( @@ -198,11 +200,11 @@ export const Registration = ({ project = DEFAULT_PROJECT }: { project?: UpsertPr control={control} render={({ field: { name, value, onChange } }) => (