diff --git a/public/locales/en/common.json b/public/locales/en/common.json index c1b378d..d8dff3b 100644 --- a/public/locales/en/common.json +++ b/public/locales/en/common.json @@ -115,5 +115,37 @@ "subtitle": "We've filled in an issue with your notes and version details. Nothing has been sent yet — review it and post when you're ready.", "open": "Open the issue" } + }, + "done": "Done", + "clearAll": "Clear all", + "upload": { + "quotaTitle": "Storage Quota Exceeded", + "quotaMessage": "You have exceeded your quota for uploading files.", + "tooLargeTitle": "File Too Large", + "tooLargeMessage": "The file you are trying to upload is too large.", + "deniedTitle": "Permission Denied", + "deniedMessage": "You do not have permission to upload files." + }, + "getStarted": "Get Started!", + "imageLoadFailed": "Failed to load image.", + "typeHere": "Type in here…", + "summaryOfChores": "This is a summary of your chores", + "loadingOffline": "You are offline", + "loadingOfflineSub": "This not available while offline. Please check your internet connection and try again.", + "loadingSlow": "This is taking longer than usual. There might be an issue.", + "navigateBack": "Navigate Back", + "bottomNav": "Bottom Navigation", + "profile": "Profile", + "autocompletePlaceholder": "Type here...", + "errorScreen": { + "title": "Something went wrong", + "fallback": "An unexpected error occurred. Try reloading — it usually fixes it.", + "tryAgain": "Try again", + "home": "Home", + "login": "Login", + "hideDetails": "Hide error details", + "showDetails": "Show error details", + "copyToClipboard": "Copy to clipboard", + "copied": "Error details copied to clipboard" } } diff --git a/src/components/animations/LoadingScreen.jsx b/src/components/animations/LoadingScreen.jsx index 6ee18c9..743e7e2 100644 --- a/src/components/animations/LoadingScreen.jsx +++ b/src/components/animations/LoadingScreen.jsx @@ -63,11 +63,14 @@ const LogoContainer = styled(Box)({ marginBottom: '24px', }) +import { useTranslation } from 'react-i18next' + const LoadingScreen = ({ - message = 'Loading...', + message = null, showLogo = true, size = 'lg', }) => { + const { t } = useTranslation('common') return ( @@ -82,7 +85,7 @@ const LoadingScreen = ({ mb: 1, }} > - Done + {t('done')} tick @@ -96,7 +99,7 @@ const LoadingScreen = ({ }} /> - {message} + {message ?? t('loading')} ) diff --git a/src/components/common/filter/ActiveFilterChips.jsx b/src/components/common/filter/ActiveFilterChips.jsx index 7978629..faef0df 100644 --- a/src/components/common/filter/ActiveFilterChips.jsx +++ b/src/components/common/filter/ActiveFilterChips.jsx @@ -1,6 +1,8 @@ import { Add, Close } from '@mui/icons-material' import { Box, Button, Chip, ChipDelete, Typography } from '@mui/joy' +import { useTranslation } from 'react-i18next' + const ActiveFilterChips = ({ chipSize = 'md', chipSx, @@ -18,6 +20,7 @@ const ActiveFilterChips = ({ showAddChip = false, totalCount, }) => { + const { t } = useTranslation('common') if (!chips.length) { return null } @@ -161,7 +164,7 @@ const ActiveFilterChips = ({ ...clearButtonSx, }} > - Clear all + {t('clearAll')} )} diff --git a/src/hooks/useConfirmationModal.js b/src/hooks/useConfirmationModal.js index 674ef47..516f0ca 100644 --- a/src/hooks/useConfirmationModal.js +++ b/src/hooks/useConfirmationModal.js @@ -1,14 +1,16 @@ import { useState } from 'react' +import { useTranslation } from 'react-i18next' const useConfirmationModal = () => { + const { t } = useTranslation('common') const [confirmModalConfig, setConfirmModalConfig] = useState({}) const showConfirmation = ( message, title, onConfirm, - confirmText = 'Confirm', - cancelText = 'Cancel', + confirmText = t('confirm'), + cancelText = t('cancel'), color = 'primary', ) => { setConfirmModalConfig({ diff --git a/src/hooks/useFileUpload.js b/src/hooks/useFileUpload.js index ec84b7e..f4430b4 100644 --- a/src/hooks/useFileUpload.js +++ b/src/hooks/useFileUpload.js @@ -5,12 +5,14 @@ import { useUserProfile } from '../queries/UserQueries' import { useNotification } from '../service/NotificationProvider' import { apiClient } from '../utils/ApiClient' import { isPlusAccount, resolvePhotoURL } from '../utils/Helpers' +import { useTranslation } from 'react-i18next' export const useFileUpload = ({ draftId, entityId, entityType = 'chore_attachment', } = {}) => { + const { t } = useTranslation('common') const { showError } = useNotification() const { data: userProfile } = useUserProfile() @@ -58,14 +60,14 @@ export const useFileUpload = ({ if (response.status === 507) { showError({ - title: 'Storage Quota Exceeded', - message: 'You have exceeded your quota for uploading files.', + title: t('upload.quotaTitle'), + message: t('upload.quotaMessage'), }) return null } else if (response.status === 413) { showError({ - title: 'File Too Large', - message: 'The file you are trying to upload is too large.', + title: t('upload.tooLargeTitle'), + message: t('upload.tooLargeMessage'), }) return null } else if (response.status === 403 && !isPlusAccount(userProfile)) { @@ -76,8 +78,8 @@ export const useFileUpload = ({ return null } else if (response.status === 403) { showError({ - title: 'Permission Denied', - message: 'You do not have permission to upload files.', + title: t('upload.deniedTitle'), + message: t('upload.deniedMessage'), }) return null } else if (!response.ok) { @@ -105,7 +107,7 @@ export const useFileUpload = ({ return null } }, - [entityType, entityId, draftId, showError, userProfile], + [entityType, entityId, draftId, showError, userProfile, t], ) return { uploadFile, isPlus: isPlusAccount(userProfile) } diff --git a/src/views/Error.jsx b/src/views/Error.jsx index f06a2b1..993b855 100644 --- a/src/views/Error.jsx +++ b/src/views/Error.jsx @@ -18,6 +18,7 @@ import { formatErrorReport, } from '../service/ErrorReportService' import ErrorReportModal from './Modals/ErrorReportModal' +import { useTranslation } from 'react-i18next' const getErrorKind = error => { if (!error) @@ -55,6 +56,7 @@ const safeMessage = error => { } const Error = () => { + const { t } = useTranslation('common') const error = useRouteError() const [showDetails, setShowDetails] = useState(false) const [copied, setCopied] = useState(false) @@ -192,7 +194,7 @@ const Error = () => { textAlign='center' sx={{ mb: 1.5 }} > - Something went wrong + {t('errorScreen.title')} {/* Error message */} @@ -207,8 +209,7 @@ const Error = () => { wordBreak: 'break-word', }} > - {message ?? - 'An unexpected error occurred. Try reloading — it usually fixes it.'} + {message ?? t('errorScreen.fallback')} {/* Primary CTA */} @@ -220,7 +221,7 @@ const Error = () => { onClick={() => window.location.reload()} sx={{ width: '100%', mb: 1.5 }} > - Try again + {t('errorScreen.tryAgain')} {/* Reporting is one tap from the failure, where the context is still @@ -246,7 +247,7 @@ const Error = () => { size='lg' startDecorator={} > - Home + {t('errorScreen.home')} @@ -293,7 +294,9 @@ const Error = () => { } sx={{ mb: 1 }} > - {showDetails ? 'Hide' : 'Show'} error details + {showDetails + ? t('errorScreen.hideDetails') + : t('errorScreen.showDetails')} {showDetails && ( @@ -312,7 +315,7 @@ const Error = () => { color='neutral' onClick={handleCopy} sx={{ position: 'absolute', top: 8, right: 8 }} - title='Copy to clipboard' + title={t('errorScreen.copyToClipboard')} > @@ -347,7 +350,7 @@ const Error = () => { anchorOrigin={{ vertical: 'bottom', horizontal: 'center' }} size='sm' > - Error details copied to clipboard + {t('errorScreen.copied')} ) diff --git a/src/views/Home.jsx b/src/views/Home.jsx index 894e7a7..5600c1b 100644 --- a/src/views/Home.jsx +++ b/src/views/Home.jsx @@ -1,10 +1,12 @@ import { Box, Button, Container, Typography } from '@mui/joy' +import { useTranslation } from 'react-i18next' import { useEffect } from 'react' import { useNavigate } from 'react-router-dom' import { useState } from 'react' import Logo from '../Logo' const Home = () => { + const { t } = useTranslation('common') const Navigate = useNavigate() const getCurrentUser = () => { return JSON.parse(localStorage.getItem('user')) @@ -36,7 +38,7 @@ const Home = () => { Navigate('/chores') }} > - Get Started! + {t('getStarted')} diff --git a/src/views/Modals/Inputs/AttachmentViewerModal.jsx b/src/views/Modals/Inputs/AttachmentViewerModal.jsx index c0eefc2..1da2fb7 100644 --- a/src/views/Modals/Inputs/AttachmentViewerModal.jsx +++ b/src/views/Modals/Inputs/AttachmentViewerModal.jsx @@ -1,4 +1,5 @@ import { Browser } from '@capacitor/browser' +import { useTranslation } from 'react-i18next' import { Capacitor } from '@capacitor/core' import { Download } from '@mui/icons-material' import { Box, CircularProgress, Typography } from '@mui/joy' @@ -29,6 +30,7 @@ const downloadUrl = (url, fileName) => { } function AttachmentViewerModal({ config }) { + const { t } = useTranslation('common') const { ResponsiveModal } = useResponsiveModal() const [imgLoaded, setImgLoaded] = useState(false) const [imgError, setImgError] = useState(false) @@ -49,7 +51,7 @@ function AttachmentViewerModal({ config }) { maxHeight='92vh' footer={ , @@ -73,7 +75,7 @@ function AttachmentViewerModal({ config }) { )} {imgError ? ( - Failed to load image. + {t('imageLoadFailed')} ) : ( } > diff --git a/src/views/Modals/Inputs/TextModal.jsx b/src/views/Modals/Inputs/TextModal.jsx index 1a1c8a2..1e945ef 100644 --- a/src/views/Modals/Inputs/TextModal.jsx +++ b/src/views/Modals/Inputs/TextModal.jsx @@ -2,6 +2,7 @@ import { Textarea } from '@mui/joy' import { useState } from 'react' import ModalActions from '../../../components/common/ModalActions' import { useResponsiveModal } from '../../../hooks/useResponsiveModal' +import { useTranslation } from 'react-i18next' function TextModal({ isOpen, @@ -12,6 +13,7 @@ function TextModal({ okText, cancelText, }) { + const { t } = useTranslation('common') const { ResponsiveModal } = useResponsiveModal() const [text, setText] = useState(current) @@ -35,7 +37,7 @@ function TextModal({ >