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:
Trey t
2025-12-02 02:02:00 -06:00
parent e62e7d4371
commit c726320c1e
59 changed files with 19839 additions and 757 deletions

View File

@@ -51,7 +51,7 @@ struct CompleteTaskView: View {
}
}
} header: {
Text("Task Details")
Text(L10n.Tasks.taskDetails)
}
.listRowBackground(Color.appBackgroundSecondary)
@@ -61,7 +61,7 @@ struct CompleteTaskView: View {
showContractorPicker = true
}) {
HStack {
Label("Select Contractor", systemImage: "wrench.and.screwdriver")
Label(L10n.Tasks.selectContractor, systemImage: "wrench.and.screwdriver")
.foregroundStyle(.primary)
Spacer()
@@ -77,7 +77,7 @@ struct CompleteTaskView: View {
}
}
} else {
Text("None")
Text(L10n.Tasks.none)
.foregroundStyle(.tertiary)
}
@@ -87,20 +87,20 @@ struct CompleteTaskView: View {
}
}
} header: {
Text("Contractor (Optional)")
Text(L10n.Tasks.contractorOptional)
} footer: {
Text("Select a contractor if they completed this work, or leave blank for manual entry.")
Text(L10n.Tasks.contractorHelper)
}
.listRowBackground(Color.appBackgroundSecondary)
// Completion Details Section
Section {
LabeledContent {
TextField("Your name", text: $completedByName)
TextField(L10n.Tasks.yourName, text: $completedByName)
.multilineTextAlignment(.trailing)
.disabled(selectedContractor != nil)
} label: {
Label("Completed By", systemImage: "person")
Label(L10n.Tasks.completedBy, systemImage: "person")
}
LabeledContent {
@@ -113,19 +113,19 @@ struct CompleteTaskView: View {
}
.padding(.leading, 12)
} label: {
Label("Actual Cost", systemImage: "dollarsign.circle")
Label(L10n.Tasks.actualCost, systemImage: "dollarsign.circle")
}
} header: {
Text("Optional Information")
Text(L10n.Tasks.optionalInfo)
} footer: {
Text("Add any additional details about completing this task.")
Text(L10n.Tasks.optionalDetails)
}
.listRowBackground(Color.appBackgroundSecondary)
// Notes Section
Section {
VStack(alignment: .leading, spacing: 8) {
Label("Notes", systemImage: "note.text")
Label(L10n.Tasks.notes, systemImage: "note.text")
.font(.subheadline)
.foregroundStyle(.secondary)
@@ -134,7 +134,7 @@ struct CompleteTaskView: View {
.scrollContentBackground(.hidden)
}
} footer: {
Text("Optional notes about the work completed.")
Text(L10n.Tasks.optionalNotes)
}
.listRowBackground(Color.appBackgroundSecondary)
@@ -142,7 +142,7 @@ struct CompleteTaskView: View {
Section {
VStack(spacing: 12) {
HStack {
Label("Quality Rating", systemImage: "star")
Label(L10n.Tasks.qualityRating, systemImage: "star")
.font(.subheadline)
Spacer()
@@ -168,7 +168,7 @@ struct CompleteTaskView: View {
.frame(maxWidth: .infinity)
}
} footer: {
Text("Rate the quality of work from 1 to 5 stars.")
Text(L10n.Tasks.rateQuality)
}
.listRowBackground(Color.appBackgroundSecondary)
@@ -179,7 +179,7 @@ struct CompleteTaskView: View {
Button(action: {
showCamera = true
}) {
Label("Take Photo", systemImage: "camera")
Label(L10n.Tasks.takePhoto, systemImage: "camera")
.frame(maxWidth: .infinity)
.foregroundStyle(Color.appPrimary)
}
@@ -191,7 +191,7 @@ struct CompleteTaskView: View {
matching: .images,
photoLibrary: .shared()
) {
Label("Library", systemImage: "photo.on.rectangle.angled")
Label(L10n.Tasks.library, systemImage: "photo.on.rectangle.angled")
.frame(maxWidth: .infinity)
.foregroundStyle(Color.appPrimary)
}
@@ -230,9 +230,9 @@ struct CompleteTaskView: View {
}
}
} header: {
Text("Photos (\(selectedImages.count)/5)")
Text("\(L10n.Tasks.photos) (\(selectedImages.count)/5)")
} footer: {
Text("Add up to 5 photos documenting the completed work.")
Text(L10n.Tasks.addPhotos)
}
.listRowBackground(Color.appBackgroundSecondary)
@@ -244,7 +244,7 @@ struct CompleteTaskView: View {
ProgressView()
.tint(.white)
} else {
Label("Complete Task", systemImage: "checkmark.circle.fill")
Label(L10n.Tasks.completeTask, systemImage: "checkmark.circle.fill")
}
}
.frame(maxWidth: .infinity)
@@ -258,17 +258,17 @@ struct CompleteTaskView: View {
.listStyle(.plain)
.scrollContentBackground(.hidden)
.background(Color.appBackgroundPrimary)
.navigationTitle("Complete Task")
.navigationTitle(L10n.Tasks.completeTask)
.navigationBarTitleDisplayMode(.inline)
.toolbar {
ToolbarItem(placement: .cancellationAction) {
Button("Cancel") {
Button(L10n.Common.cancel) {
dismiss()
}
}
}
.alert("Error", isPresented: $showError) {
Button("OK", role: .cancel) {}
.alert(L10n.Tasks.error, isPresented: $showError) {
Button(L10n.Common.ok, role: .cancel) {}
} message: {
Text(errorMessage)
}
@@ -386,9 +386,9 @@ struct ContractorPickerView: View {
}) {
HStack {
VStack(alignment: .leading) {
Text("None (Manual Entry)")
Text(L10n.Tasks.noneManual)
.foregroundStyle(.primary)
Text("Enter name manually")
Text(L10n.Tasks.enterManually)
.font(.caption)
.foregroundStyle(.secondary)
}
@@ -450,11 +450,11 @@ struct ContractorPickerView: View {
}
}
}
.navigationTitle("Select Contractor")
.navigationTitle(L10n.Tasks.selectContractor)
.navigationBarTitleDisplayMode(.inline)
.toolbar {
ToolbarItem(placement: .cancellationAction) {
Button("Cancel") {
Button(L10n.Common.cancel) {
dismiss()
}
}