Total rebrand across all Go API source files: - Go module path: casera-api -> honeydue-api - All imports updated (130+ files) - Docker: containers, images, networks renamed - Email templates: support email, noreply, icon URL - Domains: casera.app/mycrib.treytartt.com -> honeyDue.treytartt.com - Bundle IDs: com.tt.casera -> com.tt.honeyDue - IAP product IDs updated - Landing page, admin panel, config defaults - Seeds, CI workflows, Makefile, docs - Database table names preserved (no migration needed) Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
14 KiB
14 KiB
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
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
{
"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
// 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:
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 version="1.0" encoding="utf-8"?>
<resources>
<!-- Auth -->
<string name="auth_login_title">Sign In</string>
<string name="auth_login_subtitle">Manage your properties with ease</string>
<string name="auth_login_username_label">Username or Email</string>
<string name="auth_login_password_label">Password</string>
<string name="auth_login_button">Sign In</string>
<string name="auth_forgot_password">Forgot Password?</string>
<!-- Properties -->
<string name="properties_title">My Properties</string>
<string name="properties_empty_title">No properties yet</string>
<string name="properties_empty_subtitle">Add your first property to get started!</string>
<string name="properties_add_button">Add Property</string>
<!-- Tasks -->
<string name="tasks_title">Tasks</string>
<string name="tasks_add_title">Add New Task</string>
<string name="tasks_column_overdue">Overdue</string>
<string name="tasks_column_due_soon">Due Soon</string>
<!-- Common -->
<string name="common_save">Save</string>
<string name="common_cancel">Cancel</string>
<string name="common_delete">Delete</string>
<string name="common_loading">Loading…</string>
<string name="common_error_generic">Something went wrong. Please try again.</string>
<!-- Accessibility -->
<string name="a11y_back">Back</string>
<string name="a11y_close">Close</string>
<string name="a11y_add_property">Add Property</string>
</resources>
2.4 Usage in Compose
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
- Xcode: File > New > File > String Catalog
- Name:
Localizable.xcstrings - Add languages: English, Spanish, French, German, Portuguese
3.3 Type-Safe String Access
Create iosApp/iosApp/Helpers/L10n.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
// 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)
-
Go API:
- Add go-i18n dependency
- Create
internal/i18n/package with i18n.go and middleware.go - Create base
en.jsonwith all extractable strings - Add middleware to router
- Test with curl using Accept-Language header
-
KMM:
- Create
composeResources/values/strings.xmlwith ~50 core strings - Verify Compose resources compile correctly
- Update one screen (LoginScreen) as proof of concept
- Create
-
iOS:
- Create
Localizable.xcstringsin Xcode - Create
L10n.swifthelper - Add ~50 core strings
- Update LoginView as proof of concept
- Create
Phase 2: API Full Localization
- Update all handlers to use localized errors
- Add lookup_translations table and seed data
- Update lookup service to return translated names
- Move email templates to files, create Spanish versions
- Update push notification service for localized content
Phase 3: Mobile String Extraction
Order by feature (same for KMM and iOS):
- Auth screens (Login, Register, Verify, Password Reset, Apple Sign In)
- Property screens (List, Detail, Form, Join, Share, Manage Users)
- Task screens (List, Detail, Form, Complete, Actions, Kanban)
- Contractor screens (List, Detail, Form)
- Document screens (List, Detail, Form, Warranties)
- Profile screens (Profile, Settings, Notifications)
- 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:
<category>_<screen/feature>_<element>
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
- Extract: All English strings defined first
- Export: JSON (Go), XML (Android), .xcstrings (iOS)
- Translate: Use Lokalise, Crowdin, or manual translation
- Import: Place translated files in correct locations
- Test: Verify in each language