feat(i18n): complete internationalization for iOS + watchOS across all views
Migrate every hardcoded Text("...") string to the L10n / LocalizedStringResource
type-safe key system with full en/fr/de/es translations (4 languages).
iOS changes (TabataGo target):
- Strings.swift: ~90 new L10n keys across 13 groups (action, tab, home, zone,
level, programs, programDetail, player, profile, settings, policy, paywall,
health, complete, activity, onboarding, goal)
- Localizable.xcstrings: 145 → 245+ keys with fr/de/es translations
- Model enums: FitnessLevel.label & FitnessGoal.label changed from String to
LocalizedStringResource, backed by L10n.level/goal keys
- Component param types changed to LocalizedStringResource: StatBadge,
SectionHeader, ProfileRow, PolicySection, CompletionStat, FeatureRow,
OnboardingHeader, PrimaryButton, SelectionCard
- All 18 view files updated: HomeTab, ActivityTab, ProgramsTab, ProfileTab,
MainTabView, SettingsView, PolicyViews, CompletionView, BodyZoneView,
ProgramDetailView, PaywallView, OnboardingView, PlayerView
Watch changes (TabataGoWatch target):
- New Localizable.xcstrings: 23 keys with en/fr/de/es (phase labels, idle
state, activity rings, complication strings)
- New WatchL10n.swift: type-safe enum (needs manual Xcode target membership)
- Updated: WatchPlayerView, WatchIdleView, WatchActivityView,
TabataGoComplication (inline LocalizedStringResource for widget target)
Both iOS and watchOS targets build with zero errors.
This commit is contained in:
@@ -20,7 +20,7 @@ struct WatchActivityView: View {
|
||||
VStack(spacing: 14) {
|
||||
|
||||
// ── Rings ──────────────────────────────────────────
|
||||
Text("Today")
|
||||
Text(LocalizedStringResource("watch.activity.today"))
|
||||
.font(.system(size: 13, weight: .semibold))
|
||||
.foregroundStyle(.secondary)
|
||||
.frame(maxWidth: .infinity, alignment: .leading)
|
||||
@@ -29,15 +29,15 @@ struct WatchActivityView: View {
|
||||
RingView(progress: moveProgress,
|
||||
color: Color(red: 1.0, green: 0.23, blue: 0.19),
|
||||
icon: "flame.fill",
|
||||
label: "Move")
|
||||
label: LocalizedStringResource("watch.activity.move"))
|
||||
RingView(progress: exerciseProgress,
|
||||
color: .green,
|
||||
icon: "figure.run",
|
||||
label: "Exercise")
|
||||
label: LocalizedStringResource("watch.activity.exercise"))
|
||||
RingView(progress: standProgress,
|
||||
color: Color(red: 0.04, green: 0.80, blue: 0.97),
|
||||
icon: "figure.stand",
|
||||
label: "Stand")
|
||||
label: LocalizedStringResource("watch.activity.stand"))
|
||||
}
|
||||
|
||||
Divider()
|
||||
@@ -50,7 +50,7 @@ struct WatchActivityView: View {
|
||||
Text("\(streak) day\(streak == 1 ? "" : "s")")
|
||||
.font(.system(size: 14, weight: .bold, design: .rounded))
|
||||
Spacer()
|
||||
Text("streak")
|
||||
Text(LocalizedStringResource("watch.activity.streak"))
|
||||
.font(.system(size: 11))
|
||||
.foregroundStyle(.secondary)
|
||||
}
|
||||
@@ -123,7 +123,7 @@ private struct RingView: View {
|
||||
let progress: Double
|
||||
let color: Color
|
||||
let icon: String
|
||||
let label: String
|
||||
let label: LocalizedStringResource
|
||||
|
||||
var body: some View {
|
||||
VStack(spacing: 4) {
|
||||
|
||||
@@ -13,7 +13,7 @@ struct WatchIdleView: View {
|
||||
Text("TabataGo")
|
||||
.font(.system(size: 18, weight: .bold, design: .rounded))
|
||||
|
||||
Text("Start a workout\non your iPhone")
|
||||
Text(LocalizedStringResource("watch.idle.startOnPhone"))
|
||||
.font(.system(size: 13))
|
||||
.foregroundStyle(.secondary)
|
||||
.multilineTextAlignment(.center)
|
||||
@@ -23,7 +23,7 @@ struct WatchIdleView: View {
|
||||
Circle()
|
||||
.fill(.green)
|
||||
.frame(width: 6, height: 6)
|
||||
Text("Connected")
|
||||
Text(LocalizedStringResource("watch.idle.connected"))
|
||||
.font(.system(size: 11))
|
||||
.foregroundStyle(.secondary)
|
||||
}
|
||||
@@ -32,7 +32,7 @@ struct WatchIdleView: View {
|
||||
Circle()
|
||||
.fill(.gray)
|
||||
.frame(width: 6, height: 6)
|
||||
Text("No phone")
|
||||
Text(LocalizedStringResource("watch.idle.noPhone"))
|
||||
.font(.system(size: 11))
|
||||
.foregroundStyle(.secondary)
|
||||
}
|
||||
|
||||
@@ -92,23 +92,23 @@ struct WatchPlayerView: View {
|
||||
|
||||
private func watchPhaseColor(_ phase: WatchPhase) -> Color {
|
||||
switch phase {
|
||||
case .prep, .warmup: return .orange
|
||||
case .work: return Color(red: 1.0, green: 0.42, blue: 0.21)
|
||||
case .prep, .warmup: return .orange
|
||||
case .work: return Color(red: 1.0, green: 0.42, blue: 0.21)
|
||||
case .rest, .interBlockRest: return Color(red: 0.35, green: 0.78, blue: 0.98)
|
||||
case .cooldown: return .cyan
|
||||
case .complete: return .green
|
||||
case .cooldown: return .cyan
|
||||
case .complete: return .green
|
||||
}
|
||||
}
|
||||
|
||||
private func watchPhaseLabel(_ phase: WatchPhase) -> String {
|
||||
private func watchPhaseLabel(_ phase: WatchPhase) -> LocalizedStringResource {
|
||||
switch phase {
|
||||
case .prep: return "GET READY"
|
||||
case .warmup: return "WARM UP"
|
||||
case .work: return "WORK"
|
||||
case .rest: return "REST"
|
||||
case .interBlockRest: return "BREAK"
|
||||
case .cooldown: return "COOL DOWN"
|
||||
case .complete: return "DONE"
|
||||
case .prep: return LocalizedStringResource("watch.phase.getReady")
|
||||
case .warmup: return LocalizedStringResource("watch.phase.warmUp")
|
||||
case .work: return LocalizedStringResource("watch.phase.work")
|
||||
case .rest: return LocalizedStringResource("watch.phase.rest")
|
||||
case .interBlockRest: return LocalizedStringResource("watch.phase.break")
|
||||
case .cooldown: return LocalizedStringResource("watch.phase.coolDown")
|
||||
case .complete: return LocalizedStringResource("watch.phase.done")
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user