diff --git a/frontend/Dockerfile b/frontend/Dockerfile index 2101e6fa..2ec78f7b 100644 --- a/frontend/Dockerfile +++ b/frontend/Dockerfile @@ -37,6 +37,7 @@ RUN chown nextjs:nodejs .next # Automatically leverage output traces to reduce image size COPY --from=builder --chown=nextjs:nodejs /app/.next/standalone ./ COPY --from=builder --chown=nextjs:nodejs /app/.next/static ./.next/static +COPY --from=builder --chown=nextjs:nodejs /app/messages ./messages USER nextjs diff --git a/frontend/app/dashboard/analytics/page.tsx b/frontend/app/dashboard/analytics/page.tsx index 9047b321..5c7b3b45 100644 --- a/frontend/app/dashboard/analytics/page.tsx +++ b/frontend/app/dashboard/analytics/page.tsx @@ -16,6 +16,7 @@ import { Progress } from '@/components/ui/progress'; import { useAnalytics } from '@/lib/hooks/use-analytics'; import Image from 'next/image'; import Link from 'next/link'; +import { useTranslations } from 'next-intl'; function StatCard({ title, @@ -195,14 +196,15 @@ function AcceptanceTrendChart({ data }: { data: { period: string; rate: number; } export default function AnalyticsPage() { + const t = useTranslations('analytics'); const { data, isLoading, isError } = useAnalytics(60); if (isLoading) { return (
Your wardrobe insights and statistics
+{t('subtitle')}
Your wardrobe insights and statistics
+{t('subtitle')}
No color data yet
+{t('insights.colorDistribution.noData')}
) : (No items yet
+{t('insights.itemTypes.noData')}
) : (Start tracking your outfits!
+{t('insights.mostWorn.noData')}
) : (Keep tracking!
+{t('insights.leastWorn.noData')}
) : (All items have been worn!
+{t('insights.neverWorn.noData')}
) : (- Browse and rate your family members' outfits + {t('subtitle')}
- Create or join a family to browse and rate each other's outfits. + {t('noFamily.description')}
- Browse and rate your family members' outfits + {t('subtitle')}
- Invite family members to start browsing and rating each other's outfits. + {t('noMembers.description')}
- Browse and rate your family members' outfits + {t('subtitle')}
- {selectedMemberInfo?.display_name ?? 'This member'} hasn't received any outfit recommendations yet. - Check back later! + {t('noOutfits.description', { member: selectedMemberInfo?.display_name ?? 'This member' })}
- Create or join a family to share your wardrobe experience + {t('description')}
- Invalid invite code. Please check and try again. + {t('invalidInviteCode')}
)}- Your outfit recommendation history will appear here once you start - receiving suggestions. + {t('empty.description')}
- No outfits for {format(date, 'MMMM d, yyyy')} + {t('noOutfitsForDate', { date: format(date, 'MMMM d, yyyy') })}
- View your past outfit recommendations + {t('subtitle')}
- {selectedDateOutfits.length} outfit{selectedDateOutfits.length !== 1 ? 's' : ''} + {t('outfitCount', { count: selectedDateOutfits.length })}
Loading your wardrobe...
+{t('layout.loading')}
- Start by accepting or rejecting outfit suggestions and rating them. - The AI will learn from your feedback to make better recommendations. + {t('noData.description')}
- Already gave feedback? Click "Compute Now" to process it. + {t('noData.alreadyGaveFeedback')}
How the AI learns from your feedback
+{t('subtitle')}
{profile.has_learning_data - ? 'The AI learns from your feedback to improve recommendations' - : 'Start rating outfits to help the AI learn your preferences'} + ? t('subtitle') + : t('subtitleEmpty')}
- Not enough feedback to determine color preferences yet. + {t('colorPreferences.noData')}
) : (- Not enough feedback to determine style preferences yet. + {t('stylePreferences.noData')}
) : (- ~{pref.preferred_layers.toFixed(1)} layers + {t('weatherPreferences.layers', { count: pref.preferred_layers.toFixed(1) })}
- Learning profile last updated: {new Date(profile.last_computed_at).toLocaleString()} + {t('lastUpdated')} {new Date(profile.last_computed_at).toLocaleString()}
)} > diff --git a/frontend/app/dashboard/notifications/page.tsx b/frontend/app/dashboard/notifications/page.tsx index 36656549..1e0aaf0c 100644 --- a/frontend/app/dashboard/notifications/page.tsx +++ b/frontend/app/dashboard/notifications/page.tsx @@ -61,16 +61,17 @@ import { Schedule, } from '@/lib/hooks/use-notifications'; import { useUserProfile } from '@/lib/hooks/use-user'; -import { OCCASIONS } from '@/lib/types'; - -const DAYS = [ - { value: 0, label: 'Monday' }, - { value: 1, label: 'Tuesday' }, - { value: 2, label: 'Wednesday' }, - { value: 3, label: 'Thursday' }, - { value: 4, label: 'Friday' }, - { value: 5, label: 'Saturday' }, - { value: 6, label: 'Sunday' }, +import { useOccasions } from '@/lib/hooks/use-translated-constants'; +import { useTranslations } from 'next-intl'; + +const DAY_KEYS = [ + { value: 0, key: 'monday' as const }, + { value: 1, key: 'tuesday' as const }, + { value: 2, key: 'wednesday' as const }, + { value: 3, key: 'thursday' as const }, + { value: 4, key: 'friday' as const }, + { value: 5, key: 'saturday' as const }, + { value: 6, key: 'sunday' as const }, ]; const CHANNEL_ICONS: Record{CHANNEL_LABELS[setting.channel]}
+{channelLabels[setting.channel] || CHANNEL_LABELS[setting.channel]}
{setting.channel === 'ntfy' && setting.config.topic} - {setting.channel === 'mattermost' && 'Webhook configured'} + {setting.channel === 'mattermost' && t('channels.webhookConfigured')} {setting.channel === 'email' && setting.config.address}