feat: Add backend support for measurement unit preferences
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

Updated backend to support measurement unit preferences (Metric/Imperial)
in user profiles. The preferences are stored in the existing JSONB
preferences column, which already exists in the database.

**Backend Changes:**
- Updated User entity to include measurementUnit in preferences type
- Created UpdateProfileDto with proper validation for preferences
- Updated auth controller to use UpdateProfileDto for PATCH /api/v1/auth/profile
- Added IsIn validator for measurementUnit ('metric' | 'imperial')

**Documentation:**
- Updated LOCALIZATION_IMPLEMENTATION_PLAN.md with completion status
- Marked Phases 1, 2, 3, 7, 8 as completed
- Marked Phase 4 (backend preferences) as in progress
- Added detailed completion markers for each subsection

**Technical Details:**
- No migration needed - using existing preferences JSONB column
- Preferences object now includes: notifications, emailUpdates, darkMode, measurementUnit
- Endpoint: PATCH /api/v1/auth/profile accepts optional preferences object
- Validation ensures measurementUnit is either 'metric' or 'imperial'

**Next Steps:**
- Frontend integration to persist language/measurement preferences to backend
- Apply localization throughout remaining pages and components
- Create onboarding flow with language/measurement selection

🤖 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:01:19 +00:00
parent c1e37d30b0
commit de691525fb
4 changed files with 135 additions and 87 deletions

View File

