i18n: complete app-wide localization (10 languages) + audit tooling
Android UI Tests / ui-tests (push) Has been cancelled
Android UI Tests / ui-tests (push) Has been cancelled
Localize all user-facing strings across iOS (SwiftUI), shared Kotlin, and Android Compose into en/es/fr/de/pt/it/ja/ko/nl/zh: - iOS String Catalogs: main + widget Localizable.xcstrings, InfoPlist.xcstrings (permissions), plural variations, ~200 new keys translated - Shared Kotlin ClientStrings table + Android composeResources/values-* (884 keys ×10), routed Api/ViewModel/util error & UI strings through localization - Backend-localized lookups/suggestions consumed via display names - Widget extension catalog; theme names, home-profile fallbacks, validation, network errors, accessibility labels all localized Add re-runnable verification gates: - scripts/i18n_audit.py — enumerate every literal, partition to GAP=0 - scripts/i18n_coverage.py — all 10 locales translated, format-specifier parity Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
This commit is contained in:
@@ -10,8 +10,13 @@ import kotlinx.serialization.Serializable
|
||||
@Serializable
|
||||
data class ResidenceType(
|
||||
val id: Int,
|
||||
val name: String
|
||||
)
|
||||
val name: String,
|
||||
@SerialName("display_name") val displayNameLocalized: String = ""
|
||||
) {
|
||||
/** Localized label for the current locale; falls back to [name]. */
|
||||
val displayName: String
|
||||
get() = displayNameLocalized.ifBlank { name }
|
||||
}
|
||||
|
||||
/**
|
||||
* Task frequency lookup - matching Go API TaskFrequencyResponse
|
||||
@@ -20,12 +25,13 @@ data class ResidenceType(
|
||||
data class TaskFrequency(
|
||||
val id: Int,
|
||||
val name: String,
|
||||
@SerialName("display_name") val displayNameLocalized: String = "",
|
||||
val days: Int? = null,
|
||||
@SerialName("display_order") val displayOrder: Int = 0
|
||||
) {
|
||||
// Helper for display
|
||||
/** Localized label for the current locale; falls back to [name]. */
|
||||
val displayName: String
|
||||
get() = name
|
||||
get() = displayNameLocalized.ifBlank { name }
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -35,13 +41,14 @@ data class TaskFrequency(
|
||||
data class TaskPriority(
|
||||
val id: Int,
|
||||
val name: String,
|
||||
@SerialName("display_name") val displayNameLocalized: String = "",
|
||||
val level: Int = 0,
|
||||
val color: String = "",
|
||||
@SerialName("display_order") val displayOrder: Int = 0
|
||||
) {
|
||||
// Helper for display
|
||||
/** Localized label for the current locale; falls back to [name]. */
|
||||
val displayName: String
|
||||
get() = name
|
||||
get() = displayNameLocalized.ifBlank { name }
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -51,11 +58,16 @@ data class TaskPriority(
|
||||
data class TaskCategory(
|
||||
val id: Int,
|
||||
val name: String,
|
||||
@SerialName("display_name") val displayNameLocalized: String = "",
|
||||
val description: String = "",
|
||||
val icon: String = "",
|
||||
val color: String = "",
|
||||
@SerialName("display_order") val displayOrder: Int = 0
|
||||
)
|
||||
) {
|
||||
/** Localized label for the current locale; falls back to [name]. */
|
||||
val displayName: String
|
||||
get() = displayNameLocalized.ifBlank { name }
|
||||
}
|
||||
|
||||
/**
|
||||
* Contractor specialty lookup
|
||||
@@ -64,9 +76,25 @@ data class TaskCategory(
|
||||
data class ContractorSpecialty(
|
||||
val id: Int,
|
||||
val name: String,
|
||||
@SerialName("display_name") val displayNameLocalized: String = "",
|
||||
val description: String? = null,
|
||||
val icon: String? = null,
|
||||
@SerialName("display_order") val displayOrder: Int = 0
|
||||
) {
|
||||
/** Localized label for the current locale; falls back to [name]. */
|
||||
val displayName: String
|
||||
get() = displayNameLocalized.ifBlank { name }
|
||||
}
|
||||
|
||||
/**
|
||||
* A selectable home-profile field option (heating type, roof type, etc.),
|
||||
* served localized by the backend. [value] is the stable code stored on the
|
||||
* residence; [displayName] is the localized label.
|
||||
*/
|
||||
@Serializable
|
||||
data class HomeProfileOption(
|
||||
val value: String,
|
||||
@SerialName("display_name") val displayName: String
|
||||
)
|
||||
|
||||
/**
|
||||
@@ -103,7 +131,10 @@ data class SeededDataResponse(
|
||||
@SerialName("task_priorities") val taskPriorities: List<TaskPriority>,
|
||||
@SerialName("task_frequencies") val taskFrequencies: List<TaskFrequency>,
|
||||
@SerialName("contractor_specialties") val contractorSpecialties: List<ContractorSpecialty>,
|
||||
@SerialName("task_templates") val taskTemplates: TaskTemplatesGroupedResponse
|
||||
@SerialName("task_templates") val taskTemplates: TaskTemplatesGroupedResponse,
|
||||
@SerialName("home_profile_options") val homeProfileOptions: Map<String, List<HomeProfileOption>> = emptyMap(),
|
||||
@SerialName("document_types") val documentTypes: List<HomeProfileOption> = emptyList(),
|
||||
@SerialName("document_categories") val documentCategories: List<HomeProfileOption> = emptyList()
|
||||
)
|
||||
|
||||
// Legacy wrapper responses for backward compatibility
|
||||
|
||||
Reference in New Issue
Block a user