Merge pull request #209 from everysingletear/i18n/common
i18n: extract shared strings into the existing `common` namespace (Part of #145)
This commit is contained in:
@@ -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 (
|
||||
<LoadingContainer>
|
||||
<LoadingContent>
|
||||
@@ -82,7 +85,7 @@ const LoadingScreen = ({
|
||||
mb: 1,
|
||||
}}
|
||||
>
|
||||
Done
|
||||
{t('done')}
|
||||
<span style={{ color: '#06b6d4' }}>tick</span>
|
||||
</Typography>
|
||||
</LogoContainer>
|
||||
@@ -96,7 +99,7 @@ const LoadingScreen = ({
|
||||
}}
|
||||
/>
|
||||
|
||||
<PulsingText level='body-md'>{message}</PulsingText>
|
||||
<PulsingText level='body-md'>{message ?? t('loading')}</PulsingText>
|
||||
</LoadingContent>
|
||||
</LoadingContainer>
|
||||
)
|
||||
|
||||
@@ -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')}
|
||||
</Button>
|
||||
)}
|
||||
</Box>
|
||||
|
||||
@@ -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({
|
||||
|
||||
@@ -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) }
|
||||
|
||||
@@ -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')}
|
||||
</Typography>
|
||||
|
||||
{/* 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')}
|
||||
</Typography>
|
||||
|
||||
{/* Primary CTA */}
|
||||
@@ -220,7 +221,7 @@ const Error = () => {
|
||||
onClick={() => window.location.reload()}
|
||||
sx={{ width: '100%', mb: 1.5 }}
|
||||
>
|
||||
Try again
|
||||
{t('errorScreen.tryAgain')}
|
||||
</Button>
|
||||
|
||||
{/* Reporting is one tap from the failure, where the context is still
|
||||
@@ -246,7 +247,7 @@ const Error = () => {
|
||||
size='lg'
|
||||
startDecorator={<HomeRounded />}
|
||||
>
|
||||
Home
|
||||
{t('errorScreen.home')}
|
||||
</Button>
|
||||
<Button
|
||||
component={Link}
|
||||
@@ -255,7 +256,7 @@ const Error = () => {
|
||||
color='neutral'
|
||||
size='lg'
|
||||
>
|
||||
Login
|
||||
{t('errorScreen.login')}
|
||||
</Button>
|
||||
</Box>
|
||||
|
||||
@@ -293,7 +294,9 @@ const Error = () => {
|
||||
}
|
||||
sx={{ mb: 1 }}
|
||||
>
|
||||
{showDetails ? 'Hide' : 'Show'} error details
|
||||
{showDetails
|
||||
? t('errorScreen.hideDetails')
|
||||
: t('errorScreen.showDetails')}
|
||||
</Button>
|
||||
|
||||
{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')}
|
||||
>
|
||||
<ContentCopyRounded fontSize='small' />
|
||||
</IconButton>
|
||||
@@ -347,7 +350,7 @@ const Error = () => {
|
||||
anchorOrigin={{ vertical: 'bottom', horizontal: 'center' }}
|
||||
size='sm'
|
||||
>
|
||||
Error details copied to clipboard
|
||||
{t('errorScreen.copied')}
|
||||
</Snackbar>
|
||||
</Box>
|
||||
)
|
||||
|
||||
@@ -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')}
|
||||
</Button>
|
||||
</Box>
|
||||
</Container>
|
||||
|
||||
@@ -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={
|
||||
<ModalActions
|
||||
secondary={{ label: 'Close', onClick: handleClose }}
|
||||
secondary={{ label: t('close'), onClick: handleClose }}
|
||||
primary={{
|
||||
label: 'Download',
|
||||
startDecorator: <Download />,
|
||||
@@ -73,7 +75,7 @@ function AttachmentViewerModal({ config }) {
|
||||
)}
|
||||
{imgError ? (
|
||||
<Typography level='body-sm' sx={{ color: 'text.secondary' }}>
|
||||
Failed to load image.
|
||||
{t('imageLoadFailed')}
|
||||
</Typography>
|
||||
) : (
|
||||
<Box
|
||||
|
||||
@@ -2,8 +2,10 @@ import { Input } from '@mui/joy'
|
||||
import { useState } from 'react'
|
||||
import ModalActions from '../../../components/common/ModalActions'
|
||||
import { useResponsiveModal } from '../../../hooks/useResponsiveModal'
|
||||
import { useTranslation } from 'react-i18next'
|
||||
|
||||
function DateModal({ isOpen, onClose, onSave, current, title }) {
|
||||
const { t } = useTranslation('common')
|
||||
const { ResponsiveModal } = useResponsiveModal()
|
||||
const [date, setDate] = useState(
|
||||
current ? new Date(current).toISOString().split('T')[0] : '',
|
||||
@@ -22,8 +24,8 @@ function DateModal({ isOpen, onClose, onSave, current, title }) {
|
||||
title={title}
|
||||
footer={
|
||||
<ModalActions
|
||||
secondary={{ label: 'Cancel', onClick: onClose }}
|
||||
primary={{ label: 'Save', onClick: handleSave, disabled: !date }}
|
||||
secondary={{ label: t('cancel'), onClick: onClose }}
|
||||
primary={{ label: t('save'), onClick: handleSave, disabled: !date }}
|
||||
/>
|
||||
}
|
||||
>
|
||||
|
||||
@@ -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({
|
||||
>
|
||||
<Textarea
|
||||
autoFocus
|
||||
placeholder='Type in here…'
|
||||
placeholder={t('typeHere')}
|
||||
value={text}
|
||||
onChange={event => setText(event.target.value)}
|
||||
minRows={3}
|
||||
|
||||
@@ -1,8 +1,10 @@
|
||||
import { Avatar, Box, List, ListItem, Typography } from '@mui/joy'
|
||||
import ModalActions from '../../../components/common/ModalActions'
|
||||
import { useResponsiveModal } from '../../../hooks/useResponsiveModal'
|
||||
import { useTranslation } from 'react-i18next'
|
||||
|
||||
const UserModal = ({ isOpen, performers = [], onSelect, onClose }) => {
|
||||
const { t } = useTranslation('common')
|
||||
const { ResponsiveModal } = useResponsiveModal()
|
||||
|
||||
return (
|
||||
@@ -13,7 +15,7 @@ const UserModal = ({ isOpen, performers = [], onSelect, onClose }) => {
|
||||
fullWidth={true}
|
||||
title='Select User'
|
||||
footer={
|
||||
<ModalActions secondary={{ label: 'Cancel', onClick: onClose }} />
|
||||
<ModalActions secondary={{ label: t('cancel'), onClick: onClose }} />
|
||||
}
|
||||
>
|
||||
<List sx={{ mb: 2 }}>
|
||||
|
||||
@@ -1,13 +1,15 @@
|
||||
import { Card, IconButton, Typography } from '@mui/joy'
|
||||
import { useTranslation } from 'react-i18next'
|
||||
|
||||
const SummaryCard = () => {
|
||||
const { t } = useTranslation('common')
|
||||
return (
|
||||
<Card>
|
||||
<div className='flex justify-between'>
|
||||
<div>
|
||||
<Typography level='h2'>Summary</Typography>
|
||||
<Typography level='body-xs'>
|
||||
This is a summary of your chores
|
||||
{t('summaryOfChores')}
|
||||
</Typography>
|
||||
</div>
|
||||
<IconButton>
|
||||
|
||||
@@ -1,7 +1,9 @@
|
||||
import { Chip, List, ListItem, ListItemButton, Textarea } from '@mui/joy'
|
||||
import { useTranslation } from 'react-i18next'
|
||||
import React, { useEffect, useRef, useState } from 'react'
|
||||
|
||||
const AutocompleteInput = ({ options, ref, value, onChange, ...props }) => {
|
||||
const { t } = useTranslation('common')
|
||||
const [filteredOptions, setFilteredOptions] = useState([])
|
||||
const [menuVisible, setMenuVisible] = useState(false)
|
||||
const [highlightedIndex, setHighlightedIndex] = useState(-1)
|
||||
@@ -82,7 +84,7 @@ const AutocompleteInput = ({ options, ref, value, onChange, ...props }) => {
|
||||
value={value}
|
||||
onChange={onChange}
|
||||
onKeyDown={handleKeyDown}
|
||||
placeholder='Type here...'
|
||||
placeholder={t('autocompletePlaceholder')}
|
||||
/>
|
||||
{menuVisible && (
|
||||
<List ref={menuRef} style={{ position: 'absolute', zIndex: 1000 }}>
|
||||
|
||||
@@ -1,31 +1,29 @@
|
||||
import { Box, Button, CircularProgress, Container } from '@mui/joy'
|
||||
import { Typography } from '@mui/material'
|
||||
import { useEffect, useState } from 'react'
|
||||
import { useTranslation } from 'react-i18next'
|
||||
import Logo from '../../Logo'
|
||||
import { networkManager } from '../../hooks/NetworkManager'
|
||||
|
||||
const LoadingComponent = () => {
|
||||
const [message, setMessage] = useState('Loading...')
|
||||
const { t } = useTranslation('common')
|
||||
const [message, setMessage] = useState(t('loading'))
|
||||
const [subMessage, setSubMessage] = useState('')
|
||||
const [isOnline, setIsOnline] = useState(networkManager.isOnline)
|
||||
|
||||
useEffect(() => {
|
||||
if (!isOnline) {
|
||||
setMessage('You are offline')
|
||||
setSubMessage(
|
||||
'This not available while offline. Please check your internet connection and try again.',
|
||||
)
|
||||
setMessage(t('loadingOffline'))
|
||||
setSubMessage(t('loadingOfflineSub'))
|
||||
}
|
||||
}, [isOnline])
|
||||
}, [isOnline, t])
|
||||
useEffect(() => {
|
||||
networkManager.registerNetworkListener(isOnline => setIsOnline(isOnline))
|
||||
|
||||
// if loading took more than 5 seconds update submessage to mention there might be an error:
|
||||
const timeout = setTimeout(() => {
|
||||
if (networkManager.isOnline) {
|
||||
setSubMessage(
|
||||
'This is taking longer than usual. There might be an issue.',
|
||||
)
|
||||
setSubMessage(t('loadingSlow'))
|
||||
}
|
||||
}, 5000)
|
||||
return () => clearTimeout(timeout)
|
||||
@@ -67,7 +65,7 @@ const LoadingComponent = () => {
|
||||
window.location.href = '/' // navigate back to the home page
|
||||
}}
|
||||
>
|
||||
Navigate Back
|
||||
{t('navigateBack')}
|
||||
</Button>
|
||||
</Box>
|
||||
</Container>
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
import * as React from 'react'
|
||||
import { useTranslation } from 'react-i18next'
|
||||
import Box from '@mui/joy/Box'
|
||||
import ListItemDecorator from '@mui/joy/ListItemDecorator'
|
||||
import Tabs from '@mui/joy/Tabs'
|
||||
@@ -10,6 +11,7 @@ import Search from '@mui/icons-material/Search'
|
||||
import Person from '@mui/icons-material/Person'
|
||||
|
||||
export default function NavBarMobile() {
|
||||
const { t } = useTranslation('common')
|
||||
const [index, setIndex] = React.useState(0)
|
||||
const colors = ['primary', 'danger', 'success', 'warning']
|
||||
return (
|
||||
@@ -32,7 +34,7 @@ export default function NavBarMobile() {
|
||||
>
|
||||
<Tabs
|
||||
size='lg'
|
||||
aria-label='Bottom Navigation'
|
||||
aria-label={t('bottomNav')}
|
||||
value={index}
|
||||
onChange={(event, value) => setIndex(value)}
|
||||
sx={theme => ({
|
||||
@@ -98,7 +100,7 @@ export default function NavBarMobile() {
|
||||
<ListItemDecorator>
|
||||
<Person />
|
||||
</ListItemDecorator>
|
||||
Profile
|
||||
{t('profile')}
|
||||
</Tab>
|
||||
</TabList>
|
||||
</Tabs>
|
||||
|
||||
Reference in New Issue
Block a user