diff --git a/packages/lib-user/src/components/MyGroups/MyGroups.js b/packages/lib-user/src/components/MyGroups/MyGroups.js index f4500b1349..906292779a 100644 --- a/packages/lib-user/src/components/MyGroups/MyGroups.js +++ b/packages/lib-user/src/components/MyGroups/MyGroups.js @@ -2,6 +2,7 @@ import { Loader, SpacedText } from '@zooniverse/react-components' import { Box, Grid, Paragraph } from 'grommet' import { arrayOf, bool, shape, string } from 'prop-types' import styled from 'styled-components' +import { useTranslation, Trans } from '../../translations/i18n.js' import GroupCardList from './components/GroupCardList' @@ -20,6 +21,7 @@ function MyGroups({ groups = [], loading = false }) { + const { t } = useTranslation() return ( <> {loading ? ( @@ -39,7 +41,7 @@ function MyGroups({ pad='medium' > - There was an error. + {t('MyGroups.error')} {error?.message} @@ -52,11 +54,8 @@ function MyGroups({ justify='center' pad='medium' > - - You are not a member of any Groups. - - - Create one below + + ]} /> ) : ( diff --git a/packages/lib-user/src/components/MyGroups/MyGroups.stories.js b/packages/lib-user/src/components/MyGroups/MyGroups.stories.js index 6293792675..06ac52621d 100644 --- a/packages/lib-user/src/components/MyGroups/MyGroups.stories.js +++ b/packages/lib-user/src/components/MyGroups/MyGroups.stories.js @@ -43,3 +43,8 @@ export const Default = { loading: false } } + +export const Empty = { + groups: [], + loading: false +} diff --git a/packages/lib-user/src/components/MyGroups/MyGroupsContainer.js b/packages/lib-user/src/components/MyGroups/MyGroupsContainer.js index 21fb1aaef8..5fd1a23722 100644 --- a/packages/lib-user/src/components/MyGroups/MyGroupsContainer.js +++ b/packages/lib-user/src/components/MyGroups/MyGroupsContainer.js @@ -4,6 +4,7 @@ import { SpacedText } from '@zooniverse/react-components' import { Anchor, Box } from 'grommet' import { bool, shape, string } from 'prop-types' import { useState } from 'react' +import { useTranslation } from '../../translations/i18n.js' import { usePanoptesMemberships, @@ -26,6 +27,7 @@ import GroupCreateFormContainer from './components/GroupCreateFormContainer' import PreviewLayout from './components/PreviewLayout' function MyGroupsContainer({ authUser, login, previewLayout = false }) { + const { t } = useTranslation() const [groupModalActive, setGroupModalActive] = useState(false) const [page, setPage] = useState(1) @@ -67,7 +69,7 @@ function MyGroupsContainer({ authUser, login, previewLayout = false }) { @@ -77,13 +79,13 @@ function MyGroupsContainer({ authUser, login, previewLayout = false }) { primaryHeaderItem={ } > - Learn more about groups + {t('MyGroups.learnMore')} } /> diff --git a/packages/lib-user/src/components/MyGroups/components/CreateButton/CreateButton.js b/packages/lib-user/src/components/MyGroups/components/CreateButton/CreateButton.js index 0ee48eda17..e58778f87d 100644 --- a/packages/lib-user/src/components/MyGroups/components/CreateButton/CreateButton.js +++ b/packages/lib-user/src/components/MyGroups/components/CreateButton/CreateButton.js @@ -2,6 +2,7 @@ import { PlainButton } from '@zooniverse/react-components' import { Add } from 'grommet-icons' import { func, string } from 'prop-types' import styled from 'styled-components' +import { useTranslation } from '../../../../translations/i18n.js' const StyledButton = styled(PlainButton)` width: fit-content; @@ -13,8 +14,11 @@ const StyledButton = styled(PlainButton)` function CreateButton({ onClick, - text = 'create new group' + text = '' }) { + const { t } = useTranslation() + const labelText = text.length ? text : t('MyGroups.createNew') + return ( } labelSize='1rem' onClick={onClick} - text={text} + text={labelText} /> ) } diff --git a/packages/lib-user/src/components/MyGroups/components/GroupCard/GroupCard.js b/packages/lib-user/src/components/MyGroups/components/GroupCard/GroupCard.js index 33f4180c24..9cb1128ae4 100644 --- a/packages/lib-user/src/components/MyGroups/components/GroupCard/GroupCard.js +++ b/packages/lib-user/src/components/MyGroups/components/GroupCard/GroupCard.js @@ -3,6 +3,7 @@ import { Box } from 'grommet' import Link from 'next/link' import { number, string } from 'prop-types' import styled, { css } from 'styled-components' +import { useTranslation } from '../../../../translations/i18n.js' import { TitledStat } from '@components/shared' @@ -10,7 +11,7 @@ const StyledListItem = styled.li` border-radius: 8px; list-style: none; width: clamp(385px, 100%, 564px); - + &:hover, &:focus-within { box-shadow: 1px 2px 6px 0px rgba(0, 0, 0, 0.25); } @@ -51,6 +52,7 @@ function GroupCard({ projects = 0, role = '' }) { + const { t } = useTranslation() return ( - {role === 'group_admin' ? 'Admin' : 'Member'} + {role === 'group_admin' ? t('MyGroups.GroupCard.admin') : t('MyGroups.GroupCard.member')} diff --git a/packages/lib-user/src/components/MyGroups/components/PreviewLayout/PreviewLayout.js b/packages/lib-user/src/components/MyGroups/components/PreviewLayout/PreviewLayout.js index 13521439ee..4010a1349e 100644 --- a/packages/lib-user/src/components/MyGroups/components/PreviewLayout/PreviewLayout.js +++ b/packages/lib-user/src/components/MyGroups/components/PreviewLayout/PreviewLayout.js @@ -2,6 +2,7 @@ import { Loader, SpacedText } from '@zooniverse/react-components' import { Anchor, Box, Paragraph } from 'grommet' import { arrayOf, bool, func, shape, string } from 'prop-types' import Link from 'next/link' +import { useTranslation, Trans } from '../../../../translations/i18n.js' import { ContentBox } from '@components/shared' import GroupCardContainer from '../GroupCard/GroupCardContainer.js' @@ -15,11 +16,12 @@ export default function PreviewLayout({ loading = false, handleGroupModal = DEFAULT_HANDLER }) { + const { t } = useTranslation() return ( {loading && ( @@ -45,11 +47,8 @@ export default function PreviewLayout({ ) : ( - - You are not a member of any Groups. - - - Create one below + + ]} /> )} @@ -62,7 +61,7 @@ export default function PreviewLayout({ }} label={ - Learn more about groups + {t('MyGroups.learnMore')} } /> diff --git a/packages/lib-user/src/components/UserHome/components/Dashboard/Dashboard.js b/packages/lib-user/src/components/UserHome/components/Dashboard/Dashboard.js index 9193675331..378b56e09f 100644 --- a/packages/lib-user/src/components/UserHome/components/Dashboard/Dashboard.js +++ b/packages/lib-user/src/components/UserHome/components/Dashboard/Dashboard.js @@ -11,6 +11,7 @@ import { useContext } from 'react' import styled, { css, useTheme } from 'styled-components' import { bool, shape, string } from 'prop-types' import { SpacedHeading, SpacedText } from '@zooniverse/react-components' +import { useTranslation } from '../../../../translations/i18n.js' import DashboardLink from './components/DashboardLink.js' import StatsTabsContainer from './components/StatsTabs/StatsTabsContainer.js' @@ -139,9 +140,11 @@ const border = { } export default function Dashboard({ user, userLoading }) { + const { t } = useTranslation() const size = useContext(ResponsiveContext) const { dark } = useTheme() + // No translations, this link is going away const blogLinkLabel = size === 'small' ? 'About your homepage' @@ -183,7 +186,7 @@ export default function Dashboard({ user, userLoading }) { }} > User avatar } - text='Favorites' + text={t('UserHome.Dashboard.favorites')} href={`https://www.zooniverse.org/favorites/${user?.login}`} /> } - text='Collections' + text={t('UserHome.Dashboard.collections')} href={`https://www.zooniverse.org/collections/${user?.login}`} /> } - text='Comments' + text={t('UserHome.Dashboard.comments')} href={`https://www.zooniverse.org/users/${user?.login}`} /> } - text='Messages' + text={t('UserHome.Dashboard.messages')} href={`https://www.zooniverse.org/inbox`} /> @@ -256,13 +259,14 @@ export default function Dashboard({ user, userLoading }) { alignSelf={size === 'small' ? 'center' : 'end'} forwardedAs={Link} href={`/users/${user?.login}/stats`} - label={More Stats} + label={{t('UserHome.Dashboard.moreStats')}} icon={} reverse color={{ light: 'dark-5', dark: 'white' }} gap='large' /> + {/* No translation, this is going away */} NEW diff --git a/packages/lib-user/src/components/UserHome/components/Dashboard/components/StatsTabs/StatsTabs.js b/packages/lib-user/src/components/UserHome/components/Dashboard/components/StatsTabs/StatsTabs.js index 276b730019..147426fa07 100644 --- a/packages/lib-user/src/components/UserHome/components/Dashboard/components/StatsTabs/StatsTabs.js +++ b/packages/lib-user/src/components/UserHome/components/Dashboard/components/StatsTabs/StatsTabs.js @@ -9,6 +9,7 @@ import { import { number, shape } from 'prop-types' import { useContext } from 'react' import styled from 'styled-components' +import { useTranslation } from '../../../../../../translations/i18n.js' import { Tip } from '@components/shared' @@ -19,6 +20,7 @@ const StyledTab = styled(Tab)` ` function Stat({ stats }) { + const { t } = useTranslation() return ( @@ -28,7 +30,7 @@ function Stat({ stats }) { color={{ dark: 'white', light: 'black' }} weight='bold' > - Classifications + {t('common.classifications')} @@ -42,7 +44,7 @@ function Stat({ stats }) { color={{ dark: 'white', light: 'black' }} weight='bold' > - Projects + {t('common.projects')} {stats.projects?.toLocaleString()} @@ -53,16 +55,17 @@ function Stat({ stats }) { } export default function StatsTabs({ statsPreview }) { + const { t } = useTranslation() const size = useContext(ResponsiveContext) return ( - + {statsPreview?.thisWeek && } - + {statsPreview?.allTime && } diff --git a/packages/lib-user/src/components/UserHome/components/RecentProjects/RecentProjects.js b/packages/lib-user/src/components/UserHome/components/RecentProjects/RecentProjects.js index c5d0de8ec1..854fe3a548 100644 --- a/packages/lib-user/src/components/UserHome/components/RecentProjects/RecentProjects.js +++ b/packages/lib-user/src/components/UserHome/components/RecentProjects/RecentProjects.js @@ -2,6 +2,7 @@ import { Anchor, Box, ResponsiveContext, Text } from 'grommet' import { arrayOf, bool, shape, string } from 'prop-types' import { useContext } from 'react' import { Loader, ProjectCard, SpacedText } from '@zooniverse/react-components' +import { useTranslation, Trans } from '../../../../translations/i18n.js' import { ContentBox } from '@components/shared' @@ -10,10 +11,11 @@ export default function RecentProjects({ projectPreferences = [], error = undefined }) { + const { t } = useTranslation() const size = useContext(ResponsiveContext) return ( - + {isLoading && ( @@ -21,20 +23,22 @@ export default function RecentProjects({ )} {!isLoading && error && ( - - There was an error fetching your recent projects - + {t('UserHome.RecentProjects.error')} )} {!isLoading && !projectPreferences.length && !error && ( - No Recent Projects found + {t('UserHome.RecentProjects.noProjects')} - Start by{' '} - - classifying any project - - . + + ]} + /> )} diff --git a/packages/lib-user/src/components/UserHome/components/RecentSubjects/RecentSubjects.js b/packages/lib-user/src/components/UserHome/components/RecentSubjects/RecentSubjects.js index 0b53859d4d..e3d34ac06c 100644 --- a/packages/lib-user/src/components/UserHome/components/RecentSubjects/RecentSubjects.js +++ b/packages/lib-user/src/components/UserHome/components/RecentSubjects/RecentSubjects.js @@ -2,6 +2,7 @@ import { Anchor, Box, ResponsiveContext, Text } from 'grommet' import { arrayOf, bool, shape, string } from 'prop-types' import { useContext } from 'react' import { Loader, SpacedText } from '@zooniverse/react-components' +import { useTranslation, Trans } from '../../../../translations/i18n.js' import { ContentBox } from '@components/shared' import SubjectCard from '../SubjectCard/SubjectCard.js' @@ -11,6 +12,7 @@ function RecentSubjects({ recents = [], error = undefined }) { + const { t } = useTranslation() const size = useContext(ResponsiveContext) return ( @@ -23,19 +25,23 @@ function RecentSubjects({ {!isLoading && error && ( - There was an error fetching recent classifications + {t('UserHome.RecentSubjects.error')} )} {!isLoading && !recents?.length && !error && ( - No Recent Classifications found + {t('UserHome.RecentSubjects.noSubjects')} - Start by{' '} - - classifying any project - {' '} - to show your recent classifications here. + + ]} + /> )} diff --git a/packages/lib-user/src/components/UserHome/components/SubjectCard/SubjectCard.js b/packages/lib-user/src/components/UserHome/components/SubjectCard/SubjectCard.js index 90fbc41ffd..06d833dfd5 100644 --- a/packages/lib-user/src/components/UserHome/components/SubjectCard/SubjectCard.js +++ b/packages/lib-user/src/components/UserHome/components/SubjectCard/SubjectCard.js @@ -10,9 +10,10 @@ /* The shape and styling of this component is similar to ProjectCard in lib-react-components */ import styled from 'styled-components' -import { Anchor, Box } from 'grommet' +import { Box } from 'grommet' import { Media, SpacedText } from '@zooniverse/react-components' import { string } from 'prop-types' +import { useTranslation } from '../../../../translations/i18n.js' const StyledBox = styled(Box)` overflow: hidden; @@ -62,6 +63,7 @@ export default function SubjectCard({ projectSlug = '', subjectID }) { + const { t } = useTranslation() // to PFE const href = `https://www.zooniverse.org/projects/${projectSlug}/talk/subjects/${subjectID}` @@ -75,14 +77,14 @@ export default function SubjectCard({ round='8px' > - {'Subject ' + subjectID} + {t('UserHome.SubjectCard.subject')} {subjectID} ) diff --git a/packages/lib-user/src/components/UserStats/UserStats.js b/packages/lib-user/src/components/UserStats/UserStats.js index e833ebdaee..e711f7b742 100644 --- a/packages/lib-user/src/components/UserStats/UserStats.js +++ b/packages/lib-user/src/components/UserStats/UserStats.js @@ -1,4 +1,5 @@ import { arrayOf, bool, func, number, shape, string } from 'prop-types' +import { useTranslation } from '../../translations/i18n.js' import { HeaderLink, @@ -39,6 +40,8 @@ function UserStats({ setSelectedProject = DEFAULT_HANDLER, user = DEFAULT_USER }) { + const { t } = useTranslation() + // set stats based on selected project const stats = selectedProject ? projectStats : allProjectsStats const totalProjects = allProjectsStats?.project_contributions?.length @@ -48,7 +51,7 @@ function UserStats({ primaryHeaderItem={ } diff --git a/packages/lib-user/src/components/shared/BarChart/BarChart.js b/packages/lib-user/src/components/shared/BarChart/BarChart.js index 3dc6a8bcce..5198fe6645 100644 --- a/packages/lib-user/src/components/shared/BarChart/BarChart.js +++ b/packages/lib-user/src/components/shared/BarChart/BarChart.js @@ -2,6 +2,7 @@ import { DataChart, ResponsiveContext, Text } from 'grommet' import { arrayOf, func, number, shape, string } from 'prop-types' import { useContext } from 'react' import styled from 'styled-components' +import { useTranslation } from '../../../translations/i18n.js' import { getDateInterval as defaultGetDateInterval @@ -10,11 +11,6 @@ import { import { getCompleteData as defaultGetCompleteData } from './helpers/getCompleteData' import getDateRangeLabel from './helpers/getDateRangeLabel' -const TYPE_LABEL = { - count: 'Classifications', - session_time: 'Time' -} - const X_AXIS_FREQUENCY = { everyOther: 'everyOther', everyFourth: 'everyFourth' @@ -51,8 +47,14 @@ function BarChart({ getDateInterval = defaultGetDateInterval, type = 'count' }) { + const { t } = useTranslation() const size = useContext(ResponsiveContext) + const TYPE_LABEL = { + count: t('common.classifications'), + session_time: t('BarChart.time') + } + // getDateInterval returns an object with a period property based on the date range, start_date, and end_date const dateInterval = getDateInterval(dateRange) @@ -60,7 +62,7 @@ function BarChart({ // including any periods without stats with a count and session_time of 0 const completeData = getCompleteData({ data, dateInterval }) - const dateRangeLabel = getDateRangeLabel(dateInterval) + const dateRangeLabel = getDateRangeLabel(dateInterval, t) const typeLabel = TYPE_LABEL[type] // with no data set gradient as 'brand' @@ -106,7 +108,7 @@ function BarChart({ return ( key + +export default function getDateRangeLabel(dateInterval, t = DEFAULT_HANDLER) { const { end_date, start_date } = dateInterval const differenceInDays = (new Date(end_date) - new Date(start_date)) / (1000 * 60 * 60 * 24) const sameMonth = new Date(end_date).getUTCMonth() === new Date(start_date).getUTCMonth() @@ -6,58 +8,58 @@ export default function getDateRangeLabel(dateInterval) { if (differenceInDays <= 7) { return { - countLabel: 'Day', + countLabel: t('BarChart.day'), time: 60, - timeLabel: 'min', + timeLabel: t('BarChart.minAbbrev'), tLDS: { timeZone: 'UTC', weekday: 'short' } } } else if (differenceInDays <= 30 && !sameMonth) { - return { - countLabel: 'Day', + return { + countLabel: t('BarChart.day'), time: 60, - timeLabel: 'min', + timeLabel: t('BarChart.minAbbrev'), tLDS: { timeZone: 'UTC', month: 'numeric', day: 'numeric' } } } else if (differenceInDays <= 31 && sameMonth) { - return { - countLabel: 'Day', + return { + countLabel: t('BarChart.day'), time: 60, - timeLabel: 'min', + timeLabel: t('BarChart.minAbbrev'), tLDS: { timeZone: 'UTC', day: 'numeric' } } } else if (differenceInDays <= 183) { return { - countLabel: 'Week of', + countLabel: t('BarChart.weekOf'), time: 3600, - timeLabel: 'hrs', + timeLabel: t('BarChart.hourAbbrev'), tLDS: { timeZone: 'UTC', month: 'numeric', day: 'numeric' } } } else if (differenceInDays <= 365 && sameYear) { return { - countLabel: 'Month of', + countLabel: t('BarChart.monthOf'), time: 3600, - timeLabel: 'hrs', + timeLabel: t('BarChart.hourAbbrev'), tLDS: { timeZone: 'UTC', month: 'short' } } } else if (differenceInDays <= 365) { return { - countLabel: 'Month of', + countLabel: t('BarChart.monthOf'), time: 3600, - timeLabel: 'hrs', + timeLabel: t('BarChart.hourAbbrev'), tLDS: { timeZone: 'UTC', month: 'short', year: 'numeric' } } } else if (differenceInDays <= 1460) { return { - countLabel: 'Month of', + countLabel: t('BarChart.monthOf'), time: 3600, - timeLabel: 'hrs', + timeLabel: t('BarChart.hourAbbrev'), tLDS: { timeZone: 'UTC', month: 'short', year: 'numeric' } } } else { return { - countLabel: 'Year', + countLabel: t('BarChart.year'), time: 3600, - timeLabel: 'hrs', + timeLabel: t('BarChart.hourAbbrev'), tLDS: { timeZone: 'UTC', year: 'numeric' } } } diff --git a/packages/lib-user/src/components/shared/BarChart/helpers/getDateRangeLabel.spec.js b/packages/lib-user/src/components/shared/BarChart/helpers/getDateRangeLabel.spec.js index 36b7e87558..23074502af 100644 --- a/packages/lib-user/src/components/shared/BarChart/helpers/getDateRangeLabel.spec.js +++ b/packages/lib-user/src/components/shared/BarChart/helpers/getDateRangeLabel.spec.js @@ -7,11 +7,11 @@ describe('components > shared > BarChart > getDateRangeLabel', function () { start_date: '2021-09-01' }) expect(dateRangeLabel).to.deep.equal({ - countLabel: 'Day', + countLabel: 'BarChart.day', time: 60, - timeLabel: 'min', + timeLabel: 'BarChart.minAbbrev', tLDS: { timeZone: 'UTC', weekday: 'short' } - }) + }) }) it('should return the expected dateRangeLabel for LAST 30 DAYS', function () { @@ -20,9 +20,9 @@ describe('components > shared > BarChart > getDateRangeLabel', function () { start_date: '2021-08-16' }) expect(dateRangeLabel).to.deep.equal({ - countLabel: 'Day', + countLabel: 'BarChart.day', time: 60, - timeLabel: 'min', + timeLabel: 'BarChart.minAbbrev', tLDS: { timeZone: 'UTC', month: 'numeric', day: 'numeric' } }) }) @@ -33,9 +33,9 @@ describe('components > shared > BarChart > getDateRangeLabel', function () { start_date: '2021-09-01' }) expect(dateRangeLabel).to.deep.equal({ - countLabel: 'Day', + countLabel: 'BarChart.day', time: 60, - timeLabel: 'min', + timeLabel: 'BarChart.minAbbrev', tLDS: { timeZone: 'UTC', day: 'numeric' } }) }) @@ -46,9 +46,9 @@ describe('components > shared > BarChart > getDateRangeLabel', function () { start_date: '2021-07-01' }) expect(dateRangeLabel).to.deep.equal({ - countLabel: 'Week of', + countLabel: 'BarChart.weekOf', time: 3600, - timeLabel: 'hrs', + timeLabel: 'BarChart.hourAbbrev', tLDS: { timeZone: 'UTC', month: 'numeric', day: 'numeric' } }) }) @@ -59,9 +59,9 @@ describe('components > shared > BarChart > getDateRangeLabel', function () { start_date: '2021-01-01' }) expect(dateRangeLabel).to.deep.equal({ - countLabel: 'Week of', + countLabel: 'BarChart.weekOf', time: 3600, - timeLabel: 'hrs', + timeLabel: 'BarChart.hourAbbrev', tLDS: { timeZone: 'UTC', month: 'numeric', day: 'numeric' } }) }) @@ -72,9 +72,9 @@ describe('components > shared > BarChart > getDateRangeLabel', function () { start_date: '2021-03-01' }) expect(dateRangeLabel).to.deep.equal({ - countLabel: 'Month of', + countLabel: 'BarChart.monthOf', time: 3600, - timeLabel: 'hrs', + timeLabel: 'BarChart.hourAbbrev', tLDS: { timeZone: 'UTC', month: 'short' } }) }) @@ -85,9 +85,9 @@ describe('components > shared > BarChart > getDateRangeLabel', function () { start_date: '2020-10-01' }) expect(dateRangeLabel).to.deep.equal({ - countLabel: 'Month of', + countLabel: 'BarChart.monthOf', time: 3600, - timeLabel: 'hrs', + timeLabel: 'BarChart.hourAbbrev', tLDS: { timeZone: 'UTC', month: 'short', year: 'numeric' } }) }) @@ -98,9 +98,9 @@ describe('components > shared > BarChart > getDateRangeLabel', function () { start_date: '2018-01-01' }) expect(dateRangeLabel).to.deep.equal({ - countLabel: 'Year', + countLabel: 'BarChart.year', time: 3600, - timeLabel: 'hrs', + timeLabel: 'BarChart.hourAbbrev', tLDS: { timeZone: 'UTC', year: 'numeric' } }) }) diff --git a/packages/lib-user/src/components/shared/GroupContainer/GroupContainer.js b/packages/lib-user/src/components/shared/GroupContainer/GroupContainer.js index af5008cbd0..830d69ecc3 100644 --- a/packages/lib-user/src/components/shared/GroupContainer/GroupContainer.js +++ b/packages/lib-user/src/components/shared/GroupContainer/GroupContainer.js @@ -4,6 +4,7 @@ import { Notification } from 'grommet' import { bool, node, shape, string } from 'prop-types' import { Children, cloneElement, useEffect, useState } from 'react' import useSWRMutation from 'swr/mutation' +import { useTranslation } from '../../../translations/i18n.js' import { ContentBox, @@ -38,6 +39,7 @@ function GroupContainer({ groupId, joinToken }) { + const { t } = useTranslation() const [showJoinNotification, setShowJoinNotification] = useState(false) // define user_group membership key @@ -82,7 +84,7 @@ function GroupContainer({ groupId, membershipId: activeMembership?.id }) - + useEffect(function handleJoinGroup() { async function createMembership() { try { @@ -97,7 +99,7 @@ function GroupContainer({ } if ( - authUser?.id && + authUser?.id && joinToken && !activeMembershipRole && !membershipError && @@ -127,23 +129,24 @@ function GroupContainer({ } }, [joinToken, activeMembershipRole]) - const status = getUserGroupStatus({ + const status = getUserGroupStatus({ authUserId: authUser?.id, createGroupMembershipError, createGroupMembershipLoading, group, groupError, groupLoading, - joinToken + joinToken, + t }) const activeMembershipDeactivatedGroup = activeMembershipRole && groupError?.status === 404 - + return ( <> {showJoinNotification && ( setShowJoinNotification(false)} status='normal' time={4000} @@ -177,7 +180,7 @@ function GroupContainer({ ) : ( - Children.map(children, child => + Children.map(children, child => cloneElement( child, { diff --git a/packages/lib-user/src/components/shared/GroupContainer/components/DeactivatedGroup.js b/packages/lib-user/src/components/shared/GroupContainer/components/DeactivatedGroup.js index 71dfe705d3..4f955c3176 100644 --- a/packages/lib-user/src/components/shared/GroupContainer/components/DeactivatedGroup.js +++ b/packages/lib-user/src/components/shared/GroupContainer/components/DeactivatedGroup.js @@ -2,6 +2,7 @@ import { Loader, SpacedText } from '@zooniverse/react-components' import { Box } from 'grommet' import { SubtractCircle } from 'grommet-icons' import { bool, func, string } from 'prop-types' +import { useTranslation } from '../../../../translations/i18n.js' import { HeaderButton } from '@components/shared' @@ -12,13 +13,14 @@ function DeactivatedGroup({ deleteMembershipLoading = false, membershipId }) { + const { t } = useTranslation() async function handleGroupMembershipLeave ({ membershipId }) { await deleteMembership({ membershipId }, { revalidate: true }) - + window.location.href = '/' } @@ -29,8 +31,7 @@ function DeactivatedGroup({ gap='medium' > - This is a deactivated group. - Please leave the group or contact the group administrator if you believe this is an error. + {t('GroupContainer.deactivated')} {deleteMembershipLoading ? ( @@ -38,7 +39,7 @@ function DeactivatedGroup({ } - label='Leave Group' + label={t('GroupContainer.leaveGroup')} onClick={() => handleGroupMembershipLeave({ membershipId, })} diff --git a/packages/lib-user/src/components/shared/GroupContainer/helpers/getUserGroupStatus.js b/packages/lib-user/src/components/shared/GroupContainer/helpers/getUserGroupStatus.js index 828ca3245e..fdbc2e800a 100644 --- a/packages/lib-user/src/components/shared/GroupContainer/helpers/getUserGroupStatus.js +++ b/packages/lib-user/src/components/shared/GroupContainer/helpers/getUserGroupStatus.js @@ -1,6 +1,8 @@ import { Loader } from '@zooniverse/react-components' import { bool, shape, string } from 'prop-types' +const DEFAULT_HANDLER = (key) => key + export function getUserGroupStatus({ authUserId = undefined, createGroupMembershipError = null, @@ -8,18 +10,19 @@ export function getUserGroupStatus({ group = null, groupError = null, groupLoading = false, - joinToken = null + joinToken = null, + t = DEFAULT_HANDLER }) { if (joinToken && !authUserId) { - return ('Log in to join the group.') + return t('GroupContainer.loginToJoin') } if (createGroupMembershipLoading) { - return ('Joining group...') + return t('GroupContainer.joining') } if (createGroupMembershipError) { - return ('Join failed.') + return t('GroupContainer.joinFail') } if (groupLoading) { @@ -27,15 +30,15 @@ export function getUserGroupStatus({ } if (groupError) { - return (`Error: ${groupError.message}.`) + return groupError.message } if (!group && !authUserId) { - return ('Group not found. You must be logged in to access a private group.') + return t('GroupContainer.noAuth') } if (!group && authUserId) { - return ('Group not found.') + return t('GroupContainer.notFound') } return null diff --git a/packages/lib-user/src/components/shared/GroupContainer/helpers/getUserGroupStatus.spec.js b/packages/lib-user/src/components/shared/GroupContainer/helpers/getUserGroupStatus.spec.js index 0d7e2b445c..811bf420ba 100644 --- a/packages/lib-user/src/components/shared/GroupContainer/helpers/getUserGroupStatus.spec.js +++ b/packages/lib-user/src/components/shared/GroupContainer/helpers/getUserGroupStatus.spec.js @@ -5,17 +5,17 @@ import { getUserGroupStatus } from './getUserGroupStatus' describe('components > shared > GroupContainer > getUserGroupStatus', function () { it('should return a message to log in if there is a join token and no auth user', function () { const result = getUserGroupStatus({ joinToken: 'token' }) - expect(result).to.equal('Log in to join the group.') + expect(result).to.equal('GroupContainer.loginToJoin') }) it('should return a message when joining a group', function () { const result = getUserGroupStatus({ createGroupMembershipLoading: true }) - expect(result).to.equal('Joining group...') + expect(result).to.equal('GroupContainer.joining') }) it('should return a message when joining a group fails', function () { const result = getUserGroupStatus({ createGroupMembershipError: { message: 'error message' } }) - expect(result).to.equal('Join failed.') + expect(result).to.equal('GroupContainer.joinFail') }) it('should return a message when loading the group', function () { @@ -27,17 +27,17 @@ describe('components > shared > GroupContainer > getUserGroupStatus', function ( it('should return a message when there is a group error', function () { const result = getUserGroupStatus({ groupError: { message: 'error message' } }) - expect(result).to.equal('Error: error message.') + expect(result).to.equal('error message') }) it('should return a message when there is no group and no auth user', function () { const result = getUserGroupStatus({}) - expect(result).to.equal('Group not found. You must be logged in to access a private group.') + expect(result).to.equal('GroupContainer.noAuth') }) it('should return a message when there is no group and there is an auth user', function () { const result = getUserGroupStatus({ authUserId: '1' }) - expect(result).to.equal('Group not found.') + expect(result).to.equal('GroupContainer.notFound') }) it('should return null when there is a group and an auth user', function () { diff --git a/packages/lib-user/src/components/shared/GroupForm/GroupForm.js b/packages/lib-user/src/components/shared/GroupForm/GroupForm.js index 0f82a460c9..2ba79c1695 100644 --- a/packages/lib-user/src/components/shared/GroupForm/GroupForm.js +++ b/packages/lib-user/src/components/shared/GroupForm/GroupForm.js @@ -2,6 +2,7 @@ import { Box, Button, Form, FormField, RadioButtonGroup, Select, TextInput, Them import { func, node, shape, string } from 'prop-types' import { useEffect, useState } from 'react' import styled from 'styled-components' +import { useTranslation } from '../../../translations/i18n.js' import FieldLabel from './components/FieldLabel' import RadioInputLabel from './components/RadioInputLabel' @@ -11,32 +12,6 @@ const StyledButton = styled(Button)` border-radius: 4px; ` -const PRIVATE_STATS_VISIBILITY = [ - { - label: `No, don't show individual stats to members`, - value: 'private_agg_only', - }, - { - label: 'Yes, show individual stats to members', - value: 'private_show_agg_and_ind', - } -] - -const PUBLIC_STATS_VISIBILITY = [ - { - label: 'No, never show individual stats', - value: 'public_agg_only', - }, - { - label: 'Yes, show individual stats if member', - value: 'public_agg_show_ind_if_member', - }, - { - label: 'Yes, always show individual stats', - value: 'public_show_all', - } -] - const DEFAULT_HANDLER = () => true const DEFAULT_VALUE = { @@ -51,6 +26,34 @@ function GroupForm({ handleDelete = DEFAULT_HANDLER, handleSubmit = DEFAULT_HANDLER }) { + const { t } = useTranslation() + + const PRIVATE_STATS_VISIBILITY = [ + { + label: t('GroupForm.privateAggOnly'), + value: 'private_agg_only', + }, + { + label: t('GroupForm.privateShowAggAndInd'), + value: 'private_show_agg_and_ind', + } + ] + + const PUBLIC_STATS_VISIBILITY = [ + { + label: t('GroupForm.publicAggOnly'), + value: 'public_agg_only', + }, + { + label: t('GroupForm.publicAggShowInd'), + value: 'public_agg_show_ind_if_member', + }, + { + label: t('GroupForm.publicShowAll'), + value: 'public_show_all', + } + ] + const [value, setValue] = useState(defaultValue) const statsVisibilityOptions = value.visibility === 'Private' ? PRIVATE_STATS_VISIBILITY : PUBLIC_STATS_VISIBILITY @@ -80,15 +83,15 @@ function GroupForm({ } }}> Group Name} - help={defaultValue?.id ? null : 'By creating a new Group you will become the admin.'} + label={{t('GroupForm.name')}} + help={defaultValue?.id ? null : t('GroupForm.nameHelp')} htmlFor='display_name' name='display_name' required validate={[ (name) => { - if (name && name.length < 4) return 'must be > 3 characters' - if (name && name.length > 60) return 'must be < 60 characters' + if (name && name.length < 4) return t('GroupForm.greaterThanChar') + if (name && name.length > 60) return t('GroupForm.lessThanChar') return undefined } ]} @@ -102,7 +105,7 @@ function GroupForm({ Public Visibility} + label={{t('GroupForm.pubVis')}} htmlFor='visibility' name='visibility' > @@ -111,17 +114,17 @@ function GroupForm({ options={[ { label: <> - Private + {t('GroupForm.private')} {' - '} - only members can view this group + {t('GroupForm.onlyMembers')} , value: 'Private' }, { label: <> - Public + {t('GroupForm.public')} {' - '} - you can share this group with anyone + {t('GroupForm.anyone')} , value: 'Public' } @@ -139,15 +142,15 @@ function GroupForm({ } }}> Show Individual Stats} - help='Admin can always see individual stats.' + label={{t('GroupForm.showInd')}} + help={t('GroupForm.showIndHelp')} htmlFor='stats_visibility' - info={defaultValue.id ? null : 'You can add all other members via a Join Link after creating the new group below.'} + info={defaultValue.id ? null : t('GroupForm.showIndInfo')} name='stats_visibility' >