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:
Millian Lamiaux
2026-04-22 00:41:19 +02:00
parent e28bebea79
commit 0f5b7b9e18
23 changed files with 4423 additions and 340 deletions

View File

@@ -0,0 +1,54 @@
import Foundation
/// Type-safe string keys for the Watch target's `Localizable.xcstrings`.
/// Usage: Text(WatchL10n.phase.work) or String(localized: WatchL10n.idle.connected)
enum WatchL10n {
enum phase {
static let getReady = LocalizedStringResource("watch.phase.getReady")
static let warmUp = LocalizedStringResource("watch.phase.warmUp")
static let work = LocalizedStringResource("watch.phase.work")
static let rest = LocalizedStringResource("watch.phase.rest")
static let `break` = LocalizedStringResource("watch.phase.break")
static let coolDown = LocalizedStringResource("watch.phase.coolDown")
static let done = LocalizedStringResource("watch.phase.done")
static func label(for phase: WatchPhase) -> LocalizedStringResource {
switch phase {
case .prep: return getReady
case .warmup: return warmUp
case .work: return work
case .rest: return rest
case .interBlockRest: return `break`
case .cooldown: return coolDown
case .complete: return done
}
}
}
enum idle {
static let startOnPhone = LocalizedStringResource("watch.idle.startOnPhone")
static let connected = LocalizedStringResource("watch.idle.connected")
static let noPhone = LocalizedStringResource("watch.idle.noPhone")
}
enum activity {
static let today = LocalizedStringResource("watch.activity.today")
static let move = LocalizedStringResource("watch.activity.move")
static let exercise = LocalizedStringResource("watch.activity.exercise")
static let stand = LocalizedStringResource("watch.activity.stand")
static let streak = LocalizedStringResource("watch.activity.streak")
}
enum complication {
static let notStarted = LocalizedStringResource("watch.complication.notStarted")
static let today = LocalizedStringResource("watch.complication.today")
static let yesterday = LocalizedStringResource("watch.complication.yesterday")
/// printf format: takes one Int (number of days). e.g. "%d days ago"
static let daysAgoFmt = LocalizedStringResource("watch.complication.daysAgoFmt")
/// printf format: takes one Int (streak count). e.g. "%d day streak"
static let dayStreakFmt = LocalizedStringResource("watch.complication.dayStreakFmt")
static let openApp = LocalizedStringResource("watch.complication.openApp")
static let description = LocalizedStringResource("watch.complication.description")
}
}