# Full Localization Plan for honeyDue ## Overview Complete localization of the honeyDue property management app across three codebases: - **Go API** - Server-side localization of errors, emails, push notifications, lookup data - **KMM/Android** - Compose Multiplatform string resources - **iOS** - Apple String Catalogs (.xcstrings) **Target Languages**: English (base), Spanish, French, German, Portuguese (extensible) **Key Decisions**: - API returns localized strings (server-side translation) - Accept-Language header determines locale - Clients display what API returns for API content - Clients localize their own UI strings --- ## Part 1: Go API Localization ### 1.1 Add Dependency ```bash go get github.com/nicksnyder/go-i18n/v2 ``` ### 1.2 Directory Structure ``` honeyDueAPI-go/ ├── internal/ │ └── i18n/ │ ├── i18n.go # Core setup, bundle, T() helper │ ├── middleware.go # Gin middleware for Accept-Language │ └── translations/ │ ├── en.json # English (base) │ ├── es.json # Spanish │ ├── fr.json # French │ ├── de.json # German │ └── pt.json # Portuguese ├── templates/ │ └── emails/ │ ├── en/ │ │ ├── welcome.html │ │ ├── verification.html │ │ └── password_reset.html │ ├── es/ │ ├── fr/ │ ├── de/ │ └── pt/ ``` ### 1.3 Core Files to Create **internal/i18n/i18n.go**: - Initialize i18n bundle with embedded translation files - Provide `T(localizer, messageID, data)` helper function - Load all JSON translation files at startup **internal/i18n/middleware.go**: - Parse Accept-Language header - Match against supported languages (en, es, fr, de, pt) - Store localizer in Gin context - Provide `GetLocalizer(c)` helper ### 1.4 Translation File Format ```json { "error.invalid_credentials": "Invalid credentials", "error.username_taken": "Username already taken", "error.email_taken": "Email already registered", "error.not_authenticated": "Not authenticated", "error.task_not_found": "Task not found", "error.residence_access_denied": "You don't have access to this property", "message.logged_out": "Logged out successfully", "message.task_deleted": "Task deleted successfully", "push.task_due_soon.title": "Task Due Soon", "push.task_due_soon.body": "{{.TaskTitle}} is due {{.DueDate}}" } ``` ### 1.5 Handler Update Pattern ```go // Before c.JSON(400, gin.H{"error": "Invalid credentials"}) // After localizer := i18n.GetLocalizer(c) c.JSON(400, gin.H{"error": i18n.T(localizer, "error.invalid_credentials", nil)}) ``` ### 1.6 Database for Lookup Translations Add translation table for task categories, priorities, statuses, frequencies: ```sql CREATE TABLE lookup_translations ( id SERIAL PRIMARY KEY, table_name VARCHAR(50) NOT NULL, record_id INT NOT NULL, locale VARCHAR(10) NOT NULL, field_name VARCHAR(50) NOT NULL, translated_value TEXT NOT NULL, UNIQUE(table_name, record_id, locale, field_name) ); ``` Update lookup endpoints to return translated names based on locale. ### 1.7 Critical Go Files to Modify | File | Action | Description | |------|--------|-------------| | `internal/i18n/i18n.go` | CREATE | Core i18n bundle and helpers | | `internal/i18n/middleware.go` | CREATE | Locale detection middleware | | `internal/i18n/translations/*.json` | CREATE | Translation files (5 languages) | | `internal/router/router.go` | MODIFY | Add i18n middleware | | `internal/handlers/auth_handler.go` | MODIFY | Localize ~22 error strings | | `internal/handlers/task_handler.go` | MODIFY | Localize ~30 error strings | | `internal/handlers/residence_handler.go` | MODIFY | Localize ~20 error strings | | `internal/handlers/contractor_handler.go` | MODIFY | Localize ~15 error strings | | `internal/handlers/document_handler.go` | MODIFY | Localize ~15 error strings | | `internal/services/email_service.go` | MODIFY | Template-based emails | | `internal/services/notification_service.go` | MODIFY | Localized push content | | `internal/services/lookup_service.go` | MODIFY | Return translated lookups | | `templates/emails/**` | CREATE | Email templates per language | --- ## Part 2: KMM/Android Localization ### 2.1 Strategy Use Compose Multiplatform Resources (already in build.gradle.kts via `compose.components.resources`). ### 2.2 Directory Structure ``` HoneyDueKMM/composeApp/src/commonMain/ └── composeResources/ ├── values/ │ └── strings.xml # English (base) ├── values-es/ │ └── strings.xml # Spanish ├── values-fr/ │ └── strings.xml # French ├── values-de/ │ └── strings.xml # German └── values-pt/ └── strings.xml # Portuguese ``` ### 2.3 String Resource Format ```xml Sign In Manage your properties with ease Username or Email Password Sign In Forgot Password? My Properties No properties yet Add your first property to get started! Add Property Tasks Add New Task Overdue Due Soon Save Cancel Delete Loading… Something went wrong. Please try again. Back Close Add Property ``` ### 2.4 Usage in Compose ```kotlin import honeydue.composeapp.generated.resources.Res import honeydue.composeapp.generated.resources.* import org.jetbrains.compose.resources.stringResource @Composable fun LoginScreen() { Text(stringResource(Res.string.auth_login_title)) Button(onClick = { /* ... */ }) { Text(stringResource(Res.string.auth_login_button)) } } ``` ### 2.5 Critical KMM Files to Modify | File | Action | Description | |------|--------|-------------| | `composeResources/values/strings.xml` | CREATE | Base English strings (~500) | | `composeResources/values-es/strings.xml` | CREATE | Spanish translations | | `composeResources/values-fr/strings.xml` | CREATE | French translations | | `composeResources/values-de/strings.xml` | CREATE | German translations | | `composeResources/values-pt/strings.xml` | CREATE | Portuguese translations | | `ui/screens/LoginScreen.kt` | MODIFY | Replace hardcoded strings | | `ui/screens/ResidencesScreen.kt` | MODIFY | Replace hardcoded strings | | `ui/screens/TasksScreen.kt` | MODIFY | Replace hardcoded strings | | `ui/screens/ContractorsScreen.kt` | MODIFY | Replace hardcoded strings | | `ui/screens/DocumentsScreen.kt` | MODIFY | Replace hardcoded strings | | `ui/components/*.kt` | MODIFY | Replace hardcoded strings (33 files) | | All other screen files | MODIFY | Replace hardcoded strings | --- ## Part 3: iOS Localization ### 3.1 Strategy Use Apple String Catalogs (`.xcstrings`) - modern approach with Xcode visual editor. ### 3.2 Create String Catalog 1. Xcode: File > New > File > String Catalog 2. Name: `Localizable.xcstrings` 3. Add languages: English, Spanish, French, German, Portuguese ### 3.3 Type-Safe String Access Create `iosApp/iosApp/Helpers/L10n.swift`: ```swift import Foundation enum L10n { enum Auth { static let loginTitle = String(localized: "auth_login_title") static let loginSubtitle = String(localized: "auth_login_subtitle") static let loginUsernameLabel = String(localized: "auth_login_username_label") static let loginPasswordLabel = String(localized: "auth_login_password_label") static let loginButton = String(localized: "auth_login_button") static let forgotPassword = String(localized: "auth_forgot_password") } enum Properties { static let title = String(localized: "properties_title") static let emptyTitle = String(localized: "properties_empty_title") static let emptySubtitle = String(localized: "properties_empty_subtitle") static let addButton = String(localized: "properties_add_button") } enum Tasks { static let title = String(localized: "tasks_title") static let addTitle = String(localized: "tasks_add_title") static let columnOverdue = String(localized: "tasks_column_overdue") static let columnDueSoon = String(localized: "tasks_column_due_soon") } enum Common { static let save = String(localized: "common_save") static let cancel = String(localized: "common_cancel") static let delete = String(localized: "common_delete") static let loading = String(localized: "common_loading") static let errorGeneric = String(localized: "common_error_generic") } } ``` ### 3.4 Usage in SwiftUI ```swift // Before Text("My Properties") .navigationTitle("My Properties") // After Text(L10n.Properties.title) .navigationTitle(L10n.Properties.title) ``` ### 3.5 Critical iOS Files to Modify | File | Action | Description | |------|--------|-------------| | `iosApp/Localizable.xcstrings` | CREATE | String catalog with all translations | | `iosApp/Helpers/L10n.swift` | CREATE | Type-safe string access | | `Login/LoginView.swift` | MODIFY | Replace ~7 hardcoded strings | | `Login/LoginViewModel.swift` | MODIFY | Replace ~12 error messages | | `Register/RegisterView.swift` | MODIFY | Replace ~10 hardcoded strings | | `Residence/*.swift` | MODIFY | Replace ~30 hardcoded strings | | `Task/*.swift` | MODIFY | Replace ~50 hardcoded strings | | `Contractor/*.swift` | MODIFY | Replace ~20 hardcoded strings | | `Document/*.swift` | MODIFY | Replace ~15 hardcoded strings | | `Profile/*.swift` | MODIFY | Replace ~20 hardcoded strings | | All other view files | MODIFY | Replace hardcoded strings | --- ## Part 4: Implementation Order ### Phase 1: Infrastructure (Do First) 1. **Go API**: - Add go-i18n dependency - Create `internal/i18n/` package with i18n.go and middleware.go - Create base `en.json` with all extractable strings - Add middleware to router - Test with curl using Accept-Language header 2. **KMM**: - Create `composeResources/values/strings.xml` with ~50 core strings - Verify Compose resources compile correctly - Update one screen (LoginScreen) as proof of concept 3. **iOS**: - Create `Localizable.xcstrings` in Xcode - Create `L10n.swift` helper - Add ~50 core strings - Update LoginView as proof of concept ### Phase 2: API Full Localization 1. Update all handlers to use localized errors 2. Add lookup_translations table and seed data 3. Update lookup service to return translated names 4. Move email templates to files, create Spanish versions 5. Update push notification service for localized content ### Phase 3: Mobile String Extraction **Order by feature (same for KMM and iOS)**: 1. Auth screens (Login, Register, Verify, Password Reset, Apple Sign In) 2. Property screens (List, Detail, Form, Join, Share, Manage Users) 3. Task screens (List, Detail, Form, Complete, Actions, Kanban) 4. Contractor screens (List, Detail, Form) 5. Document screens (List, Detail, Form, Warranties) 6. Profile screens (Profile, Settings, Notifications) 7. Common components (Dialogs, Cards, Empty States, Loading) ### Phase 4: Create Translation Files - Create es.json, fr.json, de.json, pt.json for Go API - Create values-es/, values-fr/, values-de/, values-pt/ for KMM - Add all language translations to iOS String Catalog ### Phase 5: Testing & Polish - Test all screens in each language - Verify email rendering in each language - Test push notifications - Verify lookup data translations - Handle edge cases (long strings, RTL future-proofing) --- ## String Naming Convention Use consistent keys across all platforms: ``` __ Examples: auth_login_title auth_login_button properties_list_empty_title properties_form_field_name tasks_detail_button_complete common_button_save common_button_cancel error_network_timeout a11y_button_back ``` --- ## Estimated String Counts | Platform | Approximate Strings | |----------|---------------------| | Go API - Errors | ~100 | | Go API - Email templates | ~50 per language | | Go API - Push notifications | ~20 | | Go API - Lookup data | ~50 | | KMM/Android | ~500 | | iOS | ~500 | | **Total unique strings** | ~700-800 | --- ## Translation Workflow 1. **Extract**: All English strings defined first 2. **Export**: JSON (Go), XML (Android), .xcstrings (iOS) 3. **Translate**: Use Lokalise, Crowdin, or manual translation 4. **Import**: Place translated files in correct locations 5. **Test**: Verify in each language