@@ -18,25 +18,37 @@ Implement comprehensive internationalization (i18n) support for the Maternal App
## Current Status ## Current Status
### ✅ Already Completed ### ✅ Already Completed (Backend)
- Backend multilanguage support for AI responses - Backend multilanguage support for AI responses
- AI safety responses in 5 languages - AI safety responses in 5 languages
- MultiLanguageService with language detection - MultiLanguageService with language detection
### ✅ Completed (Frontend - Phase 1)
- ✅ i18next framework installed and configured
- ✅ I18nProvider integrated into app layout
- ✅ Translation files structure created (35 files: 5 languages × 7 namespaces)
- ✅ Custom hooks created (useTranslation, useLocale, useFormatting)
- ✅ Measurement unit conversion utilities implemented
- ✅ Language selector component (Settings page)
- ✅ Measurement unit selector component (Settings page)
- ✅ Settings page integration with language/measurement preferences
### ⏳ In Progress
- Backend user schema update for measurement preferences
### ❌ To Be Implemented ### ❌ To Be Implemented
- Frontend i18next framework - Language selector in onboarding flow
- Translation files for all UI strings - Apply translations to all pages and components
- Language selector in onboarding - Date/time localization throughout app
- Language selector in settings
- Date/time localization
- Measurement unit preferences (Metric/Imperial)
- Number formatting per locale - Number formatting per locale
- Tracking forms with unit conversions
- Professional translations (currently using placeholder translations)
--- ---
## Phase 1: Framework Setup ## Phase 1: Framework Setup ✅ COMPLETED
### 1.1 Install Dependencies - latest versions only! ### 1.1 Install Dependencies - latest versions only!
**Files**: `package.json` **Files**: `package.json`
```bash ```bash
@@ -49,31 +61,31 @@ npm install date-fns # Already installed, confirm locales
- `react-i18next` - React bindings - `react-i18next` - React bindings
- `i18next-browser-languagedetector` - Auto-detect user language - `i18next-browser-languagedetector` - Auto-detect user language
### 1.2 Create i18n Configuration ### 1.2 Create i18n Configuration
**File**: `lib/i18n/config.ts` (NEW) **File**: `lib/i18n/config.ts` (CREATED)
- Initialize i18next - Initialize i18next
- Configure language detector - Configure language detector
- Set up fallback language (en-US) - Set up fallback language (en-US)
- Configure interpolation - Configure interpolation
- Load translation resources - Load translation resources
### 1.3 Create i18n Provider ### 1.3 Create i18n Provider
**File**: `components/providers/I18nProvider.tsx` (NEW) **File**: `components/providers/I18nProvider.tsx` (CREATED)
- Wrap app with I18nextProvider - Wrap app with I18nextProvider
- Initialize i18n on mount - Initialize i18n on mount
- Handle language loading states - Handle language loading states
### 1.4 Update Root Layout ### 1.4 Update Root Layout
**File**: `app/layout.tsx` (MODIFY) **File**: `app/layout.tsx` (MODIFIED)
- Add I18nProvider to provider stack - Add I18nProvider to provider stack
- Set html lang attribute dynamically - Set html lang attribute dynamically (TODO)
--- ---
## Phase 2: Translation Files Structure ## Phase 2: Translation Files Structure ✅ COMPLETED
### 2.1 Directory Structure ### 2.1 Directory Structure
``` ```
@@ -141,33 +153,34 @@ locales/
--- ---
## Phase 3: Custom Hooks ## Phase 3: Custom Hooks ✅ COMPLETED
### 3.1 useTranslation Hook ### 3.1 useTranslation Hook
**File**: `hooks/useTranslation.ts` (NEW) **File**: `hooks/useTranslation.ts` (CREATED)
- Re-export from react-i18next with type safety - Re-export from react-i18next with type safety
- Custom hook for easier usage - Custom hook for easier usage
### 3.2 useLocale Hook ### 3.2 useLocale Hook
**File**: `hooks/useLocale.ts` (NEW) **File**: `hooks/useLocale.ts` (CREATED)
- Get current locale - Get current locale
- Change locale - Change locale
- Get available locales - Get available locales
- Get locale display name - Get locale display name
- ✅ Measurement system management
### 3.3 useFormatting Hook ### 3.3 useFormatting Hook
**File**: `hooks/useFormatting.ts` (NEW) **File**: `hooks/useFormatting.ts` (CREATED)
- Format dates based on locale - Format dates based on locale
- Format numbers based on locale - Format numbers based on locale
- Format currency based on locale - Format currency based on locale
- Format units based on preference - Format units based on preference
--- ---
## Phase 4: Measurement Unit Preference ## Phase 4: Measurement Unit Preference ⏳ IN PROGRESS
### 4.1 Backend Schema Update ### 4.1 Backend Schema Update
**File**: `src/database/migrations/V0XX_add_measurement_preference.sql` (NEW) **File**: `src/database/migrations/V0XX_add_measurement_preference.sql` (NEW)
@@ -200,10 +213,10 @@ Add optional `measurementUnit` field.
--- ---
## Phase 5: Frontend User Preferences ## Phase 5: Frontend User Preferences ✅ PARTIALLY COMPLETED
### 5.1 Redux State ### 5.1 Redux State
**File**: `store/slices/userSlice.ts` (MODIFY) **File**: `store/slices/userSlice.ts` (TODO)
Add to user state: Add to user state:
```typescript ```typescript
@@ -214,28 +227,28 @@ interface UserState {
} }
``` ```
### 5.2 Language Selector Component ### 5.2 Language Selector Component
**File**: `components/settings/LanguageSelector.tsx` (NEW) **File**: `components/settings/LanguageSelector.tsx` (CREATED)
Features: Features:
- Dropdown with 5 language options - Dropdown with 5 language options
- Flag icons for each language - ✅ Shows language names in native script
- Updates user preference on change - Updates user preference on change
- Persists to backend - Persists to backend (pending backend schema)
- Updates i18n instance - Updates i18n instance
### 5.3 Measurement Unit Selector Component ### 5.3 Measurement Unit Selector Component
**File**: `components/settings/MeasurementUnitSelector.tsx` (NEW) **File**: `components/settings/MeasurementUnitSelector.tsx` (CREATED)
Features: Features:
- Toggle or dropdown (Metric/Imperial) - ✅ Dropdown (Metric/Imperial)
- Updates user preference on change - Updates user preference on change
- Persists to backend - Persists to backend (pending backend schema)
- Shows example conversions - Shows unit examples
--- ---
## Phase 6: Onboarding Flow Integration ## Phase 6: Onboarding Flow Integration ❌ TODO
### 6.1 Update Onboarding Page ### 6.1 Update Onboarding Page
**File**: `app/(auth)/onboarding/page.tsx` (MODIFY) **File**: `app/(auth)/onboarding/page.tsx` (MODIFY)
@@ -266,47 +279,47 @@ Include language and measurementUnit in profile update call.
--- ---
## Phase 7: Settings Page Integration ## Phase 7: Settings Page Integration ✅ COMPLETED
### 7.1 Update Settings Page ### 7.1 Update Settings Page
**File**: `app/settings/page.tsx` (MODIFY) **File**: `app/settings/page.tsx` (MODIFIED)
Add new sections: Add new sections:
- **Language & Region** section - **Preferences** section
- Language selector - Language selector
- Measurement unit selector - Measurement unit selector
### 7.2 Settings UI Components ### 7.2 Settings UI Components
**Files**: **Files**:
- `components/settings/LanguageSettings.tsx` (NEW) - `components/settings/LanguageSelector.tsx` (CREATED)
- `components/settings/MeasurementSettings.tsx` (NEW) - `components/settings/MeasurementUnitSelector.tsx` (CREATED)
--- ---
## Phase 8: Unit Conversion Utilities ## Phase 8: Unit Conversion Utilities ✅ COMPLETED
### 8.1 Conversion Utility ### 8.1 Conversion Utility
**File**: `lib/units/conversions.ts` (NEW) **File**: `lib/utils/unitConversion.ts` (CREATED)
Functions: Functions:
- `mlToOz(ml: number): number` - `convertWeight` / `convertWeightToKg`
- `ozToMl(oz: number): number` - `convertHeight` / `convertHeightToCm`
- `kgToLb(kg: number): number` - `convertTemperature` / `convertTemperatureToCelsius`
- `lbToKg(lb: number): number` - `convertVolume` / `convertVolumeToMl`
- `cmToIn(cm: number): number` - `getUnitSymbol`
- `inToCm(in: number): number` - `getConversionFactor`
### 8.2 Format Unit Hook ### 8.2 Format Unit Hook
**File**: `hooks/useUnitFormat.ts` (NEW) **File**: `hooks/useFormatting.ts` (CREATED)
- Get user's measurement preference - Get user's measurement preference
- Format value with correct unit - Format value with correct unit
- Convert between units - Convert between units
- Display with locale-specific formatting - Display with locale-specific formatting
--- ---
## Phase 9: Apply Localization Throughout App ## Phase 9: Apply Localization Throughout App ❌ TODO
### 9.1 Update All Pages ### 9.1 Update All Pages
Replace hardcoded strings with translation keys: Replace hardcoded strings with translation keys:

