feat: Complete comprehensive localization of all tracking and management pages
Some checks failed
CI/CD Pipeline / Build Application (push) Has been cancelled
CI/CD Pipeline / Lint and Test (push) Has been cancelled
CI/CD Pipeline / E2E Tests (push) Has been cancelled

- Feeding page: 47+ strings localized with validation, success/error messages
- Medicine page: 44 strings localized with unit conversion support
- Sleep page: Already localized (verified)
- Diaper page: Already localized (verified)
- Activity page: Already localized (verified)
- AI Assistant: 51 strings localized including chat interface and suggested questions
- Children page: 38 strings fully localized with gender labels
- Family page: 42 strings localized with role management
- Insights page: 41 strings localized including charts and analytics

Added translation files:
- locales/en/ai.json (44 keys)
- locales/en/family.json (42 keys)
- locales/en/insights.json (41 keys)

Updated translation files:
- locales/en/tracking.json (added feeding, health/medicine sections)
- locales/en/children.json (verified complete)

All pages now use useTranslation hook with proper namespaces.
All user-facing text externalized and ready for multi-language support.
This commit is contained in:
2025-10-03 13:57:47 +00:00
parent 5fea603922
commit 41320638e5
10 changed files with 434 additions and 204 deletions

View File

@@ -25,8 +25,10 @@ import { InviteMemberDialog } from '@/components/family/InviteMemberDialog';
import { JoinFamilyDialog } from '@/components/family/JoinFamilyDialog';
import { RemoveMemberDialog } from '@/components/family/RemoveMemberDialog';
import { motion } from 'framer-motion';
import { useTranslation } from '@/hooks/useTranslation';
export default function FamilyPage() {
const { t } = useTranslation('family');
const { user, refreshUser } = useAuth();
const [family, setFamily] = useState<Family | null>(null);
const [members, setMembers] = useState<FamilyMember[]>([]);
@@ -47,7 +49,7 @@ export default function FamilyPage() {
fetchFamilyData();
} else {
setLoading(false);
setError('No family found. Please complete onboarding first.');
setError(t('messages.noFamilyFound'));
}
}, [familyId]);
@@ -65,7 +67,7 @@ export default function FamilyPage() {
setMembers(membersData);
} catch (err: any) {
console.error('Failed to fetch family data:', err);
setError(err.response?.data?.message || 'Failed to load family information');
setError(err.response?.data?.message || t('messages.failedToLoad'));
} finally {
setLoading(false);
}
@@ -76,26 +78,26 @@ export default function FamilyPage() {
try {
await navigator.clipboard.writeText(family.shareCode);
setSnackbar({ open: true, message: 'Share code copied to clipboard!' });
setSnackbar({ open: true, message: t('messages.shareCodeCopied') });
} catch (err) {
setSnackbar({ open: true, message: 'Failed to copy share code' });
setSnackbar({ open: true, message: t('messages.shareCodeCopyFailed') });
}
};
const handleInviteMember = async (data: InviteMemberData) => {
if (!familyId) {
throw new Error('No family ID found');
throw new Error(t('messages.noFamilyId'));
}
try {
setActionLoading(true);
await familiesApi.inviteMember(familyId, data);
setSnackbar({ open: true, message: 'Invitation sent successfully!' });
setSnackbar({ open: true, message: t('messages.invitationSent') });
await fetchFamilyData();
setInviteDialogOpen(false);
} catch (err: any) {
console.error('Failed to invite member:', err);
throw new Error(err.response?.data?.message || 'Failed to send invitation');
throw new Error(err.response?.data?.message || t('messages.failedToInvite'));
} finally {
setActionLoading(false);
}
@@ -105,13 +107,13 @@ export default function FamilyPage() {
try {
setActionLoading(true);
await familiesApi.joinFamily(data);
setSnackbar({ open: true, message: 'Successfully joined family!' });
setSnackbar({ open: true, message: t('messages.joinedFamily') });
await refreshUser();
await fetchFamilyData();
setJoinDialogOpen(false);
} catch (err: any) {
console.error('Failed to join family:', err);
throw new Error(err.response?.data?.message || 'Failed to join family');
throw new Error(err.response?.data?.message || t('messages.failedToJoin'));
} finally {
setActionLoading(false);
}
@@ -128,13 +130,13 @@ export default function FamilyPage() {
try {
setActionLoading(true);
await familiesApi.removeMember(familyId, memberToRemove.userId);
setSnackbar({ open: true, message: 'Member removed successfully' });
setSnackbar({ open: true, message: t('messages.memberRemoved') });
await fetchFamilyData();
setRemoveDialogOpen(false);
setMemberToRemove(null);
} catch (err: any) {
console.error('Failed to remove member:', err);
setError(err.response?.data?.message || 'Failed to remove member');
setError(err.response?.data?.message || t('messages.failedToRemove'));
} finally {
setActionLoading(false);
}
@@ -164,10 +166,10 @@ export default function FamilyPage() {
<Box sx={{ display: 'flex', justifyContent: 'space-between', alignItems: 'center', mb: 4 }}>
<Box>
<Typography variant="h4" component="h1" fontWeight="600" gutterBottom>
Family
{t('pageTitle')}
</Typography>
<Typography variant="body1" color="text.secondary">
Manage your family members and share access
{t('pageSubtitle')}
</Typography>
</Box>
<Box sx={{ display: 'flex', gap: 1 }}>
@@ -177,7 +179,7 @@ export default function FamilyPage() {
onClick={() => setJoinDialogOpen(true)}
disabled={loading}
>
Join Family
{t('buttons.joinFamily')}
</Button>
<Button
variant="contained"
@@ -185,7 +187,7 @@ export default function FamilyPage() {
onClick={() => setInviteDialogOpen(true)}
disabled={loading || !familyId}
>
Invite Member
{t('buttons.inviteMember')}
</Button>
</Box>
</Box>
@@ -208,10 +210,10 @@ export default function FamilyPage() {
<Card>
<CardContent>
<Typography variant="h6" component="h2" fontWeight="600" gutterBottom>
Family Share Code
{t('shareCode.title')}
</Typography>
<Typography variant="body2" color="text.secondary" sx={{ mb: 2 }}>
Share this code with family members to give them access to your family&apos;s data
{t('shareCode.description')}
</Typography>
<Box sx={{ display: 'flex', alignItems: 'center', gap: 2, flexWrap: 'wrap' }}>
<Chip
@@ -229,7 +231,7 @@ export default function FamilyPage() {
startIcon={<ContentCopy />}
onClick={handleCopyCode}
>
Copy Code
{t('buttons.copyCode')}
</Button>
</Box>
</CardContent>
@@ -242,30 +244,30 @@ export default function FamilyPage() {
<Card>
<CardContent>
<Typography variant="h6" fontWeight="600" gutterBottom sx={{ mb: 3 }}>
Family Members ({members.length})
{t('members.title', { count: members.length })}
</Typography>
{members.length === 0 ? (
<Box sx={{ textAlign: 'center', py: 4 }}>
<People sx={{ fontSize: 48, color: 'text.secondary', mb: 2 }} />
<Typography variant="body2" color="text.secondary" gutterBottom>
No family members yet
{t('members.noMembers')}
</Typography>
<Typography variant="body2" color="text.secondary" sx={{ mb: 2 }}>
Invite family members to collaborate on child care
{t('members.noMembersDescription')}
</Typography>
<Button
variant="outlined"
startIcon={<PersonAdd />}
onClick={() => setInviteDialogOpen(true)}
>
Invite First Member
{t('buttons.inviteFirstMember')}
</Button>
</Box>
) : (
<Box sx={{ display: 'flex', flexDirection: 'column', gap: 2 }}>
{members.map((member, index) => {
const memberName = member.user?.name || 'Unknown User';
const memberName = member.user?.name || t('placeholders.unknownUser');
return (
<Box key={member.id} component="div">
<motion.div
@@ -289,15 +291,15 @@ export default function FamilyPage() {
{memberName}
</Typography>
{isCurrentUser(member.userId) && (
<Chip label="You" size="small" color="success" />
<Chip label={t('members.youLabel')} size="small" color="success" />
)}
</Box>
<Typography variant="body2" color="text.secondary">
{member.user?.email || 'No email'}
{member.user?.email || t('placeholders.noEmail')}
</Typography>
</Box>
<Chip
label={member.role.charAt(0).toUpperCase() + member.role.slice(1)}
label={t(`roles.${member.role}`)}
color={getRoleColor(member.role)}
size="small"
/>
@@ -306,7 +308,7 @@ export default function FamilyPage() {
size="small"
onClick={() => handleRemoveClick(member)}
color="error"
aria-label={`Remove ${memberName} from family`}
aria-label={t('members.removeAriaLabel', { name: memberName })}
>
<Delete />
</IconButton>