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:
@@ -57,7 +57,7 @@ struct ContractorFormSheet: View {
|
||||
Image(systemName: "person")
|
||||
.foregroundColor(Color.appPrimary)
|
||||
.frame(width: 24)
|
||||
TextField("Name", text: $name)
|
||||
TextField(L10n.Contractors.nameLabel, text: $name)
|
||||
.focused($focusedField, equals: .name)
|
||||
}
|
||||
|
||||
@@ -65,13 +65,13 @@ struct ContractorFormSheet: View {
|
||||
Image(systemName: "building.2")
|
||||
.foregroundColor(Color.appPrimary)
|
||||
.frame(width: 24)
|
||||
TextField("Company", text: $company)
|
||||
TextField(L10n.Contractors.companyLabel, text: $company)
|
||||
.focused($focusedField, equals: .company)
|
||||
}
|
||||
} header: {
|
||||
Text("Basic Information")
|
||||
Text(L10n.Contractors.basicInfoSection)
|
||||
} footer: {
|
||||
Text("Required: Name")
|
||||
Text(L10n.Contractors.basicInfoFooter)
|
||||
.font(.caption)
|
||||
.foregroundColor(Color.appError)
|
||||
}
|
||||
@@ -84,7 +84,7 @@ struct ContractorFormSheet: View {
|
||||
Image(systemName: "house")
|
||||
.foregroundColor(Color.appPrimary)
|
||||
.frame(width: 24)
|
||||
Text(selectedResidenceName ?? "Personal (No Residence)")
|
||||
Text(selectedResidenceName ?? L10n.Contractors.personalNoResidence)
|
||||
.foregroundColor(selectedResidenceName == nil ? Color.appTextSecondary.opacity(0.7) : Color.appTextPrimary)
|
||||
Spacer()
|
||||
Image(systemName: "chevron.down")
|
||||
@@ -93,11 +93,11 @@ struct ContractorFormSheet: View {
|
||||
}
|
||||
}
|
||||
} header: {
|
||||
Text("Residence (Optional)")
|
||||
Text(L10n.Contractors.residenceSection)
|
||||
} footer: {
|
||||
Text(selectedResidenceId == nil
|
||||
? "Only you will see this contractor"
|
||||
: "All users of \(selectedResidenceName ?? "") will see this contractor")
|
||||
? L10n.Contractors.residenceFooterPersonal
|
||||
: String(format: L10n.Contractors.residenceFooterShared, selectedResidenceName ?? ""))
|
||||
.font(.caption)
|
||||
}
|
||||
.listRowBackground(Color.appBackgroundSecondary)
|
||||
@@ -108,7 +108,7 @@ struct ContractorFormSheet: View {
|
||||
Image(systemName: "phone.fill")
|
||||
.foregroundColor(Color.appPrimary)
|
||||
.frame(width: 24)
|
||||
TextField("Phone", text: $phone)
|
||||
TextField(L10n.Contractors.phoneLabel, text: $phone)
|
||||
.keyboardType(.phonePad)
|
||||
.focused($focusedField, equals: .phone)
|
||||
}
|
||||
@@ -117,7 +117,7 @@ struct ContractorFormSheet: View {
|
||||
Image(systemName: "envelope.fill")
|
||||
.foregroundColor(Color.appAccent)
|
||||
.frame(width: 24)
|
||||
TextField("Email", text: $email)
|
||||
TextField(L10n.Contractors.emailLabel, text: $email)
|
||||
.keyboardType(.emailAddress)
|
||||
.textInputAutocapitalization(.never)
|
||||
.autocorrectionDisabled()
|
||||
@@ -128,14 +128,14 @@ struct ContractorFormSheet: View {
|
||||
Image(systemName: "globe")
|
||||
.foregroundColor(Color.appAccent)
|
||||
.frame(width: 24)
|
||||
TextField("Website", text: $website)
|
||||
TextField(L10n.Contractors.websiteLabel, text: $website)
|
||||
.keyboardType(.URL)
|
||||
.textInputAutocapitalization(.never)
|
||||
.autocorrectionDisabled()
|
||||
.focused($focusedField, equals: .website)
|
||||
}
|
||||
} header: {
|
||||
Text("Contact Information")
|
||||
Text(L10n.Contractors.contactInfoSection)
|
||||
}
|
||||
.listRowBackground(Color.appBackgroundSecondary)
|
||||
|
||||
@@ -147,7 +147,7 @@ struct ContractorFormSheet: View {
|
||||
.foregroundColor(Color.appPrimary)
|
||||
.frame(width: 24)
|
||||
if selectedSpecialtyIds.isEmpty {
|
||||
Text("Select Specialties")
|
||||
Text(L10n.Contractors.selectSpecialtiesPlaceholder)
|
||||
.foregroundColor(Color.appTextSecondary.opacity(0.5))
|
||||
} else {
|
||||
let selectedNames = specialties
|
||||
@@ -164,7 +164,7 @@ struct ContractorFormSheet: View {
|
||||
}
|
||||
}
|
||||
} header: {
|
||||
Text("Specialties")
|
||||
Text(L10n.Contractors.specialtiesSection)
|
||||
}
|
||||
.listRowBackground(Color.appBackgroundSecondary)
|
||||
|
||||
@@ -174,7 +174,7 @@ struct ContractorFormSheet: View {
|
||||
Image(systemName: "location.fill")
|
||||
.foregroundColor(Color.appError)
|
||||
.frame(width: 24)
|
||||
TextField("Street Address", text: $streetAddress)
|
||||
TextField(L10n.Contractors.streetAddressLabel, text: $streetAddress)
|
||||
.focused($focusedField, equals: .streetAddress)
|
||||
}
|
||||
|
||||
@@ -182,7 +182,7 @@ struct ContractorFormSheet: View {
|
||||
Image(systemName: "building.2.crop.circle")
|
||||
.foregroundColor(Color.appPrimary)
|
||||
.frame(width: 24)
|
||||
TextField("City", text: $city)
|
||||
TextField(L10n.Contractors.cityLabel, text: $city)
|
||||
.focused($focusedField, equals: .city)
|
||||
}
|
||||
|
||||
@@ -191,20 +191,20 @@ struct ContractorFormSheet: View {
|
||||
Image(systemName: "map")
|
||||
.foregroundColor(Color.appAccent)
|
||||
.frame(width: 24)
|
||||
TextField("State", text: $stateProvince)
|
||||
TextField(L10n.Contractors.stateLabel, text: $stateProvince)
|
||||
.focused($focusedField, equals: .stateProvince)
|
||||
}
|
||||
|
||||
Divider()
|
||||
.frame(height: 24)
|
||||
|
||||
TextField("ZIP", text: $postalCode)
|
||||
TextField(L10n.Contractors.zipLabel, text: $postalCode)
|
||||
.keyboardType(.numberPad)
|
||||
.focused($focusedField, equals: .postalCode)
|
||||
.frame(maxWidth: 100)
|
||||
}
|
||||
} header: {
|
||||
Text("Address")
|
||||
Text(L10n.Contractors.addressSection)
|
||||
}
|
||||
.listRowBackground(Color.appBackgroundSecondary)
|
||||
|
||||
@@ -221,9 +221,9 @@ struct ContractorFormSheet: View {
|
||||
.focused($focusedField, equals: .notes)
|
||||
}
|
||||
} header: {
|
||||
Text("Notes")
|
||||
Text(L10n.Contractors.notesSection)
|
||||
} footer: {
|
||||
Text("Private notes about this contractor")
|
||||
Text(L10n.Contractors.notesFooter)
|
||||
.font(.caption)
|
||||
}
|
||||
.listRowBackground(Color.appBackgroundSecondary)
|
||||
@@ -231,7 +231,7 @@ struct ContractorFormSheet: View {
|
||||
// Favorite
|
||||
Section {
|
||||
Toggle(isOn: $isFavorite) {
|
||||
Label("Mark as Favorite", systemImage: "star.fill")
|
||||
Label(L10n.Contractors.favoriteLabel, systemImage: "star.fill")
|
||||
.foregroundColor(isFavorite ? Color.appAccent : Color.appTextPrimary)
|
||||
}
|
||||
.tint(Color.appAccent)
|
||||
@@ -255,11 +255,11 @@ struct ContractorFormSheet: View {
|
||||
.listStyle(.plain)
|
||||
.scrollContentBackground(.hidden)
|
||||
.background(Color.appBackgroundPrimary)
|
||||
.navigationTitle(contractor == nil ? "Add Contractor" : "Edit Contractor")
|
||||
.navigationTitle(contractor == nil ? L10n.Contractors.addTitle : L10n.Contractors.editTitle)
|
||||
.navigationBarTitleDisplayMode(.inline)
|
||||
.toolbar {
|
||||
ToolbarItem(placement: .cancellationAction) {
|
||||
Button("Cancel") {
|
||||
Button(L10n.Common.cancel) {
|
||||
dismiss()
|
||||
}
|
||||
}
|
||||
@@ -269,7 +269,7 @@ struct ContractorFormSheet: View {
|
||||
if viewModel.isCreating || viewModel.isUpdating {
|
||||
ProgressView()
|
||||
} else {
|
||||
Text(contractor == nil ? "Add" : "Save")
|
||||
Text(contractor == nil ? L10n.Contractors.addButton : L10n.Common.save)
|
||||
.bold()
|
||||
}
|
||||
}
|
||||
@@ -305,7 +305,7 @@ struct ContractorFormSheet: View {
|
||||
showingResidencePicker = false
|
||||
}) {
|
||||
HStack {
|
||||
Text("Personal (No Residence)")
|
||||
Text(L10n.Contractors.personalNoResidence)
|
||||
.foregroundColor(Color.appTextPrimary)
|
||||
Spacer()
|
||||
if selectedResidenceId == nil {
|
||||
@@ -348,11 +348,11 @@ struct ContractorFormSheet: View {
|
||||
.listStyle(.plain)
|
||||
.scrollContentBackground(.hidden)
|
||||
.background(Color.appBackgroundPrimary)
|
||||
.navigationTitle("Select Residence")
|
||||
.navigationTitle(L10n.Contractors.selectResidence)
|
||||
.navigationBarTitleDisplayMode(.inline)
|
||||
.toolbar {
|
||||
ToolbarItem(placement: .confirmationAction) {
|
||||
Button("Done") {
|
||||
Button(L10n.Common.done) {
|
||||
showingResidencePicker = false
|
||||
}
|
||||
}
|
||||
@@ -390,16 +390,16 @@ struct ContractorFormSheet: View {
|
||||
.listStyle(.plain)
|
||||
.scrollContentBackground(.hidden)
|
||||
.background(Color.appBackgroundPrimary)
|
||||
.navigationTitle("Select Specialties")
|
||||
.navigationTitle(L10n.Contractors.selectSpecialties)
|
||||
.navigationBarTitleDisplayMode(.inline)
|
||||
.toolbar {
|
||||
ToolbarItem(placement: .cancellationAction) {
|
||||
Button("Clear") {
|
||||
Button(L10n.Contractors.clearAction) {
|
||||
selectedSpecialtyIds.removeAll()
|
||||
}
|
||||
}
|
||||
ToolbarItem(placement: .confirmationAction) {
|
||||
Button("Done") {
|
||||
Button(L10n.Common.done) {
|
||||
showingSpecialtyPicker = false
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user