View File

@@ -91,6 +91,7 @@ export class User {
notifications?: boolean; notifications?: boolean;
emailUpdates?: boolean; emailUpdates?: boolean;
darkMode?: boolean; darkMode?: boolean;
measurementUnit?: 'metric' | 'imperial';
}; };
@CreateDateColumn({ name: 'created_at' }) @CreateDateColumn({ name: 'created_at' })

View File

@@ -24,6 +24,7 @@ import { RegisterDto } from './dto/register.dto';
import { LoginDto } from './dto/login.dto'; import { LoginDto } from './dto/login.dto';
import { RefreshTokenDto } from './dto/refresh-token.dto'; import { RefreshTokenDto } from './dto/refresh-token.dto';
import { LogoutDto } from './dto/logout.dto'; import { LogoutDto } from './dto/logout.dto';
import { UpdateProfileDto } from './dto/update-profile.dto';
import { import {
RequestPasswordResetDto, RequestPasswordResetDto,
ResetPasswordDto, ResetPasswordDto,
@@ -88,7 +89,7 @@ export class AuthController {
@HttpCode(HttpStatus.OK) @HttpCode(HttpStatus.OK)
async updateProfile( async updateProfile(
@CurrentUser() user: any, @CurrentUser() user: any,
@Body() updateData: { name?: string }, @Body() updateData: UpdateProfileDto,
) { ) {
return await this.authService.updateProfile(user.userId, updateData); return await this.authService.updateProfile(user.userId, updateData);
} }

View File

@@ -0,0 +1,33 @@
import { IsString, IsOptional, IsObject, ValidateNested, IsIn } from 'class-validator';
import { Type } from 'class-transformer';
export class UserPreferencesDto {
@IsOptional()
notifications?: boolean;
@IsOptional()
emailUpdates?: boolean;
@IsOptional()
darkMode?: boolean;
@IsOptional()
@IsIn(['metric', 'imperial'])
measurementUnit?: 'metric' | 'imperial';
}
export class UpdateProfileDto {
@IsOptional()
@IsString()
name?: string;
@IsOptional()
@IsString()
locale?: string;
@IsOptional()
@IsObject()
@ValidateNested()
@Type(() => UserPreferencesDto)
preferences?: UserPreferencesDto;
}