Files
honeyDueAPI/docs/# Full Localization Plan for Casera.md
Trey t c17e85c14e Add comprehensive i18n localization support
- Add go-i18n package for internationalization
- Create i18n middleware to extract Accept-Language header
- Add translation files for en, es, fr, de, pt languages
- Localize all handler error messages and responses
- Add language context to all API handlers

Supported languages: English, Spanish, French, German, Portuguese

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude <noreply@anthropic.com>
2025-12-02 02:01:47 -06:00

13 KiB

Full Localization Plan for Casera

Overview

Complete localization of the Casera 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

myCribAPI-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

MyCribKMM/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 casera.composeapp.generated.resources.Res
import casera.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:

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)

  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:

<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

  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