Add comprehensive i18n localization for KMM and iOS
KMM (Android/Shared): - Add strings.xml with 200+ localized strings - Add translation files for es, fr, de, pt languages - Update all screens to use stringResource() for i18n - Add Accept-Language header to API client for all platforms iOS: - Add L10n.swift helper with type-safe string accessors - Add Localizable.xcstrings with translations for all 5 languages - Update all SwiftUI views to use L10n.* for localized strings - Localize Auth, Residence, Task, Contractor, Document, and Profile views Supported languages: English, Spanish, French, German, Portuguese 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude <noreply@anthropic.com>
This commit is contained in:
@@ -12,7 +12,7 @@ struct JoinResidenceView: View {
|
||||
NavigationView {
|
||||
Form {
|
||||
Section {
|
||||
TextField("Share Code", text: $shareCode)
|
||||
TextField(L10n.Residences.shareCode, text: $shareCode)
|
||||
.textInputAutocapitalization(.characters)
|
||||
.autocorrectionDisabled()
|
||||
.onChange(of: shareCode) { newValue in
|
||||
@@ -25,9 +25,9 @@ struct JoinResidenceView: View {
|
||||
}
|
||||
.disabled(viewModel.isLoading)
|
||||
} header: {
|
||||
Text("Enter Share Code")
|
||||
Text(L10n.Residences.enterShareCode)
|
||||
} footer: {
|
||||
Text("Enter the 6-character code shared with you to join a residence")
|
||||
Text(L10n.Residences.shareCodeFooter)
|
||||
.foregroundColor(Color.appTextSecondary)
|
||||
}
|
||||
.listRowBackground(Color.appBackgroundSecondary)
|
||||
@@ -48,7 +48,7 @@ struct JoinResidenceView: View {
|
||||
ProgressView()
|
||||
.progressViewStyle(CircularProgressViewStyle())
|
||||
} else {
|
||||
Text("Join Residence")
|
||||
Text(L10n.Residences.joinButton)
|
||||
.fontWeight(.semibold)
|
||||
}
|
||||
Spacer()
|
||||
@@ -61,11 +61,11 @@ struct JoinResidenceView: View {
|
||||
.listStyle(.plain)
|
||||
.scrollContentBackground(.hidden)
|
||||
.background(Color.appBackgroundPrimary)
|
||||
.navigationTitle("Join Residence")
|
||||
.navigationTitle(L10n.Residences.joinTitle)
|
||||
.navigationBarTitleDisplayMode(.inline)
|
||||
.toolbar {
|
||||
ToolbarItem(placement: .navigationBarLeading) {
|
||||
Button("Cancel") {
|
||||
Button(L10n.Common.cancel) {
|
||||
dismiss()
|
||||
}
|
||||
.disabled(viewModel.isLoading)
|
||||
@@ -76,7 +76,7 @@ struct JoinResidenceView: View {
|
||||
|
||||
private func joinResidence() {
|
||||
guard shareCode.count == 6 else {
|
||||
viewModel.errorMessage = "Share code must be 6 characters"
|
||||
viewModel.errorMessage = L10n.Residences.shareCodeMust6
|
||||
return
|
||||
}
|
||||
|
||||
|
||||
@@ -43,7 +43,7 @@ struct ManageUsersView: View {
|
||||
|
||||
// Users list
|
||||
VStack(alignment: .leading, spacing: 12) {
|
||||
Text("Users (\(users.count))")
|
||||
Text("\(L10n.Residences.users) (\(users.count))")
|
||||
.font(.headline)
|
||||
.padding(.horizontal)
|
||||
|
||||
@@ -66,11 +66,11 @@ struct ManageUsersView: View {
|
||||
.listStyle(.plain)
|
||||
.scrollContentBackground(.hidden)
|
||||
.background(Color.appBackgroundPrimary)
|
||||
.navigationTitle("Manage Users")
|
||||
.navigationTitle(L10n.Residences.manageUsers)
|
||||
.navigationBarTitleDisplayMode(.inline)
|
||||
.toolbar {
|
||||
ToolbarItem(placement: .navigationBarTrailing) {
|
||||
Button("Close") {
|
||||
Button(L10n.Common.close) {
|
||||
dismiss()
|
||||
}
|
||||
}
|
||||
|
||||
@@ -54,35 +54,35 @@ struct ResidenceDetailView: View {
|
||||
leadingToolbar
|
||||
trailingToolbar
|
||||
}
|
||||
|
||||
|
||||
// MARK: Alerts
|
||||
.alert("Generate Report", isPresented: $showReportConfirmation) {
|
||||
Button("Cancel", role: .cancel) {
|
||||
.alert(L10n.Residences.generateReport, isPresented: $showReportConfirmation) {
|
||||
Button(L10n.Common.cancel, role: .cancel) {
|
||||
showReportConfirmation = false
|
||||
}
|
||||
Button("Generate") {
|
||||
Button(L10n.Residences.generate) {
|
||||
viewModel.generateTasksReport(residenceId: residenceId, email: "")
|
||||
showReportConfirmation = false
|
||||
}
|
||||
} message: {
|
||||
Text("This will generate a comprehensive report of your property including all tasks, documents, and contractors.")
|
||||
Text(L10n.Residences.generateReportMessage)
|
||||
}
|
||||
|
||||
.alert("Delete Residence", isPresented: $showDeleteConfirmation) {
|
||||
Button("Cancel", role: .cancel) { }
|
||||
|
||||
.alert(L10n.Residences.deleteTitle, isPresented: $showDeleteConfirmation) {
|
||||
Button(L10n.Common.cancel, role: .cancel) { }
|
||||
.accessibilityIdentifier(AccessibilityIdentifiers.Alert.cancelButton)
|
||||
Button("Delete", role: .destructive) {
|
||||
Button(L10n.Common.delete, role: .destructive) {
|
||||
deleteResidence()
|
||||
}
|
||||
.accessibilityIdentifier(AccessibilityIdentifiers.Alert.deleteButton)
|
||||
} message: {
|
||||
if let residence = viewModel.selectedResidence {
|
||||
Text("Are you sure you want to delete \(residence.name)? This action cannot be undone and will delete all associated tasks, documents, and data.")
|
||||
Text("\(L10n.Residences.deleteConfirmMessage)")
|
||||
}
|
||||
}
|
||||
|
||||
.alert("Maintenance Report", isPresented: $showReportAlert) {
|
||||
Button("OK", role: .cancel) { }
|
||||
|
||||
.alert(L10n.Residences.maintenanceReport, isPresented: $showReportAlert) {
|
||||
Button(L10n.Common.ok, role: .cancel) { }
|
||||
} message: {
|
||||
Text(viewModel.reportMessage ?? "")
|
||||
}
|
||||
@@ -189,7 +189,7 @@ private extension ResidenceDetailView {
|
||||
var loadingView: some View {
|
||||
VStack(spacing: 16) {
|
||||
ProgressView()
|
||||
Text("Loading residence...")
|
||||
Text(L10n.Residences.loadingResidence)
|
||||
.font(.subheadline)
|
||||
.foregroundColor(Color.appTextSecondary)
|
||||
}
|
||||
@@ -226,9 +226,9 @@ private extension ResidenceDetailView {
|
||||
reloadTasks: { loadResidenceTasks() }
|
||||
)
|
||||
} else if isLoadingTasks {
|
||||
ProgressView("Loading tasks...")
|
||||
ProgressView(L10n.Residences.loadingTasks)
|
||||
} else if let tasksError = tasksError {
|
||||
Text("Error loading tasks: \(tasksError)")
|
||||
Text("\(L10n.Residences.errorLoadingTasks): \(tasksError)")
|
||||
.foregroundColor(Color.appError)
|
||||
.padding()
|
||||
}
|
||||
@@ -242,7 +242,7 @@ private extension ResidenceDetailView {
|
||||
Image(systemName: "person.2.fill")
|
||||
.font(.title2)
|
||||
.foregroundColor(Color.appPrimary)
|
||||
Text("Contractors")
|
||||
Text(L10n.Residences.contractors)
|
||||
.font(.title2.weight(.bold))
|
||||
.foregroundColor(Color.appPrimary)
|
||||
}
|
||||
@@ -256,7 +256,7 @@ private extension ResidenceDetailView {
|
||||
}
|
||||
.padding()
|
||||
} else if let error = contractorsError {
|
||||
Text("Error: \(error)")
|
||||
Text("\(L10n.Common.error): \(error)")
|
||||
.foregroundColor(Color.appError)
|
||||
.padding()
|
||||
} else if contractors.isEmpty {
|
||||
@@ -265,10 +265,10 @@ private extension ResidenceDetailView {
|
||||
Image(systemName: "person.crop.circle.badge.plus")
|
||||
.font(.system(size: 48))
|
||||
.foregroundColor(Color.appTextSecondary.opacity(0.6))
|
||||
Text("No contractors yet")
|
||||
Text(L10n.Residences.noContractors)
|
||||
.font(.headline)
|
||||
.foregroundColor(Color.appTextPrimary)
|
||||
Text("Add contractors from the Contractors tab")
|
||||
Text(L10n.Residences.addContractorsPrompt)
|
||||
.font(.subheadline)
|
||||
.foregroundColor(Color.appTextSecondary)
|
||||
}
|
||||
@@ -303,7 +303,7 @@ private extension ResidenceDetailView {
|
||||
var leadingToolbar: some ToolbarContent {
|
||||
ToolbarItem(placement: .navigationBarLeading) {
|
||||
if viewModel.selectedResidence != nil {
|
||||
Button("Edit") {
|
||||
Button(L10n.Common.edit) {
|
||||
showEditResidence = true
|
||||
}
|
||||
.accessibilityIdentifier(AccessibilityIdentifiers.Residence.editButton)
|
||||
|
||||
@@ -43,7 +43,7 @@ struct ResidencesListView: View {
|
||||
})
|
||||
}
|
||||
}
|
||||
.navigationTitle("My Properties")
|
||||
.navigationTitle(L10n.Residences.title)
|
||||
.navigationBarTitleDisplayMode(.inline)
|
||||
.toolbar {
|
||||
ToolbarItemGroup(placement: .navigationBarTrailing) {
|
||||
@@ -134,10 +134,10 @@ private struct ResidencesContent: View {
|
||||
// Properties Header
|
||||
HStack {
|
||||
VStack(alignment: .leading, spacing: AppSpacing.xxs) {
|
||||
Text("Your Properties")
|
||||
Text(L10n.Residences.yourProperties)
|
||||
.font(.title3.weight(.semibold))
|
||||
.foregroundColor(Color.appTextPrimary)
|
||||
Text("\(residences.count) \(residences.count == 1 ? "property" : "properties")")
|
||||
Text("\(residences.count) \(residences.count == 1 ? L10n.Residences.property : L10n.Residences.properties)")
|
||||
.font(.callout)
|
||||
.foregroundColor(Color.appTextSecondary)
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user