feat: Apply localization to Login, Dashboard, and Navigation (Phase 9 - Batch 1)

**Pages Localized:**
- Login page: All UI strings (titles, labels, buttons, links)
- Dashboard page: Welcome message, quick actions, daily summary, predictions
- AppShell: Connection status and presence indicators
- MobileNav: Menu items and app branding
- TabBar: Bottom navigation labels

**Translation Files:**
- Created dashboard.json for all 5 languages (en, es, fr, pt, zh)
- Enhanced common.json with navigation and connection strings
- Updated i18n config to include dashboard namespace

**Languages Supported:**
- English, Spanish, French, Portuguese, Chinese (Simplified)

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude <noreply@anthropic.com>
This commit is contained in:
2025-10-03 11:17:47 +00:00
parent de691525fb
commit acadfe7905
16 changed files with 289 additions and 54 deletions

View File

@@ -26,6 +26,7 @@ import { tokenStorage } from '@/lib/utils/tokenStorage';
import { biometricApi } from '@/lib/api/biometric';
import { startAuthentication } from '@simplewebauthn/browser';
import Link from 'next/link';
import { useTranslation } from '@/hooks/useTranslation';
const loginSchema = z.object({
email: z.string().email('Invalid email address'),
@@ -35,6 +36,7 @@ const loginSchema = z.object({
type LoginFormData = z.infer<typeof loginSchema>;
export default function LoginPage() {
const { t } = useTranslation('auth');
const [showPassword, setShowPassword] = useState(false);
const [error, setError] = useState<string | null>(null);
const [isLoading, setIsLoading] = useState(false);
@@ -178,7 +180,7 @@ export default function LoginPage() {
fontWeight="600"
color="primary.main"
>
Welcome Back 👋
{t('login.title')}
</Typography>
<Typography
variant="body2"
@@ -186,7 +188,7 @@ export default function LoginPage() {
color="text.secondary"
sx={{ mb: 3 }}
>
Sign in to continue tracking your child's journey
{t('login.subtitle')}
</Typography>
{error && (
@@ -198,7 +200,7 @@ export default function LoginPage() {
<Box component="form" onSubmit={handleSubmit(onSubmit)}>
<TextField
fullWidth
label="Email"
label={t('login.email')}
type="email"
margin="normal"
error={!!errors.email}
@@ -213,7 +215,7 @@ export default function LoginPage() {
<TextField
fullWidth
label="Password"
label={t('login.password')}
type={showPassword ? 'text' : 'password'}
margin="normal"
error={!!errors.password}
@@ -245,7 +247,7 @@ export default function LoginPage() {
variant="body2"
sx={{ cursor: 'pointer', textDecoration: 'none' }}
>
Forgot password?
{t('login.forgotPassword')}
</MuiLink>
</Box>
@@ -260,14 +262,14 @@ export default function LoginPage() {
{isLoading ? (
<CircularProgress size={24} color="inherit" />
) : (
'Sign In'
t('login.submit')
)}
</Button>
</Box>
<Divider sx={{ my: 3 }}>
<Typography variant="body2" color="text.secondary">
OR
{t('login.or')}
</Typography>
</Divider>
@@ -279,7 +281,7 @@ export default function LoginPage() {
disabled={isLoading}
sx={{ mb: 2 }}
>
Continue with Google
{t('login.continueWithGoogle')}
</Button>
<Button
@@ -289,7 +291,7 @@ export default function LoginPage() {
size="large"
disabled={isLoading}
>
Continue with Apple
{t('login.continueWithApple')}
</Button>
{isBiometricSupported && (
@@ -302,16 +304,16 @@ export default function LoginPage() {
onClick={handleBiometricLogin}
sx={{ mt: 2 }}
>
{isBiometricLoading ? 'Authenticating...' : 'Sign in with Biometrics'}
{isBiometricLoading ? t('login.biometric.authenticating') : t('login.biometric.useFaceId')}
</Button>
)}
<Box sx={{ mt: 3, textAlign: 'center' }}>
<Typography variant="body2" color="text.secondary">
Don't have an account?{' '}
{t('login.noAccount')}{' '}
<Link href="/register" passHref legacyBehavior>
<MuiLink sx={{ cursor: 'pointer', fontWeight: 600 }}>
Sign up
{t('login.signUp')}
</MuiLink>
</Link>
</Typography>

View File

@@ -25,8 +25,10 @@ import { useQuery } from '@apollo/client/react';
import { GET_DASHBOARD } from '@/graphql/queries/dashboard';
import { format } from 'date-fns';
import { useRealTimeActivities } from '@/hooks/useWebSocket';
import { useTranslation } from '@/hooks/useTranslation';
export default function HomePage() {
const { t } = useTranslation('dashboard');
const { user, isLoading: authLoading } = useAuth();
const router = useRouter();
const [selectedChildId, setSelectedChildId] = useState<string | null>(null);
@@ -93,12 +95,12 @@ export default function HomePage() {
}, [data, selectedChildId]);
const quickActions = [
{ icon: <Restaurant />, label: 'Feeding', color: '#E91E63', path: '/track/feeding' }, // Pink with 4.5:1 contrast
{ icon: <Hotel />, label: 'Sleep', color: '#1976D2', path: '/track/sleep' }, // Blue with 4.5:1 contrast
{ icon: <BabyChangingStation />, label: 'Diaper', color: '#F57C00', path: '/track/diaper' }, // Orange with 4.5:1 contrast
{ icon: <MedicalServices />, label: 'Medicine', color: '#C62828', path: '/track/medication' }, // Red with 4.5:1 contrast
{ icon: <Insights />, label: 'Activities', color: '#558B2F', path: '/activities' }, // Green with 4.5:1 contrast
{ icon: <SmartToy />, label: 'AI Assistant', color: '#D84315', path: '/ai-assistant' }, // Deep orange with 4.5:1 contrast
{ icon: <Restaurant />, label: t('quickActions.feeding'), color: '#E91E63', path: '/track/feeding' }, // Pink with 4.5:1 contrast
{ icon: <Hotel />, label: t('quickActions.sleep'), color: '#1976D2', path: '/track/sleep' }, // Blue with 4.5:1 contrast
{ icon: <BabyChangingStation />, label: t('quickActions.diaper'), color: '#F57C00', path: '/track/diaper' }, // Orange with 4.5:1 contrast
{ icon: <MedicalServices />, label: t('quickActions.medicine'), color: '#C62828', path: '/track/medication' }, // Red with 4.5:1 contrast
{ icon: <Insights />, label: t('quickActions.activities'), color: '#558B2F', path: '/activities' }, // Green with 4.5:1 contrast
{ icon: <SmartToy />, label: t('quickActions.aiAssistant'), color: '#D84315', path: '/ai-assistant' }, // Deep orange with 4.5:1 contrast
];
const formatSleepHours = (minutes: number) => {
@@ -132,15 +134,15 @@ export default function HomePage() {
transition={{ duration: 0.5 }}
>
<Typography variant="h4" component="h1" gutterBottom fontWeight="600" sx={{ mb: 1 }}>
Welcome Back{user?.name ? `, ${user.name}` : ''}! 👋
{user?.name ? t('welcomeBackWithName', { name: user.name }) : t('welcomeBack')} 👋
</Typography>
<Typography variant="body1" sx={{ mb: 4, color: 'text.primary' }}>
Track your child's activities and get AI-powered insights
{t('subtitle')}
</Typography>
{/* Quick Actions */}
<Typography variant="h6" component="h2" gutterBottom fontWeight="600" sx={{ mb: 2 }}>
Quick Actions
{t('quickActions.title')}
</Typography>
<Grid container spacing={2} sx={{ mb: 4 }}>
{quickActions.map((action, index) => (
@@ -160,7 +162,7 @@ export default function HomePage() {
router.push(action.path);
}
}}
aria-label={`Navigate to ${action.label}`}
aria-label={t('quickActions.navigateTo', { action: action.label })}
sx={{
p: 3,
height: '100%',
@@ -196,7 +198,7 @@ export default function HomePage() {
{/* Today's Summary */}
<Typography variant="h6" component="h2" gutterBottom fontWeight="600" sx={{ mb: 2 }}>
Today's Summary{selectedChild ? ` - ${selectedChild.name}` : ''}
{selectedChild ? t('summary.titleWithChild', { childName: selectedChild.name }) : t('summary.title')}
</Typography>
<ErrorBoundary
isolate
@@ -210,8 +212,8 @@ export default function HomePage() {
<Box sx={{ textAlign: 'center', py: 4 }}>
<Typography variant="body2" sx={{ color: 'rgba(0, 0, 0, 0.7)' }}>
{children.length === 0
? 'Add a child to start tracking'
: 'No activities tracked today'}
? t('summary.noChild')
: t('summary.noActivities')}
</Typography>
</Box>
) : (
@@ -232,7 +234,7 @@ export default function HomePage() {
{dailySummary.feedingCount || 0}
</Typography>
<Typography variant="body2" sx={{ color: 'rgba(0, 0, 0, 0.7)' }}>
Feedings
{t('summary.feedings')}
</Typography>
</Box>
</Grid>
@@ -254,7 +256,7 @@ export default function HomePage() {
: '0m'}
</Typography>
<Typography variant="body2" sx={{ color: 'rgba(0, 0, 0, 0.7)' }}>
Sleep
{t('summary.sleep')}
</Typography>
</Box>
</Grid>
@@ -274,7 +276,7 @@ export default function HomePage() {
{dailySummary.diaperCount || 0}
</Typography>
<Typography variant="body2" sx={{ color: 'rgba(0, 0, 0, 0.7)' }}>
Diapers
{t('summary.diapers')}
</Typography>
</Box>
</Grid>
@@ -294,7 +296,7 @@ export default function HomePage() {
{dailySummary.medicationCount || 0}
</Typography>
<Typography variant="body2" sx={{ color: 'rgba(0, 0, 0, 0.7)' }}>
Medications
{t('summary.medications')}
</Typography>
</Box>
</Grid>
@@ -308,13 +310,13 @@ export default function HomePage() {
<Box sx={{ mt: 4 }}>
<Paper sx={{ p: 3, bgcolor: 'primary.light' }}>
<Typography variant="body2" sx={{ color: 'rgba(0, 0, 0, 0.7)' }} gutterBottom>
Next Predicted Activity
{t('predictions.title')}
</Typography>
<Typography variant="h6" fontWeight="600" gutterBottom>
Nap time in 45 minutes
{t('predictions.napTime', { minutes: 45 })}
</Typography>
<Typography variant="body2" sx={{ color: 'rgba(0, 0, 0, 0.7)' }}>
Based on your child's sleep patterns
{t('predictions.basedOnPatterns')}
</Typography>
</Paper>
</Box>