feat: add next-intl internationalization with 4 locales#76
Closed
gzhang33 wants to merge 6 commits into
Closed
Conversation
Co-authored-by: Copilot Autofix powered by AI <175728472+Copilot@users.noreply.github.com>
Integrate next-intl for full i18n support across the frontend: - Add next-intl package and configure middleware + request config - Create locale switcher component with dropdown menu - Extract all hardcoded strings to translation files (967 keys) - Support English, Chinese, French, and Italian locales - Migrate all pages, components, dialogs, and cards to use useTranslations() - Replace hardcoded toast messages, titles, placeholders, and inline text - Add use-translated-constants hook for dynamic locale-aware constants - Add formatWornAgo i18n helper and location error message translations - Update tests with next-intl mock setup - All 65 tests pass, TypeScript compiles clean, production build succeeds Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Contributor
There was a problem hiding this comment.
Pull request overview
This PR introduces end-to-end internationalization in the Next.js frontend using next-intl (supporting en/zh/fr/it) and updates the backend weather API to support resolving saved, free-form location names via a geocoding service (with configurable User-Agent/contact metadata).
Changes:
- Frontend: integrate
next-intl(plugin + request config), add locale switching viaNEXT_LOCALEcookie, and migrate UI strings to translation keys. - Frontend: add i18n-aware helpers/hooks (e.g.,
formatWornAgo, translated constants for types/colors/occasions) and expand test coverage for i18n/location helpers. - Backend: add Nominatim geocoding support for weather endpoints + config/test coverage for geocoding User-Agent behavior.
Reviewed changes
Copilot reviewed 71 out of 72 changed files in this pull request and generated 8 comments.
Show a summary per file
| File | Description |
|---|---|
| frontend/tests/utils.test.ts | Adds unit tests for formatWornAgo (and keeps existing cn tests). |
| frontend/tests/setup.ts | Mocks next-intl / next-intl/server for frontend tests. |
| frontend/tests/location.test.ts | Adds tests for new frontend location helper module. |
| frontend/package.json | Adds next-intl dependency. |
| frontend/next.config.js | Wraps Next config with next-intl plugin. |
| frontend/lib/utils.ts | Updates formatWornAgo to be translation-function driven. |
| frontend/lib/location.ts | Introduces reusable helpers for network/reverse-geocoded locations + i18n error mapping. |
| frontend/lib/hooks/use-translated-constants.ts | Adds hooks returning translated clothing types/colors/occasions. |
| frontend/i18n/request.ts | Adds next-intl request config (locale resolution + message loading). |
| frontend/Dockerfile | Copies messages/ into the runtime image. |
| frontend/components/ui/dropdown-menu.tsx | Adds dropdown menu UI wrapper (used by locale switcher). |
| frontend/components/studio/details-panel.tsx | Migrates strings/toasts/warnings to translations in studio details panel. |
| frontend/components/studio/canvas-panel.tsx | Migrates canvas empty state + aria-labels to translations. |
| frontend/components/sidebar.tsx | Translates sidebar navigation labels + brand alt/name. |
| frontend/components/shared/occasion-chips.tsx | Uses translated occasions instead of hardcoded constants. |
| frontend/components/shared/lineage-card.tsx | Translates lineage card text with/without date. |
| frontend/components/shared/item-picker.tsx | Translates search/loading/empty states + count labels. |
| frontend/components/shared/clone-to-lookbook-dialog.tsx | Translates dialog text and toast messages. |
| frontend/components/pairing-card.tsx | Translates pairing card labels and toast messages. |
| frontend/components/pagination.tsx | Translates aria-labels and “Page X of Y”. |
| frontend/components/outfits/outfit-card.tsx | Translates source badge labels + fallback title/meta labels. |
| frontend/components/outfit-preview-dialog.tsx | Translates dialog UI strings, titles, tooltips, and toasts. |
| frontend/components/outfit-history-card.tsx | Translates history card UI strings and toasts. |
| frontend/components/outfit-calendar.tsx | Translates legend/source badges. |
| frontend/components/offline-indicator.tsx | Translates offline indicator message. |
| frontend/components/mobile-sidebar.tsx | Translates mobile sidebar nav/labels and brand strings. |
| frontend/components/mobile-nav.tsx | Translates bottom mobile nav labels. |
| frontend/components/locale-switcher.tsx | Adds locale switcher that sets NEXT_LOCALE cookie and reloads. |
| frontend/components/image-lightbox.tsx | Translates lightbox action label. |
| frontend/components/header.tsx | Adds locale switcher to header + translates aria-labels/fallbacks. |
| frontend/components/generate-pairings-dialog.tsx | Translates dialog strings and success messaging for generated pairings. |
| frontend/components/feedback-dialog.tsx | Translates multi-step feedback dialog UI/toasts. |
| frontend/components/family-ratings.tsx | Translates family rating form/display strings and toasts. |
| frontend/components/color-eyedropper.tsx | Translates eyedropper dialog strings; uses translated color names. |
| frontend/components/bulk-action-toolbar.tsx | Translates bulk selection labels and delete confirmation copy. |
| frontend/components/add-item-dialog.tsx | Translates add/bulk upload dialog; uses translated type/color constants. |
| frontend/app/page.tsx | Migrates landing page to server-side getTranslations. |
| frontend/app/not-found.tsx | Migrates 404 page to server-side getTranslations. |
| frontend/app/login/page.tsx | Migrates login UI + error messages to translations. |
| frontend/app/layout.tsx | Wraps app with NextIntlClientProvider, sets <html lang> dynamically. |
| frontend/app/invite/page.tsx | Migrates invite page text + error mapping to translations. |
| frontend/app/error.tsx | Migrates global error page text/buttons to translations. |
| frontend/app/dashboard/wardrobe/page.tsx | Migrates wardrobe page strings (filters, labels, toasts) to translations. |
| frontend/app/dashboard/suggest/page.tsx | Migrates suggest page strings; refactors greeting/weather hints to key-based translation. |
| frontend/app/dashboard/pairings/page.tsx | Migrates pairings page strings + empty states to translations. |
| frontend/app/dashboard/page.tsx | Migrates dashboard widgets/labels/toasts to translations. |
| frontend/app/dashboard/outfits/page.tsx | Migrates outfits list/calendar page strings to translation keys. |
| frontend/app/dashboard/outfits/new/page.tsx | Migrates studio editor/new outfit flow strings and toasts to translations. |
| frontend/app/dashboard/outfits/[id]/page.tsx | Migrates outfit detail page strings and confirm/toasts to translations. |
| frontend/app/dashboard/notifications/page.tsx | Migrates notifications UI; translates days/occasions/channels/schedules. |
| frontend/app/dashboard/learning/page.tsx | Migrates learning page strings to translations. |
| frontend/app/dashboard/layout.tsx | Migrates dashboard layout loading text to translations. |
| frontend/app/dashboard/history/page.tsx | Migrates history page labels/filters/empty states to translations. |
| frontend/app/dashboard/family/page.tsx | Migrates family management UI (including rich text) to translations. |
| frontend/app/dashboard/family/feed/page.tsx | Migrates family feed strings/badges/actions to translations. |
| frontend/app/dashboard/analytics/page.tsx | Migrates analytics page strings to translations. |
| backend/tests/test_weather_service.py | Adds test coverage for geocoding JSON decode failure behavior. |
| backend/tests/test_weather_api.py | Adds API tests for geocoding fallback and failure handling. |
| backend/tests/test_config.py | Adds tests for geocoding User-Agent construction logic. |
| backend/app/services/weather_service.py | Adds geocode_location_name and GeocodingServiceError. |
| backend/app/main.py | Uses settings.app_version for FastAPI version metadata. |
| backend/app/config.py | Adds app_version, geocoding configuration, and user-agent builder helper. |
| backend/app/api/weather.py | Adds geocoding fallback for saved location names + consistent 503 response detail. |
| .env.example | Documents geocoding env vars and frontend network location override. |
Files not reviewed (1)
- frontend/package-lock.json: Language not supported
💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.
| {item.last_worn_at ? ( | ||
| <p className={`text-xs mt-1 ${getWornAgoColorClass(item.last_worn_at, userTimezone)}`}> | ||
| {formatWornAgo(item.last_worn_at, userTimezone)} | ||
| {formatWornAgo(item.last_worn_at, userTimezone, tShared.raw)} |
Comment on lines
136
to
138
| <p className="font-medium text-lg"> | ||
| {generatedPairings.length} outfit{generatedPairings.length !== 1 ? 's' : ''} created! | ||
| {generatedPairings.length} outfit{generatedPairings.length !== 1 ? 's' : ''} {t('success').replace(/\d+ outfit\(s\) /, '')} | ||
| </p> |
| <Loader2 className="mr-2 h-4 w-4 animate-spin" /> | ||
| ) : null} | ||
| Leave | ||
| {t('leaveFamily').split(' ')[0]} |
Comment on lines
+509
to
512
| <DialogTitle>{t('schedule.addSchedule')}</DialogTitle> | ||
| <DialogDescription> | ||
| Set up when you want to receive outfit recommendations. | ||
| {t('schedule.description').split('.')[0]}. | ||
| </DialogDescription> |
| return; | ||
| } | ||
| toast.error(getErrorMessage(error, 'Failed to save outfit')); | ||
| toast.error(getErrorMessage(error, t('new.outfitUpdated'))); |
Comment on lines
+25
to
+32
| vi.mock('next-intl', () => ({ | ||
| useTranslations: () => (key: string) => key, | ||
| })) | ||
|
|
||
| vi.mock('next-intl/server', () => ({ | ||
| getTranslations: async () => (key: string) => key, | ||
| getMessages: async () => ({}), | ||
| })) |
Comment on lines
+1
to
+3
| import { describe, it, expect, vi } from 'vitest' | ||
| import { cn, formatWornAgo, getDaysSinceDateInTimezone } from '@/lib/utils' | ||
|
|
Comment on lines
+28
to
29
| function computeWarnings(items: StudioItem[], t: any): string[] { | ||
| const warnings: string[] = []; |
Author
|
Closing in favor of two focused PRs:
|
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Summary
useTranslations()use-translated-constantshook for dynamic locale-aware constantsformatWornAgoi18n helper and location error message translationsTest Plan
npx tsc --noEmit— zero TypeScript errorsnpx vitest run— all 65 tests passnpm run build— production build succeeds🤖 Generated with Claude Code