From 089f8b9f7b59673e77120641cb823820bf7d7218 Mon Sep 17 00:00:00 2001 From: Matt Bruce Date: Mon, 9 Feb 2026 13:22:50 -0600 Subject: [PATCH] localization Signed-off-by: Matt Bruce --- PRD.md | 3 + README.md | 3 + TheNoiseClock/App/ContentView.swift | 8 +- .../Alarms/Intents/AlarmIntents.swift | 37 +- .../Features/Alarms/Models/Alarm.swift | 18 +- .../Alarms/Services/AlarmKitService.swift | 14 +- .../Alarms/State/AlarmViewModel.swift | 9 +- .../Features/Alarms/Views/AddAlarmView.swift | 35 +- .../Features/Alarms/Views/AlarmView.swift | 6 +- .../Views/Components/AlarmRowView.swift | 24 +- .../Components/AlertOptionsSection.swift | 10 +- .../Views/Components/EmptyAlarmsView.swift | 6 +- .../Views/Components/LabelEditView.swift | 9 +- .../NotificationMessageEditView.swift | 4 +- .../Components/RepeatSelectionView.swift | 14 +- .../Components/SnoozeSelectionView.swift | 11 +- .../Views/Components/SoundSelectionView.swift | 4 +- .../Views/Components/TimePickerSection.swift | 2 +- .../Components/TimeUntilAlarmSection.swift | 18 +- .../Features/Alarms/Views/EditAlarmView.swift | 31 +- .../Clock/Views/ClockSettingsView.swift | 16 +- .../Features/Clock/Views/ClockView.swift | 25 +- .../Views/Components/BatteryOverlayView.swift | 2 +- .../Components/ClockDisplayContainer.swift | 2 +- .../Clock/Views/Components/ClockToolbar.swift | 2 +- .../Clock/Views/Components/DigitView.swift | 2 +- .../Views/Components/NoiseMiniPlayer.swift | 6 +- .../Settings/AdvancedAppearanceSection.swift | 20 +- .../Settings/AdvancedDisplaySection.swift | 20 +- .../Settings/BasicAppearanceSection.swift | 51 +- .../Settings/BasicDisplaySection.swift | 24 +- .../Components/Settings/FontSection.swift | 25 +- .../Settings/NightModeSection.swift | 26 +- .../Components/Settings/OverlaySection.swift | 22 +- .../Views/Components/TimeDisplayView.swift | 22 +- .../Views/Components/SoundCategoryView.swift | 2 +- .../Views/Components/SoundControlView.swift | 12 +- .../Features/Noise/Views/NoiseView.swift | 22 +- .../Components/OnboardingBottomControls.swift | 12 +- .../Components/OnboardingGetStartedPage.swift | 14 +- .../OnboardingPermissionsPage.swift | 22 +- .../Components/OnboardingWelcomePage.swift | 10 +- TheNoiseClock/Localizable.xcstrings | 4406 +++++++++++++++++ .../Animations/DigitAnimationStyle.swift | 12 +- .../Shared/Models/SoundCategory.swift | 36 +- .../Shared/Utilities/KeepAwakePrompt.swift | 8 +- 46 files changed, 4825 insertions(+), 262 deletions(-) create mode 100644 TheNoiseClock/Localizable.xcstrings diff --git a/PRD.md b/PRD.md index 95479c8..7f3ca85 100644 --- a/PRD.md +++ b/PRD.md @@ -798,6 +798,9 @@ Use **iPhone 17 Pro Max (iOS 26.2)** as the primary simulator for build and test - **Async/Await**: Modern concurrency patterns throughout the codebase - **Observation Framework**: @Observable for reactive state management - **SwiftUI Navigation**: Latest NavigationStack and navigation APIs with iOS 26 features +- **Localization**: User-facing runtime/accessibility strings use modern `String(localized:)` with `Localizable.xcstrings` coverage for English (`en`), Spanish - Mexico (`es-MX`), and French - Canada (`fr-CA`) +- **Settings Localization**: Clock settings screens use explicit localization keys for all section labels, control titles/subtitles, and helper copy +- **Localization Consistency**: App localization catalog uses a key-only convention (namespaced keys) to avoid mixed source-string and keyed entries - **Accessibility**: Full VoiceOver and Dynamic Type support with iOS 26 enhancements - **Adaptive Layout**: Support for all device sizes and orientations with Liquid Glass - **Performance**: Optimized for 120Hz ProMotion displays and iOS 26 performance improvements diff --git a/README.md b/README.md index cc6b315..30f175a 100644 --- a/README.md +++ b/README.md @@ -146,6 +146,9 @@ Swift access is provided via: - Alarm list ordering now prioritizes enabled alarms and sorts by the next trigger time for faster at-a-glance relevance. - Repeat-day controls now follow locale weekday order and include accessibility identifiers for repeat/alert options controls. - Onboarding now explicitly highlights repeat schedules plus per-alarm vibration/flash/volume customization for better feature discovery. +- Non-`Text` user-facing strings were migrated to `String(localized:)` and backed by `TheNoiseClock/Localizable.xcstrings` with `en`, `es-MX`, and `fr-CA` translations. +- Clock Settings UI copy now uses explicit localization keys (no raw `Text(\"...\")` literals in settings screens). +- Localization catalog is normalized to a single pattern: namespaced key-based entries only (no source-string keys). --- diff --git a/TheNoiseClock/App/ContentView.swift b/TheNoiseClock/App/ContentView.swift index b3c4686..2e9cbe9 100644 --- a/TheNoiseClock/App/ContentView.swift +++ b/TheNoiseClock/App/ContentView.swift @@ -79,25 +79,25 @@ struct ContentView: View { private var mainTabView: some View { TabView(selection: $selectedTab) { - Tab("Clock", systemImage: "clock", value: AppTab.clock) { + Tab(String(localized: "tab.clock", defaultValue: "Clock"), systemImage: "clock", value: AppTab.clock) { NavigationStack { ClockView(viewModel: clockViewModel, isOnClockTab: isOnClockTab) } } - Tab("Alarms", systemImage: "alarm", value: AppTab.alarms) { + Tab(String(localized: "tab.alarms", defaultValue: "Alarms"), systemImage: "alarm", value: AppTab.alarms) { NavigationStack { AlarmView(viewModel: alarmViewModel) } } - Tab("Noise", systemImage: "waveform", value: AppTab.noise) { + Tab(String(localized: "tab.noise", defaultValue: "Noise"), systemImage: "waveform", value: AppTab.noise) { NavigationStack { NoiseView() } } - Tab("Settings", systemImage: "gearshape", value: AppTab.settings) { + Tab(String(localized: "tab.settings", defaultValue: "Settings"), systemImage: "gearshape", value: AppTab.settings) { NavigationStack { ClockSettingsView( style: clockViewModel.style, diff --git a/TheNoiseClock/Features/Alarms/Intents/AlarmIntents.swift b/TheNoiseClock/Features/Alarms/Intents/AlarmIntents.swift index 702bcd3..633ec4f 100644 --- a/TheNoiseClock/Features/Alarms/Intents/AlarmIntents.swift +++ b/TheNoiseClock/Features/Alarms/Intents/AlarmIntents.swift @@ -17,10 +17,15 @@ import Foundation /// Intent to stop an active alarm from the Live Activity or notification. struct StopAlarmIntent: LiveActivityIntent { - static let title: LocalizedStringResource = "Stop Alarm" - static let description = IntentDescription("Stops the currently ringing alarm") + static let title = LocalizedStringResource("alarm_intent.stop.title", defaultValue: "Stop Alarm") + static let description = IntentDescription( + LocalizedStringResource( + "alarm_intent.stop.description", + defaultValue: "Stops the currently ringing alarm" + ) + ) - @Parameter(title: "Alarm ID") + @Parameter(title: LocalizedStringResource("alarm_intent.parameter.alarm_id", defaultValue: "Alarm ID")) var alarmId: String static var supportedModes: IntentModes { .background } @@ -48,10 +53,15 @@ struct StopAlarmIntent: LiveActivityIntent { /// Intent to snooze an active alarm from the Live Activity or notification. struct SnoozeAlarmIntent: LiveActivityIntent { - static let title: LocalizedStringResource = "Snooze Alarm" - static let description = IntentDescription("Snoozes the currently ringing alarm") + static let title = LocalizedStringResource("alarm_intent.snooze.title", defaultValue: "Snooze Alarm") + static let description = IntentDescription( + LocalizedStringResource( + "alarm_intent.snooze.description", + defaultValue: "Snoozes the currently ringing alarm" + ) + ) - @Parameter(title: "Alarm ID") + @Parameter(title: LocalizedStringResource("alarm_intent.parameter.alarm_id", defaultValue: "Alarm ID")) var alarmId: String static var supportedModes: IntentModes { .background } @@ -80,11 +90,16 @@ struct SnoozeAlarmIntent: LiveActivityIntent { /// Intent to open the app when the user taps the Live Activity. struct OpenAlarmAppIntent: LiveActivityIntent { - static let title: LocalizedStringResource = "Open TheNoiseClock" - static let description = IntentDescription("Opens the app to the alarm screen") + static let title = LocalizedStringResource("alarm_intent.open_app.title", defaultValue: "Open TheNoiseClock") + static let description = IntentDescription( + LocalizedStringResource( + "alarm_intent.open_app.description", + defaultValue: "Opens the app to the alarm screen" + ) + ) static let openAppWhenRun = true - @Parameter(title: "Alarm ID") + @Parameter(title: LocalizedStringResource("alarm_intent.parameter.alarm_id", defaultValue: "Alarm ID")) var alarmId: String init() { @@ -110,9 +125,9 @@ enum AlarmIntentError: Error, LocalizedError { var errorDescription: String? { switch self { case .invalidAlarmID: - return "Invalid alarm ID" + return String(localized: "alarm_intent.error.invalid_alarm_id", defaultValue: "Invalid alarm ID") case .alarmNotFound: - return "Alarm not found" + return String(localized: "alarm_intent.error.alarm_not_found", defaultValue: "Alarm not found") } } } diff --git a/TheNoiseClock/Features/Alarms/Models/Alarm.swift b/TheNoiseClock/Features/Alarms/Models/Alarm.swift index 562ae8b..51f6371 100644 --- a/TheNoiseClock/Features/Alarms/Models/Alarm.swift +++ b/TheNoiseClock/Features/Alarms/Models/Alarm.swift @@ -43,8 +43,8 @@ struct Alarm: Identifiable, Codable, Equatable { isEnabled: Bool = true, repeatWeekdays: [Int] = [], soundName: String = AppConstants.SystemSounds.defaultSound, - label: String = "Alarm", - notificationMessage: String = "Your alarm is ringing", + label: String = String(localized: "alarm.default_label", defaultValue: "Alarm"), + notificationMessage: String = String(localized: "alarm.default_notification_message", defaultValue: "Your alarm is ringing"), snoozeDuration: Int = 9, isVibrationEnabled: Bool = true, isLightFlashEnabled: Bool = false, @@ -72,8 +72,8 @@ struct Alarm: Identifiable, Codable, Equatable { try container.decodeIfPresent([Int].self, forKey: .repeatWeekdays) ?? [] ) self.soundName = try container.decodeIfPresent(String.self, forKey: .soundName) ?? AppConstants.SystemSounds.defaultSound - self.label = try container.decodeIfPresent(String.self, forKey: .label) ?? "Alarm" - self.notificationMessage = try container.decodeIfPresent(String.self, forKey: .notificationMessage) ?? "Your alarm is ringing" + self.label = try container.decodeIfPresent(String.self, forKey: .label) ?? String(localized: "alarm.default_label", defaultValue: "Alarm") + self.notificationMessage = try container.decodeIfPresent(String.self, forKey: .notificationMessage) ?? String(localized: "alarm.default_notification_message", defaultValue: "Your alarm is ringing") self.snoozeDuration = try container.decodeIfPresent(Int.self, forKey: .snoozeDuration) ?? 9 self.isVibrationEnabled = try container.decodeIfPresent(Bool.self, forKey: .isVibrationEnabled) ?? true self.isLightFlashEnabled = try container.decodeIfPresent(Bool.self, forKey: .isLightFlashEnabled) ?? false @@ -127,19 +127,21 @@ struct Alarm: Identifiable, Codable, Equatable { static func repeatSummary(for weekdays: [Int]) -> String { let normalized = sanitizedWeekdays(weekdays) - guard !normalized.isEmpty else { return "Once" } + guard !normalized.isEmpty else { + return String(localized: "alarm.repeat.once", defaultValue: "Once") + } let weekdaySet = Set(normalized) if weekdaySet.count == 7 { - return "Every day" + return String(localized: "alarm.repeat.every_day", defaultValue: "Every day") } if weekdaySet == Set([2, 3, 4, 5, 6]) { - return "Weekdays" + return String(localized: "alarm.repeat.weekdays", defaultValue: "Weekdays") } if weekdaySet == Set([1, 7]) { - return "Weekends" + return String(localized: "alarm.repeat.weekends", defaultValue: "Weekends") } let symbols = Calendar.current.shortWeekdaySymbols diff --git a/TheNoiseClock/Features/Alarms/Services/AlarmKitService.swift b/TheNoiseClock/Features/Alarms/Services/AlarmKitService.swift index d443f72..619bbfc 100644 --- a/TheNoiseClock/Features/Alarms/Services/AlarmKitService.swift +++ b/TheNoiseClock/Features/Alarms/Services/AlarmKitService.swift @@ -83,14 +83,14 @@ final class AlarmKitService { // Create the stop button for the alarm let stopButton = AlarmButton( - text: "Stop", + text: LocalizedStringResource("alarm.action.stop"), textColor: .red, systemImageName: "stop.fill" ) // Create the snooze button (secondary button with countdown behavior) let snoozeButton = AlarmButton( - text: "Snooze", + text: LocalizedStringResource("alarm.action.snooze"), textColor: .white, systemImageName: "moon.zzz" ) @@ -422,9 +422,15 @@ enum AlarmKitError: Error, LocalizedError { var errorDescription: String? { switch self { case .notAuthorized: - return "AlarmKit is not authorized. Please enable alarm permissions in Settings." + return String( + localized: "alarmkit.error.not_authorized", + defaultValue: "AlarmKit is not authorized. Please enable alarm permissions in Settings." + ) case .schedulingFailed(let error): - return "Failed to schedule alarm: \(error.localizedDescription)" + return String( + localized: "alarmkit.error.scheduling_failed", + defaultValue: "Failed to schedule alarm: \(error.localizedDescription)" + ) } } } diff --git a/TheNoiseClock/Features/Alarms/State/AlarmViewModel.swift b/TheNoiseClock/Features/Alarms/State/AlarmViewModel.swift index c96e206..ea3e4f7 100644 --- a/TheNoiseClock/Features/Alarms/State/AlarmViewModel.swift +++ b/TheNoiseClock/Features/Alarms/State/AlarmViewModel.swift @@ -152,8 +152,8 @@ final class AlarmViewModel { time: Date, repeatWeekdays: [Int] = [], soundName: String = AppConstants.SystemSounds.defaultSound, - label: String = "Alarm", - notificationMessage: String = "Your alarm is ringing", + label: String = String(localized: "alarm.default_label", defaultValue: "Alarm"), + notificationMessage: String = String(localized: "alarm.default_notification_message", defaultValue: "Your alarm is ringing"), snoozeDuration: Int = 9, isVibrationEnabled: Bool = true, isLightFlashEnabled: Bool = false, @@ -219,6 +219,9 @@ final class AlarmViewModel { } else { error.localizedDescription } - return "Unable to \(action) alarm. \(detail)" + return String( + localized: "alarm.operation.error_message", + defaultValue: "Unable to \(action) alarm. \(detail)" + ) } } diff --git a/TheNoiseClock/Features/Alarms/Views/AddAlarmView.swift b/TheNoiseClock/Features/Alarms/Views/AddAlarmView.swift index b26a234..1946a25 100644 --- a/TheNoiseClock/Features/Alarms/Views/AddAlarmView.swift +++ b/TheNoiseClock/Features/Alarms/Views/AddAlarmView.swift @@ -19,8 +19,8 @@ struct AddAlarmView: View { @State private var newAlarmTime = Calendar.current.date(bySettingHour: 6, minute: 0, second: 0, of: Date()) ?? Date() @State private var repeatWeekdays: [Int] = [] @State private var selectedSoundName = AppConstants.SystemSounds.defaultSound - @State private var alarmLabel = "Alarm" - @State private var notificationMessage = "Your alarm is ringing" + @State private var alarmLabel = String(localized: "alarm.default_label", defaultValue: "Alarm") + @State private var notificationMessage = String(localized: "alarm.default_notification_message", defaultValue: "Your alarm is ringing") @State private var snoozeDuration = 9 // minutes @State private var isVibrationEnabled = true @State private var isLightFlashEnabled = false @@ -44,7 +44,7 @@ struct AddAlarmView: View { Image(systemName: "textformat") .foregroundStyle(AppAccent.primary) .frame(width: 24) - Text("Label") + Text(String(localized: "alarm.editor.label", defaultValue: "Label")) Spacer() Text(alarmLabel) .foregroundStyle(.secondary) @@ -57,7 +57,7 @@ struct AddAlarmView: View { Image(systemName: "message") .foregroundStyle(AppAccent.primary) .frame(width: 24) - Text("Message") + Text(String(localized: "alarm.editor.message", defaultValue: "Message")) Spacer() Text(notificationMessage) .foregroundStyle(.secondary) @@ -71,7 +71,7 @@ struct AddAlarmView: View { Image(systemName: "music.note") .foregroundStyle(AppAccent.primary) .frame(width: 24) - Text("Sound") + Text(String(localized: "alarm.editor.sound", defaultValue: "Sound")) Spacer() Text(getSoundDisplayName(selectedSoundName)) .foregroundStyle(.secondary) @@ -84,7 +84,7 @@ struct AddAlarmView: View { Image(systemName: "repeat") .foregroundStyle(AppAccent.primary) .frame(width: 24) - Text("Repeat") + Text(String(localized: "alarm.editor.repeat", defaultValue: "Repeat")) Spacer() Text(Alarm.repeatSummary(for: repeatWeekdays)) .foregroundStyle(.secondary) @@ -98,9 +98,14 @@ struct AddAlarmView: View { Image(systemName: "clock.arrow.circlepath") .foregroundStyle(AppAccent.primary) .frame(width: 24) - Text("Snooze") + Text(String(localized: "alarm.editor.snooze", defaultValue: "Snooze")) Spacer() - Text("for \(snoozeDuration) min") + Text( + String( + localized: "alarm.editor.snooze_for_minutes", + defaultValue: "for \(snoozeDuration) min" + ) + ) .foregroundStyle(.secondary) } } @@ -113,12 +118,12 @@ struct AddAlarmView: View { } .listStyle(.insetGrouped) } - .navigationTitle("Alarm") + .navigationTitle(String(localized: "alarm.add.navigation_title", defaultValue: "Alarm")) .navigationBarTitleDisplayMode(.inline) .navigationBarBackButtonHidden(true) .toolbar { ToolbarItem(placement: .navigationBarLeading) { - Button("Cancel") { + Button(String(localized: "common.cancel", defaultValue: "Cancel")) { isPresented = false } .foregroundStyle(AppAccent.primary) @@ -126,7 +131,11 @@ struct AddAlarmView: View { } ToolbarItem(placement: .navigationBarTrailing) { - Button(isSaving ? "Saving..." : "Save") { + Button( + isSaving + ? String(localized: "common.saving", defaultValue: "Saving...") + : String(localized: "common.save", defaultValue: "Save") + ) { Task { await saveAlarm() } @@ -138,8 +147,8 @@ struct AddAlarmView: View { } } } - .alert("Alarm Error", isPresented: $isShowingSaveErrorAlert) { - Button("OK", role: .cancel) { } + .alert(String(localized: "alarm.error.title", defaultValue: "Alarm Error"), isPresented: $isShowingSaveErrorAlert) { + Button(String(localized: "common.ok", defaultValue: "OK"), role: .cancel) { } } message: { Text(saveErrorMessage) } diff --git a/TheNoiseClock/Features/Alarms/Views/AlarmView.swift b/TheNoiseClock/Features/Alarms/Views/AlarmView.swift index 1731cf5..6e1ab47 100644 --- a/TheNoiseClock/Features/Alarms/Views/AlarmView.swift +++ b/TheNoiseClock/Features/Alarms/Views/AlarmView.swift @@ -71,7 +71,7 @@ struct AlarmView: View { .frame(maxWidth: .infinity, alignment: .center) } } - .navigationTitle(isPad ? "" : "Alarms") + .navigationTitle(isPad ? "" : String(localized: "alarms.navigation_title", defaultValue: "Alarms")) .navigationBarTitleDisplayMode(.inline) .toolbar { ToolbarItem(placement: .navigationBarTrailing) { @@ -107,8 +107,8 @@ struct AlarmView: View { } .sensoryFeedback(.impact(flexibility: .soft), trigger: showAddAlarm) .accessibilityIdentifier("alarms.screen") - .alert("Alarm Error", isPresented: $viewModel.isShowingErrorAlert) { - Button("OK", role: .cancel) { + .alert(String(localized: "alarm.error.title", defaultValue: "Alarm Error"), isPresented: $viewModel.isShowingErrorAlert) { + Button(String(localized: "common.ok", defaultValue: "OK"), role: .cancel) { viewModel.dismissErrorAlert() } } message: { diff --git a/TheNoiseClock/Features/Alarms/Views/Components/AlarmRowView.swift b/TheNoiseClock/Features/Alarms/Views/Components/AlarmRowView.swift index f99ee2b..d1a740e 100644 --- a/TheNoiseClock/Features/Alarms/Views/Components/AlarmRowView.swift +++ b/TheNoiseClock/Features/Alarms/Views/Components/AlarmRowView.swift @@ -36,7 +36,12 @@ struct AlarmRowView: View { .font(.caption) .foregroundStyle(AppTextColors.tertiary) - Text("• \(AlarmSoundService.shared.getSoundDisplayName(alarm.soundName))") + Text( + String( + localized: "alarm.row.sound_prefix", + defaultValue: "• \(AlarmSoundService.shared.getSoundDisplayName(alarm.soundName))" + ) + ) .font(.caption) .foregroundStyle(AppTextColors.secondary) @@ -46,7 +51,7 @@ struct AlarmRowView: View { .font(.caption2) .foregroundStyle(AppStatus.warning) .symbolEffect(.pulse, options: .repeating) - Text("Foreground only for full alarm sound") + Text(String(localized: "alarm.row.keep_awake_warning", defaultValue: "Foreground only for full alarm sound")) .font(.caption2) .foregroundStyle(AppTextColors.tertiary) } @@ -73,14 +78,23 @@ struct AlarmRowView: View { } .accessibilityElement(children: .contain) .accessibilityIdentifier("alarms.row.\(alarm.id.uuidString)") - .accessibilityLabel("\(alarm.label), \(alarm.formattedTime())") - .accessibilityValue(alarm.isEnabled ? "Enabled" : "Disabled") + .accessibilityLabel( + String( + localized: "alarm.row.accessibility_label", + defaultValue: "\(alarm.label), \(alarm.formattedTime())" + ) + ) + .accessibilityValue( + alarm.isEnabled + ? String(localized: "common.enabled", defaultValue: "Enabled") + : String(localized: "common.disabled", defaultValue: "Disabled") + ) } .swipeActions(edge: .trailing, allowsFullSwipe: true) { Button(role: .destructive) { onDelete() } label: { - Label("Delete", systemImage: "trash") + Label(String(localized: "common.delete", defaultValue: "Delete"), systemImage: "trash") } } } diff --git a/TheNoiseClock/Features/Alarms/Views/Components/AlertOptionsSection.swift b/TheNoiseClock/Features/Alarms/Views/Components/AlertOptionsSection.swift index cf442f7..94518da 100644 --- a/TheNoiseClock/Features/Alarms/Views/Components/AlertOptionsSection.swift +++ b/TheNoiseClock/Features/Alarms/Views/Components/AlertOptionsSection.swift @@ -15,20 +15,20 @@ struct AlertOptionsSection: View { @Binding var volume: Float var body: some View { - Section("Alert Options") { - Toggle("Vibration", isOn: $isVibrationEnabled) + Section(String(localized: "alarm.alert_options.section_title", defaultValue: "Alert Options")) { + Toggle(String(localized: "alarm.alert_options.vibration", defaultValue: "Vibration"), isOn: $isVibrationEnabled) .tint(AppAccent.primary) .accessibilityIdentifier("alarms.alertOptions.vibrationToggle") - Toggle("Flash Screen", isOn: $isLightFlashEnabled) + Toggle(String(localized: "alarm.alert_options.flash_screen", defaultValue: "Flash Screen"), isOn: $isLightFlashEnabled) .tint(AppAccent.primary) .accessibilityIdentifier("alarms.alertOptions.flashToggle") VStack(alignment: .leading, spacing: Design.Spacing.small) { HStack { - Text("Volume") + Text(String(localized: "alarm.alert_options.volume", defaultValue: "Volume")) Spacer() - Text("\(Int((volume * 100).rounded()))%") + Text(Double(volume), format: .percent.precision(.fractionLength(0))) .foregroundStyle(.secondary) } diff --git a/TheNoiseClock/Features/Alarms/Views/Components/EmptyAlarmsView.swift b/TheNoiseClock/Features/Alarms/Views/Components/EmptyAlarmsView.swift index 1992d3c..6296935 100644 --- a/TheNoiseClock/Features/Alarms/Views/Components/EmptyAlarmsView.swift +++ b/TheNoiseClock/Features/Alarms/Views/Components/EmptyAlarmsView.swift @@ -32,11 +32,11 @@ struct EmptyAlarmsView: View { } VStack(spacing: Design.Spacing.small) { - Text("No Alarms Set") + Text(String(localized: "alarms.empty.title", defaultValue: "No Alarms Set")) .typography(.title2Bold) .foregroundStyle(AppTextColors.primary) - Text("Create an alarm to wake up gently on your own terms.") + Text(String(localized: "alarms.empty.subtitle", defaultValue: "Create an alarm to wake up gently on your own terms.")) .typography(.body) .foregroundStyle(AppTextColors.secondary) .multilineTextAlignment(.center) @@ -48,7 +48,7 @@ struct EmptyAlarmsView: View { HStack { Image(systemName: "plus.circle.fill") .symbolEffect(.bounce, options: .nonRepeating) - Text("Add Your First Alarm") + Text(String(localized: "alarms.empty.cta", defaultValue: "Add Your First Alarm")) } .typography(.bodyEmphasis) .foregroundStyle(.white) diff --git a/TheNoiseClock/Features/Alarms/Views/Components/LabelEditView.swift b/TheNoiseClock/Features/Alarms/Views/Components/LabelEditView.swift index 213956c..ea6a583 100644 --- a/TheNoiseClock/Features/Alarms/Views/Components/LabelEditView.swift +++ b/TheNoiseClock/Features/Alarms/Views/Components/LabelEditView.swift @@ -16,12 +16,15 @@ struct LabelEditView: View { var body: some View { Form { Section { - TextField("Alarm Label", text: $label) + TextField( + String(localized: "alarm.label.placeholder", defaultValue: "Alarm Label"), + text: $label + ) .typography(.body) .foregroundStyle(AppTextColors.primary) .padding(.vertical, Design.Spacing.small) } footer: { - Text("Enter a name for your alarm.") + Text(String(localized: "alarm.label.footer", defaultValue: "Enter a name for your alarm.")) .typography(.caption) .foregroundStyle(AppTextColors.secondary) } @@ -29,7 +32,7 @@ struct LabelEditView: View { } .scrollContentBackground(.hidden) .background(AppSurface.primary.ignoresSafeArea()) - .navigationTitle("Label") + .navigationTitle(String(localized: "alarm.label.navigation_title", defaultValue: "Label")) .navigationBarTitleDisplayMode(.inline) } } diff --git a/TheNoiseClock/Features/Alarms/Views/Components/NotificationMessageEditView.swift b/TheNoiseClock/Features/Alarms/Views/Components/NotificationMessageEditView.swift index 34291bb..51219c5 100644 --- a/TheNoiseClock/Features/Alarms/Views/Components/NotificationMessageEditView.swift +++ b/TheNoiseClock/Features/Alarms/Views/Components/NotificationMessageEditView.swift @@ -22,7 +22,7 @@ struct NotificationMessageEditView: View { .foregroundStyle(AppTextColors.primary) .padding(.vertical, Design.Spacing.xxSmall) } footer: { - Text("This message will appear when the alarm rings.") + Text(String(localized: "alarm.message.footer", defaultValue: "This message will appear when the alarm rings.")) .typography(.caption) .foregroundStyle(AppTextColors.secondary) } @@ -30,7 +30,7 @@ struct NotificationMessageEditView: View { } .scrollContentBackground(.hidden) .background(AppSurface.primary.ignoresSafeArea()) - .navigationTitle("Message") + .navigationTitle(String(localized: "alarm.message.navigation_title", defaultValue: "Message")) .navigationBarTitleDisplayMode(.inline) } } diff --git a/TheNoiseClock/Features/Alarms/Views/Components/RepeatSelectionView.swift b/TheNoiseClock/Features/Alarms/Views/Components/RepeatSelectionView.swift index 56995d2..bac1b58 100644 --- a/TheNoiseClock/Features/Alarms/Views/Components/RepeatSelectionView.swift +++ b/TheNoiseClock/Features/Alarms/Views/Components/RepeatSelectionView.swift @@ -16,14 +16,14 @@ struct RepeatSelectionView: View { var body: some View { List { - Section("Quick Picks") { - quickPickRow(id: "once", title: "Once", weekdays: []) - quickPickRow(id: "everyday", title: "Every Day", weekdays: allWeekdays) - quickPickRow(id: "weekdays", title: "Weekdays", weekdays: [2, 3, 4, 5, 6]) - quickPickRow(id: "weekends", title: "Weekends", weekdays: [1, 7]) + Section(String(localized: "alarm.repeat.quick_picks_section", defaultValue: "Quick Picks")) { + quickPickRow(id: "once", title: String(localized: "alarm.repeat.once", defaultValue: "Once"), weekdays: []) + quickPickRow(id: "everyday", title: String(localized: "alarm.repeat.every_day", defaultValue: "Every Day"), weekdays: allWeekdays) + quickPickRow(id: "weekdays", title: String(localized: "alarm.repeat.weekdays", defaultValue: "Weekdays"), weekdays: [2, 3, 4, 5, 6]) + quickPickRow(id: "weekends", title: String(localized: "alarm.repeat.weekends", defaultValue: "Weekends"), weekdays: [1, 7]) } - Section("Repeat On") { + Section(String(localized: "alarm.repeat.repeat_on_section", defaultValue: "Repeat On")) { ForEach(orderedWeekdays, id: \.self) { weekday in HStack { Text(dayName(for: weekday)) @@ -46,7 +46,7 @@ struct RepeatSelectionView: View { .listStyle(.insetGrouped) .scrollContentBackground(.hidden) .background(AppSurface.primary.ignoresSafeArea()) - .navigationTitle("Repeat") + .navigationTitle(String(localized: "alarm.repeat.navigation_title", defaultValue: "Repeat")) .navigationBarTitleDisplayMode(.inline) } diff --git a/TheNoiseClock/Features/Alarms/Views/Components/SnoozeSelectionView.swift b/TheNoiseClock/Features/Alarms/Views/Components/SnoozeSelectionView.swift index abcf724..a4bf71c 100644 --- a/TheNoiseClock/Features/Alarms/Views/Components/SnoozeSelectionView.swift +++ b/TheNoiseClock/Features/Alarms/Views/Components/SnoozeSelectionView.swift @@ -17,10 +17,15 @@ struct SnoozeSelectionView: View { var body: some View { List { - Section("Snooze Duration") { + Section(String(localized: "alarm.snooze.duration_section", defaultValue: "Snooze Duration")) { ForEach(snoozeOptions, id: \.self) { duration in HStack { - Text("\(duration) minutes") + Text( + String( + localized: "alarm.snooze.duration_minutes", + defaultValue: "\(duration) minutes" + ) + ) .foregroundStyle(AppTextColors.primary) Spacer() if snoozeDuration == duration { @@ -39,7 +44,7 @@ struct SnoozeSelectionView: View { .listStyle(.insetGrouped) .scrollContentBackground(.hidden) .background(AppSurface.primary.ignoresSafeArea()) - .navigationTitle("Snooze") + .navigationTitle(String(localized: "alarm.snooze.navigation_title", defaultValue: "Snooze")) .navigationBarTitleDisplayMode(.inline) } } diff --git a/TheNoiseClock/Features/Alarms/Views/Components/SoundSelectionView.swift b/TheNoiseClock/Features/Alarms/Views/Components/SoundSelectionView.swift index b812619..727d0a4 100644 --- a/TheNoiseClock/Features/Alarms/Views/Components/SoundSelectionView.swift +++ b/TheNoiseClock/Features/Alarms/Views/Components/SoundSelectionView.swift @@ -23,7 +23,7 @@ struct SoundSelectionView: View { var body: some View { List { - Section("Alarm Sounds") { + Section(String(localized: "alarm.sound.section_title", defaultValue: "Alarm Sounds")) { ForEach(alarmSounds, id: \.id) { sound in HStack { Text(sound.name) @@ -50,7 +50,7 @@ struct SoundSelectionView: View { .listStyle(.insetGrouped) .scrollContentBackground(.hidden) .background(AppSurface.primary.ignoresSafeArea()) - .navigationTitle("Sound") + .navigationTitle(String(localized: "alarm.sound.navigation_title", defaultValue: "Sound")) .navigationBarTitleDisplayMode(.inline) .toolbar { ToolbarItem(placement: .navigationBarTrailing) { diff --git a/TheNoiseClock/Features/Alarms/Views/Components/TimePickerSection.swift b/TheNoiseClock/Features/Alarms/Views/Components/TimePickerSection.swift index e816d4e..05bbf17 100644 --- a/TheNoiseClock/Features/Alarms/Views/Components/TimePickerSection.swift +++ b/TheNoiseClock/Features/Alarms/Views/Components/TimePickerSection.swift @@ -18,7 +18,7 @@ struct TimePickerSection: View { VStack(spacing: 0) { DatePicker( - "Time", + String(localized: "alarm.time_picker.label", defaultValue: "Time"), selection: $selectedTime, displayedComponents: .hourAndMinute ) diff --git a/TheNoiseClock/Features/Alarms/Views/Components/TimeUntilAlarmSection.swift b/TheNoiseClock/Features/Alarms/Views/Components/TimeUntilAlarmSection.swift index 3d5f205..70f59c7 100644 --- a/TheNoiseClock/Features/Alarms/Views/Components/TimeUntilAlarmSection.swift +++ b/TheNoiseClock/Features/Alarms/Views/Components/TimeUntilAlarmSection.swift @@ -46,23 +46,29 @@ struct TimeUntilAlarmSection: View { let components = calendar.dateComponents([.hour, .minute], from: now, to: nextAlarmTime) if let hours = components.hour, let minutes = components.minute { if hours > 0 { - return "Will turn on in \(hours)h \(minutes)m" + return String( + localized: "alarm.time_until.hours_minutes", + defaultValue: "Will turn on in \(hours)h \(minutes)m" + ) } else if minutes > 0 { - return "Will turn on in \(minutes)m" + return String( + localized: "alarm.time_until.minutes", + defaultValue: "Will turn on in \(minutes)m" + ) } else { - return "Will turn on now" + return String(localized: "alarm.time_until.now", defaultValue: "Will turn on now") } } - return "Will turn on tomorrow" + return String(localized: "alarm.time_until.tomorrow_fallback", defaultValue: "Will turn on tomorrow") } private var dayText: String { let calendar = Calendar.current if calendar.isDateInToday(nextAlarmTime) { - return "Today" + return String(localized: "calendar.today", defaultValue: "Today") } else if calendar.isDateInTomorrow(nextAlarmTime) { - return "Tomorrow" + return String(localized: "calendar.tomorrow", defaultValue: "Tomorrow") } else { return nextAlarmTime.formatted(.dateTime.weekday(.wide)) } diff --git a/TheNoiseClock/Features/Alarms/Views/EditAlarmView.swift b/TheNoiseClock/Features/Alarms/Views/EditAlarmView.swift index 95abd76..1b84ebc 100644 --- a/TheNoiseClock/Features/Alarms/Views/EditAlarmView.swift +++ b/TheNoiseClock/Features/Alarms/Views/EditAlarmView.swift @@ -64,7 +64,7 @@ struct EditAlarmView: View { Image(systemName: "textformat") .foregroundStyle(AppAccent.primary) .frame(width: 24) - Text("Label") + Text(String(localized: "alarm.editor.label", defaultValue: "Label")) .foregroundStyle(AppTextColors.primary) Spacer() Text(alarmLabel) @@ -79,7 +79,7 @@ struct EditAlarmView: View { Image(systemName: "message") .foregroundStyle(AppAccent.primary) .frame(width: 24) - Text("Message") + Text(String(localized: "alarm.editor.message", defaultValue: "Message")) .foregroundStyle(AppTextColors.primary) Spacer() Text(notificationMessage) @@ -95,7 +95,7 @@ struct EditAlarmView: View { Image(systemName: "music.note") .foregroundStyle(AppAccent.primary) .frame(width: 24) - Text("Sound") + Text(String(localized: "alarm.editor.sound", defaultValue: "Sound")) .foregroundStyle(AppTextColors.primary) Spacer() Text(getSoundDisplayName(selectedSoundName)) @@ -110,7 +110,7 @@ struct EditAlarmView: View { Image(systemName: "repeat") .foregroundStyle(AppAccent.primary) .frame(width: 24) - Text("Repeat") + Text(String(localized: "alarm.editor.repeat", defaultValue: "Repeat")) .foregroundStyle(AppTextColors.primary) Spacer() Text(Alarm.repeatSummary(for: repeatWeekdays)) @@ -126,10 +126,15 @@ struct EditAlarmView: View { Image(systemName: "clock.arrow.circlepath") .foregroundStyle(AppAccent.primary) .frame(width: 24) - Text("Snooze") + Text(String(localized: "alarm.editor.snooze", defaultValue: "Snooze")) .foregroundStyle(AppTextColors.primary) Spacer() - Text("for \(snoozeDuration) min") + Text( + String( + localized: "alarm.editor.snooze_for_minutes", + defaultValue: "for \(snoozeDuration) min" + ) + ) .foregroundStyle(AppTextColors.secondary) } } @@ -146,12 +151,12 @@ struct EditAlarmView: View { .scrollContentBackground(.hidden) .background(AppSurface.primary.ignoresSafeArea()) } - .navigationTitle("Edit Alarm") + .navigationTitle(String(localized: "alarm.edit.navigation_title", defaultValue: "Edit Alarm")) .navigationBarTitleDisplayMode(.inline) .navigationBarBackButtonHidden(true) .toolbar { ToolbarItem(placement: .navigationBarLeading) { - Button("Cancel") { + Button(String(localized: "common.cancel", defaultValue: "Cancel")) { dismiss() } .foregroundStyle(AppAccent.primary) @@ -159,7 +164,11 @@ struct EditAlarmView: View { } ToolbarItem(placement: .navigationBarTrailing) { - Button(isSaving ? "Saving..." : "Save") { + Button( + isSaving + ? String(localized: "common.saving", defaultValue: "Saving...") + : String(localized: "common.save", defaultValue: "Save") + ) { Task { await saveAlarm() } @@ -171,8 +180,8 @@ struct EditAlarmView: View { } } } - .alert("Alarm Error", isPresented: $isShowingSaveErrorAlert) { - Button("OK", role: .cancel) { } + .alert(String(localized: "alarm.error.title", defaultValue: "Alarm Error"), isPresented: $isShowingSaveErrorAlert) { + Button(String(localized: "common.ok", defaultValue: "OK"), role: .cancel) { } } message: { Text(saveErrorMessage) } diff --git a/TheNoiseClock/Features/Clock/Views/ClockSettingsView.swift b/TheNoiseClock/Features/Clock/Views/ClockSettingsView.swift index 945f479..2504098 100644 --- a/TheNoiseClock/Features/Clock/Views/ClockSettingsView.swift +++ b/TheNoiseClock/Features/Clock/Views/ClockSettingsView.swift @@ -55,23 +55,23 @@ struct ClockSettingsView: View { #if DEBUG SettingsSectionHeader( - title: "Debug", + title: String(localized: "settings.debug.section_title", defaultValue: "Debug"), systemImage: "ant.fill", accentColor: AppStatus.error ) SettingsCard(backgroundColor: AppSurface.card, borderColor: AppBorder.subtle) { SettingsNavigationRow( - title: "Icon Generator", - subtitle: "Generate and save app icon", + title: String(localized: "settings.debug.icon_generator.title", defaultValue: "Icon Generator"), + subtitle: String(localized: "settings.debug.icon_generator.subtitle", defaultValue: "Generate and save app icon"), backgroundColor: AppSurface.primary ) { IconGeneratorView(config: .noiseClock, appName: "TheNoiseClock") } SettingsNavigationRow( - title: "Branding Preview", - subtitle: "Preview icon and launch screen", + title: String(localized: "settings.debug.branding_preview.title", defaultValue: "Branding Preview"), + subtitle: String(localized: "settings.debug.branding_preview.subtitle", defaultValue: "Preview icon and launch screen"), backgroundColor: AppSurface.primary ) { BrandingPreviewView( @@ -90,10 +90,10 @@ struct ClockSettingsView: View { } label: { HStack { VStack(alignment: .leading, spacing: Design.Spacing.xxSmall) { - Text("Reset Onboarding") + Text(String(localized: "settings.debug.reset_onboarding.title", defaultValue: "Reset Onboarding")) .typography(.body) .foregroundStyle(AppTextColors.primary) - Text("Show onboarding screens again on next launch") + Text(String(localized: "settings.debug.reset_onboarding.subtitle", defaultValue: "Show onboarding screens again on next launch")) .typography(.caption) .foregroundStyle(AppTextColors.secondary) } @@ -116,7 +116,7 @@ struct ClockSettingsView: View { .padding(.bottom, Design.Spacing.xxxLarge) } .background(AppSurface.primary) - .navigationTitle(isPad ? "" : "Settings") + .navigationTitle(isPad ? "" : String(localized: "settings.navigation_title", defaultValue: "Settings")) .navigationBarTitleDisplayMode(.inline) .onAppear { digitColor = Color(hex: style.digitColorHex) ?? .white diff --git a/TheNoiseClock/Features/Clock/Views/ClockView.swift b/TheNoiseClock/Features/Clock/Views/ClockView.swift index b78471d..4040f33 100644 --- a/TheNoiseClock/Features/Clock/Views/ClockView.swift +++ b/TheNoiseClock/Features/Clock/Views/ClockView.swift @@ -180,11 +180,26 @@ struct ClockView: View { let hasDynamicIsland = windowInsets.left > 0 || windowInsets.right > 0 VStack(alignment: .leading, spacing: 2) { - Text("Screen: \(Int(size.width))×\(Int(size.height))") - Text("Window Insets: L:\(Int(windowInsets.left)) R:\(Int(windowInsets.right)) T:\(Int(windowInsets.top)) B:\(Int(windowInsets.bottom))") - Text("Symmetric Inset: \(Int(symmetricInset))") - Text("Dynamic Island: \(hasDynamicIsland && isLandscape ? "Yes" : "No")") - Text("Orientation: \(isLandscape ? "Landscape" : "Portrait")") + Text(String(localized: "clock.debug.screen", defaultValue: "Screen: \(Int(size.width))×\(Int(size.height))")) + Text( + String( + localized: "clock.debug.window_insets", + defaultValue: "Window Insets: L:\(Int(windowInsets.left)) R:\(Int(windowInsets.right)) T:\(Int(windowInsets.top)) B:\(Int(windowInsets.bottom))" + ) + ) + Text(String(localized: "clock.debug.symmetric_inset", defaultValue: "Symmetric Inset: \(Int(symmetricInset))")) + Text( + String( + localized: "clock.debug.dynamic_island", + defaultValue: "Dynamic Island: \(hasDynamicIsland && isLandscape ? "Yes" : "No")" + ) + ) + Text( + String( + localized: "clock.debug.orientation", + defaultValue: "Orientation: \(isLandscape ? "Landscape" : "Portrait")" + ) + ) } .font(.system(size: 10, weight: .bold, design: .monospaced)) .foregroundStyle(.green) diff --git a/TheNoiseClock/Features/Clock/Views/Components/BatteryOverlayView.swift b/TheNoiseClock/Features/Clock/Views/Components/BatteryOverlayView.swift index 1576f1d..a6f411b 100644 --- a/TheNoiseClock/Features/Clock/Views/Components/BatteryOverlayView.swift +++ b/TheNoiseClock/Features/Clock/Views/Components/BatteryOverlayView.swift @@ -27,7 +27,7 @@ struct BatteryOverlayView: View { .foregroundStyle(batteryColor) .symbolEffect(.pulse, options: .repeating, isActive: isCharging) .contentTransition(.symbolEffect(.replace)) - Text("\(batteryLevel)%") + Text(Double(batteryLevel) / 100, format: .percent.precision(.fractionLength(0))) .foregroundStyle(color) .contentTransition(.numericText()) .animation(.snappy(duration: 0.3), value: batteryLevel) diff --git a/TheNoiseClock/Features/Clock/Views/Components/ClockDisplayContainer.swift b/TheNoiseClock/Features/Clock/Views/Components/ClockDisplayContainer.swift index b60f1d5..548edc1 100644 --- a/TheNoiseClock/Features/Clock/Views/Components/ClockDisplayContainer.swift +++ b/TheNoiseClock/Features/Clock/Views/Components/ClockDisplayContainer.swift @@ -45,7 +45,7 @@ struct ClockDisplayContainer: View { .animation(.smooth(duration: Design.Animation.standard), value: isFullScreenMode) .accessibilityElement(children: .ignore) .accessibilityIdentifier("clock.timeDisplay") - .accessibilityLabel("Current time") + .accessibilityLabel(String(localized: "clock.accessibility.current_time", defaultValue: "Current time")) .accessibilityValue(accessibilityTimeValue) } } diff --git a/TheNoiseClock/Features/Clock/Views/Components/ClockToolbar.swift b/TheNoiseClock/Features/Clock/Views/Components/ClockToolbar.swift index f9b540f..a78b270 100644 --- a/TheNoiseClock/Features/Clock/Views/Components/ClockToolbar.swift +++ b/TheNoiseClock/Features/Clock/Views/Components/ClockToolbar.swift @@ -17,7 +17,7 @@ struct ClockToolbar: View { var body: some View { let isPad = UIDevice.current.userInterfaceIdiom == .pad EmptyView() - .navigationTitle(isDisplayMode || isPad ? "" : "Clock") + .navigationTitle(isDisplayMode || isPad ? "" : String(localized: "clock.navigation_title", defaultValue: "Clock")) .navigationBarTitleDisplayMode(.inline) .navigationBarBackButtonHidden(isDisplayMode) .toolbar(isDisplayMode ? .hidden : .automatic) diff --git a/TheNoiseClock/Features/Clock/Views/Components/DigitView.swift b/TheNoiseClock/Features/Clock/Views/Components/DigitView.swift index 42a3f92..7b076ac 100644 --- a/TheNoiseClock/Features/Clock/Views/Components/DigitView.swift +++ b/TheNoiseClock/Features/Clock/Views/Components/DigitView.swift @@ -195,7 +195,7 @@ private struct GlowAnimationModifier: ViewModifier { animationStyle: .spring) .border(Color.black) - Text(":") + Text(String(localized: "clock.time.colon", defaultValue: ":")) .font(.system(size: sharedFontSize)) .border(Color.black) diff --git a/TheNoiseClock/Features/Clock/Views/Components/NoiseMiniPlayer.swift b/TheNoiseClock/Features/Clock/Views/Components/NoiseMiniPlayer.swift index d6d87eb..b92ea53 100644 --- a/TheNoiseClock/Features/Clock/Views/Components/NoiseMiniPlayer.swift +++ b/TheNoiseClock/Features/Clock/Views/Components/NoiseMiniPlayer.swift @@ -36,7 +36,11 @@ struct NoiseMiniPlayer: View { .sensoryFeedback(.impact(flexibility: .soft), trigger: isPlaying) VStack(alignment: .leading, spacing: 0) { - Text(isPlaying ? "Playing" : "Paused") + Text( + isPlaying + ? String(localized: "noise.mini_player.playing", defaultValue: "Playing") + : String(localized: "noise.mini_player.paused", defaultValue: "Paused") + ) .font(.system(size: 8, weight: .bold)) .foregroundStyle(color.opacity(0.6)) .textCase(.uppercase) diff --git a/TheNoiseClock/Features/Clock/Views/Components/Settings/AdvancedAppearanceSection.swift b/TheNoiseClock/Features/Clock/Views/Components/Settings/AdvancedAppearanceSection.swift index 7054438..af11ce3 100644 --- a/TheNoiseClock/Features/Clock/Views/Components/Settings/AdvancedAppearanceSection.swift +++ b/TheNoiseClock/Features/Clock/Views/Components/Settings/AdvancedAppearanceSection.swift @@ -14,7 +14,7 @@ struct AdvancedAppearanceSection: View { var body: some View { VStack(alignment: .leading, spacing: Design.Spacing.small) { SettingsSectionHeader( - title: "Advanced Appearance", + title: String(localized: "settings.advanced_appearance.section_title", defaultValue: "Advanced Appearance"), systemImage: "sparkles", accentColor: AppAccent.primary ) @@ -22,14 +22,14 @@ struct AdvancedAppearanceSection: View { SettingsCard(backgroundColor: AppSurface.card, borderColor: AppBorder.subtle) { VStack(spacing: 0) { SettingsNavigationRow( - title: "Digit Animation", + title: String(localized: "settings.advanced_appearance.digit_animation.title", defaultValue: "Digit Animation"), subtitle: style.digitAnimationStyle.displayName, backgroundColor: .clear ) { SettingsSelectionView( selection: $style.digitAnimationStyle, options: DigitAnimationStyle.allCases, - title: "Digit Animation", + title: String(localized: "settings.advanced_appearance.digit_animation.title", defaultValue: "Digit Animation"), toString: { $0.displayName } ) } @@ -40,8 +40,8 @@ struct AdvancedAppearanceSection: View { .padding(.horizontal, Design.Spacing.medium) SettingsToggle( - title: "Randomize Color", - subtitle: "Shift the color every minute", + title: String(localized: "settings.advanced_appearance.randomize_color.title", defaultValue: "Randomize Color"), + subtitle: String(localized: "settings.advanced_appearance.randomize_color.subtitle", defaultValue: "Shift the color every minute"), isOn: $style.randomizeColor, accentColor: AppAccent.primary ) @@ -52,8 +52,8 @@ struct AdvancedAppearanceSection: View { .padding(.horizontal, Design.Spacing.medium) SettingsSlider( - title: "Glow", - subtitle: "Adjust the glow intensity", + title: String(localized: "settings.advanced_appearance.glow.title", defaultValue: "Glow"), + subtitle: String(localized: "settings.advanced_appearance.glow.subtitle", defaultValue: "Adjust the glow intensity"), value: $style.glowIntensity, in: 0.0...1.0, step: 0.01, @@ -67,8 +67,8 @@ struct AdvancedAppearanceSection: View { .padding(.horizontal, Design.Spacing.medium) SettingsSlider( - title: "Clock Opacity", - subtitle: "Set the clock transparency", + title: String(localized: "settings.advanced_appearance.clock_opacity.title", defaultValue: "Clock Opacity"), + subtitle: String(localized: "settings.advanced_appearance.clock_opacity.subtitle", defaultValue: "Set the clock transparency"), value: $style.clockOpacity, in: 0.0...1.0, step: 0.01, @@ -78,7 +78,7 @@ struct AdvancedAppearanceSection: View { } } - Text("Fine-tune the visual appearance of your clock.") + Text(String(localized: "settings.advanced_appearance.footer", defaultValue: "Fine-tune the visual appearance of your clock.")) .font(.caption) .foregroundStyle(AppTextColors.tertiary) } diff --git a/TheNoiseClock/Features/Clock/Views/Components/Settings/AdvancedDisplaySection.swift b/TheNoiseClock/Features/Clock/Views/Components/Settings/AdvancedDisplaySection.swift index 36dadd5..5df407e 100644 --- a/TheNoiseClock/Features/Clock/Views/Components/Settings/AdvancedDisplaySection.swift +++ b/TheNoiseClock/Features/Clock/Views/Components/Settings/AdvancedDisplaySection.swift @@ -14,7 +14,7 @@ struct AdvancedDisplaySection: View { var body: some View { VStack(alignment: .leading, spacing: Design.Spacing.small) { SettingsSectionHeader( - title: "Advanced Display", + title: String(localized: "settings.advanced_display.section_title", defaultValue: "Advanced Display"), systemImage: "eye", accentColor: AppAccent.primary ) @@ -22,8 +22,8 @@ struct AdvancedDisplaySection: View { SettingsCard(backgroundColor: AppSurface.card, borderColor: AppBorder.subtle) { VStack(spacing: 0) { SettingsToggle( - title: "Keep Awake", - subtitle: "Prevent sleep in display mode", + title: String(localized: "settings.advanced_display.keep_awake.title", defaultValue: "Keep Awake"), + subtitle: String(localized: "settings.advanced_display.keep_awake.subtitle", defaultValue: "Prevent sleep in display mode"), isOn: $style.keepAwake, accentColor: AppAccent.primary ) @@ -36,11 +36,11 @@ struct AdvancedDisplaySection: View { .padding(.horizontal, Design.Spacing.medium) HStack { - Text("Current Brightness") + Text(String(localized: "settings.advanced_display.current_brightness", defaultValue: "Current Brightness")) .font(.subheadline.weight(.medium)) .foregroundStyle(AppTextColors.primary) Spacer() - Text("\(Int(style.effectiveBrightness * 100))%") + Text(style.effectiveBrightness, format: .percent.precision(.fractionLength(0))) .font(.subheadline) .foregroundStyle(AppTextColors.secondary) .contentTransition(.numericText()) @@ -52,27 +52,27 @@ struct AdvancedDisplaySection: View { } } - Text("Advanced display and system integration settings. Keep Awake helps alarms stay active while the app remains open.") + Text(String(localized: "settings.advanced_display.footer", defaultValue: "Advanced display and system integration settings. Keep Awake helps alarms stay active while the app remains open.")) .font(.caption) .foregroundStyle(AppTextColors.tertiary) SettingsSectionHeader( - title: "Focus Modes", + title: String(localized: "settings.focus_modes.section_title", defaultValue: "Focus Modes"), systemImage: "moon.zzz.fill", accentColor: AppAccent.primary ) SettingsCard(backgroundColor: AppSurface.card, borderColor: AppBorder.subtle) { SettingsToggle( - title: "Respect Focus Modes", - subtitle: "Follow Do Not Disturb rules", + title: String(localized: "settings.focus_modes.respect_focus.title", defaultValue: "Respect Focus Modes"), + subtitle: String(localized: "settings.focus_modes.respect_focus.subtitle", defaultValue: "Follow Do Not Disturb rules"), isOn: $style.respectFocusModes, accentColor: AppAccent.primary ) .accessibilityIdentifier("settings.respectFocus.toggle") } - Text("Control how the app behaves when Focus modes are active.") + Text(String(localized: "settings.focus_modes.footer", defaultValue: "Control how the app behaves when Focus modes are active.")) .font(.caption) .foregroundStyle(AppTextColors.tertiary) } diff --git a/TheNoiseClock/Features/Clock/Views/Components/Settings/BasicAppearanceSection.swift b/TheNoiseClock/Features/Clock/Views/Components/Settings/BasicAppearanceSection.swift index fbc1371..2faf2e3 100644 --- a/TheNoiseClock/Features/Clock/Views/Components/Settings/BasicAppearanceSection.swift +++ b/TheNoiseClock/Features/Clock/Views/Components/Settings/BasicAppearanceSection.swift @@ -16,7 +16,7 @@ struct BasicAppearanceSection: View { var body: some View { VStack(alignment: .leading, spacing: Design.Spacing.small) { SettingsSectionHeader( - title: "Colors", + title: String(localized: "settings.colors.section_title", defaultValue: "Colors"), systemImage: "paintpalette.fill", accentColor: AppAccent.primary ) @@ -24,16 +24,16 @@ struct BasicAppearanceSection: View { SettingsCard(backgroundColor: AppSurface.card, borderColor: AppBorder.subtle) { VStack(spacing: 0) { SettingsNavigationRow( - title: "Color Theme", - subtitle: style.selectedColorTheme, + title: String(localized: "settings.colors.theme.title", defaultValue: "Color Theme"), + subtitle: localizedThemeName(for: style.selectedColorTheme), backgroundColor: .clear ) { SettingsSelectionView( selection: $style.selectedColorTheme, options: ClockStyle.availableColorThemes().map { $0.0 }, - title: "Color Theme", + title: String(localized: "settings.colors.theme.title", defaultValue: "Color Theme"), toString: { theme in - ClockStyle.availableColorThemes().first(where: { $0.0 == theme })?.1 ?? theme + localizedThemeName(for: theme) } ) } @@ -51,7 +51,11 @@ struct BasicAppearanceSection: View { .frame(height: 1) .padding(.horizontal, Design.Spacing.medium) - ColorPicker("Digit Color", selection: $digitColor, supportsOpacity: false) + ColorPicker( + String(localized: "settings.colors.digit_color", defaultValue: "Digit Color"), + selection: $digitColor, + supportsOpacity: false + ) .foregroundStyle(AppTextColors.primary) .padding(.horizontal, Design.Spacing.medium) .padding(.vertical, Design.Spacing.small) @@ -61,7 +65,11 @@ struct BasicAppearanceSection: View { .frame(height: 1) .padding(.horizontal, Design.Spacing.medium) - ColorPicker("Background Color", selection: $backgroundColor, supportsOpacity: true) + ColorPicker( + String(localized: "settings.colors.background_color", defaultValue: "Background Color"), + selection: $backgroundColor, + supportsOpacity: true + ) .foregroundStyle(AppTextColors.primary) .padding(.horizontal, Design.Spacing.medium) .padding(.vertical, Design.Spacing.small) @@ -69,7 +77,7 @@ struct BasicAppearanceSection: View { } } - Text("Choose your favorite color theme or create a custom look.") + Text(String(localized: "settings.colors.footer", defaultValue: "Choose your favorite color theme or create a custom look.")) .font(.caption) .foregroundStyle(AppTextColors.tertiary) } @@ -85,33 +93,32 @@ struct BasicAppearanceSection: View { } } - /// Get the color for a theme - private func themeColor(for theme: String) -> Color { + private func localizedThemeName(for theme: String) -> String { switch theme { case "Custom": - return .gray + return String(localized: "settings.colors.theme.custom", defaultValue: "Custom") case "Night": - return .white + return String(localized: "settings.colors.theme.night", defaultValue: "Night") case "Day": - return .black + return String(localized: "settings.colors.theme.day", defaultValue: "Day") case "Red": - return .red + return String(localized: "settings.colors.theme.red", defaultValue: "Red") case "Orange": - return .orange + return String(localized: "settings.colors.theme.orange", defaultValue: "Orange") case "Yellow": - return .yellow + return String(localized: "settings.colors.theme.yellow", defaultValue: "Yellow") case "Green": - return .green + return String(localized: "settings.colors.theme.green", defaultValue: "Green") case "Blue": - return .blue + return String(localized: "settings.colors.theme.blue", defaultValue: "Blue") case "Purple": - return .purple + return String(localized: "settings.colors.theme.purple", defaultValue: "Purple") case "Pink": - return .pink + return String(localized: "settings.colors.theme.pink", defaultValue: "Pink") case "White": - return .white + return String(localized: "settings.colors.theme.white", defaultValue: "White") default: - return .gray + return theme } } } diff --git a/TheNoiseClock/Features/Clock/Views/Components/Settings/BasicDisplaySection.swift b/TheNoiseClock/Features/Clock/Views/Components/Settings/BasicDisplaySection.swift index 7c083a2..5af1a10 100644 --- a/TheNoiseClock/Features/Clock/Views/Components/Settings/BasicDisplaySection.swift +++ b/TheNoiseClock/Features/Clock/Views/Components/Settings/BasicDisplaySection.swift @@ -14,7 +14,7 @@ struct BasicDisplaySection: View { var body: some View { VStack(alignment: .leading, spacing: Design.Spacing.small) { SettingsSectionHeader( - title: "Display", + title: String(localized: "settings.display.section_title", defaultValue: "Display"), systemImage: "display", accentColor: AppAccent.primary ) @@ -22,8 +22,8 @@ struct BasicDisplaySection: View { SettingsCard(backgroundColor: AppSurface.card, borderColor: AppBorder.subtle) { VStack(spacing: 0) { SettingsToggle( - title: "24‑Hour Format", - subtitle: "Use military time", + title: String(localized: "settings.display.use_24_hour.title", defaultValue: "24‑Hour Format"), + subtitle: String(localized: "settings.display.use_24_hour.subtitle", defaultValue: "Use military time"), isOn: $style.use24Hour, accentColor: AppAccent.primary ) @@ -34,8 +34,8 @@ struct BasicDisplaySection: View { .padding(.horizontal, Design.Spacing.medium) SettingsToggle( - title: "Show Seconds", - subtitle: "Display seconds in the clock", + title: String(localized: "settings.display.show_seconds.title", defaultValue: "Show Seconds"), + subtitle: String(localized: "settings.display.show_seconds.subtitle", defaultValue: "Display seconds in the clock"), isOn: $style.showSeconds, accentColor: AppAccent.primary ) @@ -47,8 +47,8 @@ struct BasicDisplaySection: View { .padding(.horizontal, Design.Spacing.medium) SettingsToggle( - title: "Show AM/PM", - subtitle: "Add an AM/PM indicator", + title: String(localized: "settings.display.show_ampm.title", defaultValue: "Show AM/PM"), + subtitle: String(localized: "settings.display.show_ampm.subtitle", defaultValue: "Add an AM/PM indicator"), isOn: $style.showAmPm, accentColor: AppAccent.primary ) @@ -60,8 +60,8 @@ struct BasicDisplaySection: View { .padding(.horizontal, Design.Spacing.medium) SettingsToggle( - title: "Auto Brightness", - subtitle: "Adapt brightness to ambient light", + title: String(localized: "settings.display.auto_brightness.title", defaultValue: "Auto Brightness"), + subtitle: String(localized: "settings.display.auto_brightness.subtitle", defaultValue: "Adapt brightness to ambient light"), isOn: $style.autoBrightness, accentColor: AppAccent.primary ) @@ -73,8 +73,8 @@ struct BasicDisplaySection: View { .padding(.horizontal, Design.Spacing.medium) SettingsToggle( - title: "Horizontal Mode", - subtitle: "Force a wide layout in portrait", + title: String(localized: "settings.display.horizontal_mode.title", defaultValue: "Horizontal Mode"), + subtitle: String(localized: "settings.display.horizontal_mode.subtitle", defaultValue: "Force a wide layout in portrait"), isOn: $style.forceHorizontalMode, accentColor: AppAccent.primary ) @@ -82,7 +82,7 @@ struct BasicDisplaySection: View { } } - Text("Basic display settings for your clock.") + Text(String(localized: "settings.display.footer", defaultValue: "Basic display settings for your clock.")) .font(.caption) .foregroundStyle(AppTextColors.tertiary) } diff --git a/TheNoiseClock/Features/Clock/Views/Components/Settings/FontSection.swift b/TheNoiseClock/Features/Clock/Views/Components/Settings/FontSection.swift index 3ab76fa..b5cdd9e 100644 --- a/TheNoiseClock/Features/Clock/Views/Components/Settings/FontSection.swift +++ b/TheNoiseClock/Features/Clock/Views/Components/Settings/FontSection.swift @@ -31,7 +31,7 @@ struct FontSection: View { var body: some View { VStack(alignment: .leading, spacing: Design.Spacing.small) { SettingsSectionHeader( - title: "Font", + title: String(localized: "settings.font.section_title", defaultValue: "Font"), systemImage: "textformat", accentColor: AppAccent.primary ) @@ -40,14 +40,14 @@ struct FontSection: View { VStack(alignment: .leading, spacing: 0) { // Font Family SettingsNavigationRow( - title: "Family", + title: String(localized: "settings.font.family.title", defaultValue: "Family"), subtitle: style.fontFamily.rawValue, backgroundColor: .clear ) { SettingsSelectionView( selection: $style.fontFamily, options: sortedFontFamilies, - title: "Font Family", + title: String(localized: "settings.font.family.selection_title", defaultValue: "Font Family"), toString: { $0.rawValue } ) } @@ -65,18 +65,18 @@ struct FontSection: View { Rectangle() .fill(AppBorder.subtle) .frame(height: 1) - .padding(.horizontal, Design.Spacing.medium) + .padding(.horizontal, Design.Spacing.small) // Font Weight SettingsNavigationRow( - title: "Weight", + title: String(localized: "settings.font.weight.title", defaultValue: "Weight"), subtitle: style.fontWeight.rawValue, backgroundColor: .clear ) { SettingsSelectionView( selection: $style.fontWeight, options: availableWeights, - title: "Font Weight", + title: String(localized: "settings.font.weight.selection_title", defaultValue: "Font Weight"), toString: { $0.rawValue } ) } @@ -89,14 +89,14 @@ struct FontSection: View { // Font Design SettingsNavigationRow( - title: "Design", + title: String(localized: "settings.font.design.title", defaultValue: "Design"), subtitle: style.fontDesign.rawValue, backgroundColor: .clear ) { SettingsSelectionView( selection: $style.fontDesign, options: Font.Design.allCases, - title: "Font Design", + title: String(localized: "settings.font.design.selection_title", defaultValue: "Font Design"), toString: { $0.rawValue } ) } @@ -108,15 +108,14 @@ struct FontSection: View { .padding(.horizontal, Design.Spacing.medium) HStack { - Text("Preview") - .font(.subheadline.weight(.medium)) - .foregroundStyle(AppTextColors.secondary) + Text(String(localized: "settings.font.preview.title", defaultValue: "Preview")).styled(.subheadingEmphasis) Spacer() - Text("12:34") + Text(String(localized: "settings.font.preview.sample_time", defaultValue: "12:34")) .font(FontUtils.createFont(name: style.fontFamily, weight: style.fontWeight, design: style.fontDesign, size: 24)) .foregroundStyle(AppTextColors.primary) } - .padding(Design.Spacing.medium) + .padding(.vertical, Design.Spacing.small) + .padding(.horizontal, Design.Spacing.small) } } } diff --git a/TheNoiseClock/Features/Clock/Views/Components/Settings/NightModeSection.swift b/TheNoiseClock/Features/Clock/Views/Components/Settings/NightModeSection.swift index 6731e8f..2955ebf 100644 --- a/TheNoiseClock/Features/Clock/Views/Components/Settings/NightModeSection.swift +++ b/TheNoiseClock/Features/Clock/Views/Components/Settings/NightModeSection.swift @@ -14,7 +14,7 @@ struct NightModeSection: View { var body: some View { VStack(alignment: .leading, spacing: Design.Spacing.small) { SettingsSectionHeader( - title: "Night Mode", + title: String(localized: "settings.night_mode.section_title", defaultValue: "Night Mode"), systemImage: "moon.stars.fill", accentColor: AppAccent.primary ) @@ -22,8 +22,8 @@ struct NightModeSection: View { SettingsCard(backgroundColor: AppSurface.card, borderColor: AppBorder.subtle) { VStack(spacing: 0) { SettingsToggle( - title: "Enable Night Mode", - subtitle: "Use a red clock for low light", + title: String(localized: "settings.night_mode.enable.title", defaultValue: "Enable Night Mode"), + subtitle: String(localized: "settings.night_mode.enable.subtitle", defaultValue: "Use a red clock for low light"), isOn: $style.nightModeEnabled, accentColor: AppAccent.primary ) @@ -34,8 +34,8 @@ struct NightModeSection: View { .padding(.horizontal, Design.Spacing.medium) SettingsToggle( - title: "Auto Night Mode", - subtitle: "Trigger based on ambient light", + title: String(localized: "settings.night_mode.auto.title", defaultValue: "Auto Night Mode"), + subtitle: String(localized: "settings.night_mode.auto.subtitle", defaultValue: "Trigger based on ambient light"), isOn: $style.autoNightMode, accentColor: AppAccent.primary ) @@ -47,8 +47,8 @@ struct NightModeSection: View { .padding(.horizontal, Design.Spacing.medium) SettingsSlider( - title: "Light Threshold", - subtitle: "Lower values activate sooner", + title: String(localized: "settings.night_mode.light_threshold.title", defaultValue: "Light Threshold"), + subtitle: String(localized: "settings.night_mode.light_threshold.subtitle", defaultValue: "Lower values activate sooner"), value: $style.ambientLightThreshold, in: 0.1...0.8, step: 0.01, @@ -63,8 +63,8 @@ struct NightModeSection: View { .padding(.horizontal, Design.Spacing.medium) SettingsToggle( - title: "Scheduled Night Mode", - subtitle: "Enable on a daily schedule", + title: String(localized: "settings.night_mode.scheduled.title", defaultValue: "Scheduled Night Mode"), + subtitle: String(localized: "settings.night_mode.scheduled.subtitle", defaultValue: "Enable on a daily schedule"), isOn: $style.scheduledNightMode, accentColor: AppAccent.primary ) @@ -76,7 +76,7 @@ struct NightModeSection: View { .padding(.horizontal, Design.Spacing.medium) HStack { - Text("Start Time") + Text(String(localized: "settings.night_mode.start_time", defaultValue: "Start Time")) .font(.subheadline.weight(.medium)) .foregroundStyle(AppTextColors.primary) Spacer() @@ -91,7 +91,7 @@ struct NightModeSection: View { .padding(.horizontal, Design.Spacing.medium) HStack { - Text("End Time") + Text(String(localized: "settings.night_mode.end_time", defaultValue: "End Time")) .font(.subheadline.weight(.medium)) .foregroundStyle(AppTextColors.primary) Spacer() @@ -110,7 +110,7 @@ struct NightModeSection: View { HStack(spacing: Design.Spacing.xSmall) { Image(systemName: "moon.fill") .foregroundStyle(AppStatus.error) - Text("Night Mode Active") + Text(String(localized: "settings.night_mode.active", defaultValue: "Night Mode Active")) .font(.subheadline.weight(.medium)) .foregroundStyle(AppStatus.error) Spacer() @@ -121,7 +121,7 @@ struct NightModeSection: View { } } - Text("Night mode displays the clock in red to reduce eye strain in low light environments.") + Text(String(localized: "settings.night_mode.footer", defaultValue: "Night mode displays the clock in red to reduce eye strain in low light environments.")) .font(.caption) .foregroundStyle(AppTextColors.tertiary) } diff --git a/TheNoiseClock/Features/Clock/Views/Components/Settings/OverlaySection.swift b/TheNoiseClock/Features/Clock/Views/Components/Settings/OverlaySection.swift index 343c495..0bcf367 100644 --- a/TheNoiseClock/Features/Clock/Views/Components/Settings/OverlaySection.swift +++ b/TheNoiseClock/Features/Clock/Views/Components/Settings/OverlaySection.swift @@ -16,7 +16,7 @@ struct OverlaySection: View { var body: some View { VStack(alignment: .leading, spacing: Design.Spacing.small) { SettingsSectionHeader( - title: "Overlays", + title: String(localized: "settings.overlays.section_title", defaultValue: "Overlays"), systemImage: "rectangle.on.rectangle.angled", accentColor: AppAccent.primary ) @@ -24,8 +24,8 @@ struct OverlaySection: View { SettingsCard(backgroundColor: AppSurface.card, borderColor: AppBorder.subtle) { VStack(spacing: 0) { SettingsToggle( - title: "Battery Level", - subtitle: "Show battery percentage", + title: String(localized: "settings.overlays.battery_level.title", defaultValue: "Battery Level"), + subtitle: String(localized: "settings.overlays.battery_level.subtitle", defaultValue: "Show battery percentage"), isOn: $style.showBattery, accentColor: AppAccent.primary ) @@ -36,8 +36,8 @@ struct OverlaySection: View { .padding(.horizontal, Design.Spacing.medium) SettingsToggle( - title: "Date", - subtitle: "Display the current date", + title: String(localized: "settings.overlays.date.title", defaultValue: "Date"), + subtitle: String(localized: "settings.overlays.date.subtitle", defaultValue: "Display the current date"), isOn: $style.showDate, accentColor: AppAccent.primary ) @@ -48,8 +48,8 @@ struct OverlaySection: View { .padding(.horizontal, Design.Spacing.medium) SettingsToggle( - title: "Next Alarm", - subtitle: "Show your next scheduled alarm", + title: String(localized: "settings.overlays.next_alarm.title", defaultValue: "Next Alarm"), + subtitle: String(localized: "settings.overlays.next_alarm.subtitle", defaultValue: "Show your next scheduled alarm"), isOn: $style.showNextAlarm, accentColor: AppAccent.primary ) @@ -60,8 +60,8 @@ struct OverlaySection: View { .padding(.horizontal, Design.Spacing.medium) SettingsToggle( - title: "Noise Controls", - subtitle: "Mini-player for white noise", + title: String(localized: "settings.overlays.noise_controls.title", defaultValue: "Noise Controls"), + subtitle: String(localized: "settings.overlays.noise_controls.subtitle", defaultValue: "Mini-player for white noise"), isOn: $style.showNoiseControls, accentColor: AppAccent.primary ) @@ -73,14 +73,14 @@ struct OverlaySection: View { .padding(.horizontal, Design.Spacing.medium) SettingsNavigationRow( - title: "Date Format", + title: String(localized: "settings.overlays.date_format.title", defaultValue: "Date Format"), subtitle: style.dateFormat, backgroundColor: .clear ) { SettingsSelectionView( selection: $style.dateFormat, options: dateFormats.map { $0.1 }, - title: "Date Format", + title: String(localized: "settings.overlays.date_format.title", defaultValue: "Date Format"), toString: { format in dateFormats.first(where: { $0.1 == format })?.0 ?? format } diff --git a/TheNoiseClock/Features/Clock/Views/Components/TimeDisplayView.swift b/TheNoiseClock/Features/Clock/Views/Components/TimeDisplayView.swift index 99c76dd..5edabb1 100644 --- a/TheNoiseClock/Features/Clock/Views/Components/TimeDisplayView.swift +++ b/TheNoiseClock/Features/Clock/Views/Components/TimeDisplayView.swift @@ -229,12 +229,22 @@ struct TimeDisplayView: View { ) VStack(alignment: .leading, spacing: 2) { - Text("Container: \(Int(containerSize.width))×\(Int(containerSize.height))") - Text("Digit Slot: \(Int(digitSlotSize.width))×\(Int(digitSlotSize.height))") - Text("Font Size: \(Int(fontSize))") - Text("Cols: \(Int(digitColumns)) Rows: \(Int(digitRows))") - Text("Portrait: \(portrait ? "true" : "false")") - Text("Family: \(fontFamily.rawValue)") + Text( + String( + localized: "clock.debug.container_size", + defaultValue: "Container: \(Int(containerSize.width))×\(Int(containerSize.height))" + ) + ) + Text( + String( + localized: "clock.debug.digit_slot_size", + defaultValue: "Digit Slot: \(Int(digitSlotSize.width))×\(Int(digitSlotSize.height))" + ) + ) + Text(String(localized: "clock.debug.font_size", defaultValue: "Font Size: \(Int(fontSize))")) + Text(String(localized: "clock.debug.grid", defaultValue: "Cols: \(Int(digitColumns)) Rows: \(Int(digitRows))")) + Text(String(localized: "clock.debug.portrait", defaultValue: "Portrait: \(portrait ? "true" : "false")")) + Text(String(localized: "clock.debug.font_family", defaultValue: "Family: \(fontFamily.rawValue)")) } .font(.system(size: 10, weight: .bold, design: .monospaced)) .foregroundStyle(.yellow) diff --git a/TheNoiseClock/Features/Noise/Views/Components/SoundCategoryView.swift b/TheNoiseClock/Features/Noise/Views/Components/SoundCategoryView.swift index 7fe4af1..fac5e8b 100644 --- a/TheNoiseClock/Features/Noise/Views/Components/SoundCategoryView.swift +++ b/TheNoiseClock/Features/Noise/Views/Components/SoundCategoryView.swift @@ -134,7 +134,7 @@ struct CategoryTab: View { .styled(.subheadingEmphasis) if count > 0 { - Text("\(count)") + Text(count, format: .number) .styled(.caption, emphasis: .secondary) .padding(.horizontal, 6) .padding(.vertical, 2) diff --git a/TheNoiseClock/Features/Noise/Views/Components/SoundControlView.swift b/TheNoiseClock/Features/Noise/Views/Components/SoundControlView.swift index fe91715..a314435 100644 --- a/TheNoiseClock/Features/Noise/Views/Components/SoundControlView.swift +++ b/TheNoiseClock/Features/Noise/Views/Components/SoundControlView.swift @@ -62,7 +62,11 @@ struct SoundControlView: View { .font(.title2.weight(.semibold)) .foregroundStyle(.white) - Text(isPlaying ? "Stop Sound" : "Play Sound") + Text( + isPlaying + ? String(localized: "noise.control.stop_sound", defaultValue: "Stop Sound") + : String(localized: "noise.control.play_sound", defaultValue: "Play Sound") + ) .font(.headline.weight(.semibold)) .foregroundStyle(.white) } @@ -79,7 +83,11 @@ struct SoundControlView: View { .animation(.easeInOut(duration: 0.2), value: isPlaying) .animation(.easeInOut(duration: 0.2), value: selectedSound) .accessibilityIdentifier("noise.playStopButton") - .accessibilityLabel(isPlaying ? "Stop Sound" : "Play Sound") + .accessibilityLabel( + isPlaying + ? String(localized: "noise.control.stop_sound", defaultValue: "Stop Sound") + : String(localized: "noise.control.play_sound", defaultValue: "Play Sound") + ) } .frame(maxWidth: 400) // Reasonable max width for iPad .padding(Design.Spacing.medium) diff --git a/TheNoiseClock/Features/Noise/Views/NoiseView.swift b/TheNoiseClock/Features/Noise/Views/NoiseView.swift index add9b7d..d93e5cd 100644 --- a/TheNoiseClock/Features/Noise/Views/NoiseView.swift +++ b/TheNoiseClock/Features/Noise/Views/NoiseView.swift @@ -44,7 +44,10 @@ struct NoiseView: View { Image(systemName: "magnifyingglass") .foregroundStyle(AppTextColors.secondary) - TextField("Search sounds", text: $searchText) + TextField( + String(localized: "noise.search.placeholder", defaultValue: "Search sounds"), + text: $searchText + ) .textFieldStyle(.plain) .foregroundStyle(AppTextColors.primary) .accessibilityIdentifier("noise.searchField") @@ -79,7 +82,7 @@ struct NoiseView: View { .frame(maxWidth: .infinity, alignment: .center) } } - .navigationTitle("Noise") + .navigationTitle(String(localized: "noise.navigation_title", defaultValue: "Noise")) .navigationBarTitleDisplayMode(.inline) .animation(.easeInOut(duration: 0.3), value: selectedSound) .accessibilityIdentifier("noise.screen") @@ -125,11 +128,16 @@ struct NoiseView: View { } VStack(spacing: 4) { - Text("Ready for Sleep?") + Text(String(localized: "noise.empty.title", defaultValue: "Ready for Sleep?")) .typography(.title3Bold) .foregroundStyle(AppTextColors.primary) - Text("Select a soothing sound below to begin your relaxation journey.") + Text( + String( + localized: "noise.empty.subtitle_portrait", + defaultValue: "Select a soothing sound below to begin your relaxation journey." + ) + ) .typography(.caption) .foregroundStyle(AppTextColors.secondary) .multilineTextAlignment(.center) @@ -170,7 +178,7 @@ struct NoiseView: View { // Left side: Player controls VStack(alignment: .leading, spacing: Design.Spacing.medium) { if !isPad { - Text("Ambient Sounds") + Text(String(localized: "noise.ambient_sounds", defaultValue: "Ambient Sounds")) .sectionTitleStyle() } @@ -191,11 +199,11 @@ struct NoiseView: View { } VStack(spacing: 4) { - Text("Ready for Sleep?") + Text(String(localized: "noise.empty.title", defaultValue: "Ready for Sleep?")) .typography(.title3Bold) .foregroundStyle(AppTextColors.primary) - Text("Select a soothing sound to begin.") + Text(String(localized: "noise.empty.subtitle_landscape", defaultValue: "Select a soothing sound to begin.")) .typography(.caption) .foregroundStyle(AppTextColors.secondary) .multilineTextAlignment(.center) diff --git a/TheNoiseClock/Features/Onboarding/Views/Components/OnboardingBottomControls.swift b/TheNoiseClock/Features/Onboarding/Views/Components/OnboardingBottomControls.swift index bea87e5..d3948aa 100644 --- a/TheNoiseClock/Features/Onboarding/Views/Components/OnboardingBottomControls.swift +++ b/TheNoiseClock/Features/Onboarding/Views/Components/OnboardingBottomControls.swift @@ -37,7 +37,11 @@ struct OnboardingBottomControls: View { onSkip() } } label: { - Text(currentPage == 0 ? "Skip" : "Back") + Text( + currentPage == 0 + ? String(localized: "onboarding.controls.skip", defaultValue: "Skip") + : String(localized: "onboarding.controls.back", defaultValue: "Back") + ) .typography(.bodyEmphasis) .foregroundStyle(AppTextColors.secondary) .frame(maxWidth: .infinity) @@ -52,7 +56,11 @@ struct OnboardingBottomControls: View { onFinish() } } label: { - Text(currentPage == totalPages - 1 ? "Get Started" : "Next") + Text( + currentPage == totalPages - 1 + ? String(localized: "onboarding.controls.get_started", defaultValue: "Get Started") + : String(localized: "onboarding.controls.next", defaultValue: "Next") + ) .typography(.bodyEmphasis) .foregroundStyle(.white) .frame(maxWidth: .infinity) diff --git a/TheNoiseClock/Features/Onboarding/Views/Components/OnboardingGetStartedPage.swift b/TheNoiseClock/Features/Onboarding/Views/Components/OnboardingGetStartedPage.swift index 65a30f3..1f8b201 100644 --- a/TheNoiseClock/Features/Onboarding/Views/Components/OnboardingGetStartedPage.swift +++ b/TheNoiseClock/Features/Onboarding/Views/Components/OnboardingGetStartedPage.swift @@ -25,22 +25,22 @@ struct OnboardingGetStartedPage: View { .foregroundStyle(AppStatus.success) } - Text("You're ready!") + Text(String(localized: "onboarding.get_started.title", defaultValue: "You're ready!")) .typography(.heroBold) .foregroundStyle(AppTextColors.primary) - Text("Your alarms will work even in silent mode and Focus mode. The interface will automatically fade out to give you a clean view of the time!") + Text(String(localized: "onboarding.get_started.subtitle", defaultValue: "Your alarms will work even in silent mode and Focus mode. The interface will automatically fade out to give you a clean view of the time!")) .typography(.body) .foregroundStyle(AppTextColors.secondary) .multilineTextAlignment(.center) .padding(.horizontal, Design.Spacing.xxLarge) VStack(alignment: .leading, spacing: Design.Spacing.small) { - OnboardingFeatureRow(icon: "alarm.fill", text: "Create your first alarm") - OnboardingFeatureRow(icon: "repeat", text: "Set repeat days (weekdays/weekends)") - OnboardingFeatureRow(icon: "slider.horizontal.3", text: "Customize vibration, flash, and volume") - OnboardingFeatureRow(icon: "clock.fill", text: "Wait 5s for full screen") - OnboardingFeatureRow(icon: "speaker.wave.2", text: "Tap Noise to play sounds") + OnboardingFeatureRow(icon: "alarm.fill", text: String(localized: "onboarding.get_started.feature_first_alarm", defaultValue: "Create your first alarm")) + OnboardingFeatureRow(icon: "repeat", text: String(localized: "onboarding.get_started.feature_repeat_days", defaultValue: "Set repeat days (weekdays/weekends)")) + OnboardingFeatureRow(icon: "slider.horizontal.3", text: String(localized: "onboarding.get_started.feature_alert_options", defaultValue: "Customize vibration, flash, and volume")) + OnboardingFeatureRow(icon: "clock.fill", text: String(localized: "onboarding.get_started.feature_full_screen", defaultValue: "Wait 5s for full screen")) + OnboardingFeatureRow(icon: "speaker.wave.2", text: String(localized: "onboarding.get_started.feature_noise", defaultValue: "Tap Noise to play sounds")) } .padding(.top, Design.Spacing.medium) diff --git a/TheNoiseClock/Features/Onboarding/Views/Components/OnboardingPermissionsPage.swift b/TheNoiseClock/Features/Onboarding/Views/Components/OnboardingPermissionsPage.swift index cd72ad3..4fcb98b 100644 --- a/TheNoiseClock/Features/Onboarding/Views/Components/OnboardingPermissionsPage.swift +++ b/TheNoiseClock/Features/Onboarding/Views/Components/OnboardingPermissionsPage.swift @@ -33,23 +33,23 @@ struct OnboardingPermissionsPage: View { .symbolEffect(.pulse, options: .repeating) } - Text("Alarms that actually work") + Text(String(localized: "onboarding.permissions.title", defaultValue: "Alarms that actually work")) .typography(.heroBold) .foregroundStyle(AppTextColors.primary) .multilineTextAlignment(.center) .fixedSize(horizontal: false, vertical: true) .padding(.horizontal, Design.Spacing.large) - Text("Works in silent mode, Focus mode, and even when your phone is locked. You can then set repeat days and customize alert behavior.") + Text(String(localized: "onboarding.permissions.subtitle", defaultValue: "Works in silent mode, Focus mode, and even when your phone is locked. You can then set repeat days and customize alert behavior.")) .typography(.body) .foregroundStyle(AppTextColors.secondary) .multilineTextAlignment(.center) .padding(.horizontal, Design.Spacing.xxLarge) VStack(alignment: .leading, spacing: Design.Spacing.small) { - OnboardingFeatureRow(icon: "moon.zzz.fill", text: "Cuts through Do Not Disturb") - OnboardingFeatureRow(icon: "lock.iphone", text: "Shows countdown on Lock Screen") - OnboardingFeatureRow(icon: "iphone.badge.play", text: "Works when app is closed") + OnboardingFeatureRow(icon: "moon.zzz.fill", text: String(localized: "onboarding.permissions.feature_dnd", defaultValue: "Cuts through Do Not Disturb")) + OnboardingFeatureRow(icon: "lock.iphone", text: String(localized: "onboarding.permissions.feature_lock_screen", defaultValue: "Shows countdown on Lock Screen")) + OnboardingFeatureRow(icon: "iphone.badge.play", text: String(localized: "onboarding.permissions.feature_app_closed", defaultValue: "Works when app is closed")) } .padding(.top, Design.Spacing.medium) @@ -76,7 +76,7 @@ struct OnboardingPermissionsPage: View { HStack(spacing: Design.Spacing.small) { Image(systemName: "checkmark.circle.fill") .font(.system(size: 24)) - Text("Alarms enabled!") + Text(String(localized: "onboarding.permissions.enabled", defaultValue: "Alarms enabled!")) } .foregroundStyle(AppStatus.success) .typography(.bodyEmphasis) @@ -89,7 +89,7 @@ struct OnboardingPermissionsPage: View { } label: { HStack { Image(systemName: "alarm.fill") - Text("Enable Alarms") + Text(String(localized: "onboarding.permissions.enable_alarms", defaultValue: "Enable Alarms")) } .typography(.bodyEmphasis) .foregroundStyle(.white) @@ -106,7 +106,7 @@ struct OnboardingPermissionsPage: View { private var keepAwakeSection: some View { VStack(spacing: Design.Spacing.small) { - Text("Want the clock always visible?") + Text(String(localized: "onboarding.permissions.keep_awake_prompt", defaultValue: "Want the clock always visible?")) .typography(.callout) .foregroundStyle(AppTextColors.tertiary) .multilineTextAlignment(.center) @@ -116,7 +116,11 @@ struct OnboardingPermissionsPage: View { } label: { HStack(spacing: Design.Spacing.small) { Image(systemName: keepAwakeEnabled ? "checkmark.circle.fill" : "bolt.fill") - Text(keepAwakeEnabled ? "Keep Awake Enabled" : "Enable Keep Awake") + Text( + keepAwakeEnabled + ? String(localized: "onboarding.permissions.keep_awake_enabled", defaultValue: "Keep Awake Enabled") + : String(localized: "onboarding.permissions.enable_keep_awake", defaultValue: "Enable Keep Awake") + ) } .typography(.callout) .foregroundStyle(keepAwakeEnabled ? AppStatus.success : AppTextColors.secondary) diff --git a/TheNoiseClock/Features/Onboarding/Views/Components/OnboardingWelcomePage.swift b/TheNoiseClock/Features/Onboarding/Views/Components/OnboardingWelcomePage.swift index 786fc7f..21af04a 100644 --- a/TheNoiseClock/Features/Onboarding/Views/Components/OnboardingWelcomePage.swift +++ b/TheNoiseClock/Features/Onboarding/Views/Components/OnboardingWelcomePage.swift @@ -20,26 +20,26 @@ struct OnboardingWelcomePage: View { } .padding(.bottom, Design.Spacing.medium) - Text("The Noise Clock") + Text(String(localized: "onboarding.welcome.title", defaultValue: "The Noise Clock")) .typography(.heroBold) .foregroundStyle(AppTextColors.primary) - Text("Your beautiful bedside companion") + Text(String(localized: "onboarding.welcome.subtitle", defaultValue: "Your beautiful bedside companion")) .typography(.title3) .foregroundStyle(AppTextColors.secondary) VStack(spacing: Design.Spacing.medium) { OnboardingFeatureRow( icon: "moon.stars.fill", - text: "Fall asleep to soothing sounds" + text: String(localized: "onboarding.welcome.feature_sleep_sounds", defaultValue: "Fall asleep to soothing sounds") ) OnboardingFeatureRow( icon: "alarm.fill", - text: "Wake up your way with custom alarms" + text: String(localized: "onboarding.welcome.feature_custom_alarms", defaultValue: "Wake up your way with custom alarms") ) OnboardingFeatureRow( icon: "clock.fill", - text: "Automatic full-screen display" + text: String(localized: "onboarding.welcome.feature_full_screen", defaultValue: "Automatic full-screen display") ) } .padding(.top, Design.Spacing.large) diff --git a/TheNoiseClock/Localizable.xcstrings b/TheNoiseClock/Localizable.xcstrings new file mode 100644 index 0000000..1aad63c --- /dev/null +++ b/TheNoiseClock/Localizable.xcstrings @@ -0,0 +1,4406 @@ +{ + "sourceLanguage": "en", + "strings": { + "alarm.add.navigation_title": { + "localizations": { + "en": { + "stringUnit": { + "state": "translated", + "value": "Alarm" + } + }, + "es-MX": { + "stringUnit": { + "state": "translated", + "value": "Alarm" + } + }, + "fr-CA": { + "stringUnit": { + "state": "translated", + "value": "Alarm" + } + } + } + }, + "alarm.alert_options.flash_screen": { + "localizations": { + "en": { + "stringUnit": { + "state": "translated", + "value": "Flash Screen" + } + }, + "es-MX": { + "stringUnit": { + "state": "translated", + "value": "Flash Screen" + } + }, + "fr-CA": { + "stringUnit": { + "state": "translated", + "value": "Flash Screen" + } + } + } + }, + "alarm.alert_options.section_title": { + "localizations": { + "en": { + "stringUnit": { + "state": "translated", + "value": "Alert Options" + } + }, + "es-MX": { + "stringUnit": { + "state": "translated", + "value": "Alert Options" + } + }, + "fr-CA": { + "stringUnit": { + "state": "translated", + "value": "Alert Options" + } + } + } + }, + "alarm.alert_options.vibration": { + "localizations": { + "en": { + "stringUnit": { + "state": "translated", + "value": "Vibration" + } + }, + "es-MX": { + "stringUnit": { + "state": "translated", + "value": "Vibration" + } + }, + "fr-CA": { + "stringUnit": { + "state": "translated", + "value": "Vibration" + } + } + } + }, + "alarm.alert_options.volume": { + "localizations": { + "en": { + "stringUnit": { + "state": "translated", + "value": "Volume" + } + }, + "es-MX": { + "stringUnit": { + "state": "translated", + "value": "Volume" + } + }, + "fr-CA": { + "stringUnit": { + "state": "translated", + "value": "Volume" + } + } + } + }, + "alarm.default_label": { + "localizations": { + "en": { + "stringUnit": { + "state": "translated", + "value": "Alarm" + } + }, + "es-MX": { + "stringUnit": { + "state": "translated", + "value": "Alarma" + } + }, + "fr-CA": { + "stringUnit": { + "state": "translated", + "value": "Alarme" + } + } + } + }, + "alarm.default_notification_message": { + "localizations": { + "en": { + "stringUnit": { + "state": "translated", + "value": "Your alarm is ringing" + } + }, + "es-MX": { + "stringUnit": { + "state": "translated", + "value": "Tu alarma está sonando" + } + }, + "fr-CA": { + "stringUnit": { + "state": "translated", + "value": "Votre alarme sonne" + } + } + } + }, + "alarm.edit.navigation_title": { + "localizations": { + "en": { + "stringUnit": { + "state": "translated", + "value": "Edit Alarm" + } + }, + "es-MX": { + "stringUnit": { + "state": "translated", + "value": "Edit Alarm" + } + }, + "fr-CA": { + "stringUnit": { + "state": "translated", + "value": "Edit Alarm" + } + } + } + }, + "alarm.editor.label": { + "localizations": { + "en": { + "stringUnit": { + "state": "translated", + "value": "Label" + } + }, + "es-MX": { + "stringUnit": { + "state": "translated", + "value": "Label" + } + }, + "fr-CA": { + "stringUnit": { + "state": "translated", + "value": "Label" + } + } + } + }, + "alarm.editor.message": { + "localizations": { + "en": { + "stringUnit": { + "state": "translated", + "value": "Message" + } + }, + "es-MX": { + "stringUnit": { + "state": "translated", + "value": "Message" + } + }, + "fr-CA": { + "stringUnit": { + "state": "translated", + "value": "Message" + } + } + } + }, + "alarm.editor.repeat": { + "localizations": { + "en": { + "stringUnit": { + "state": "translated", + "value": "Repeat" + } + }, + "es-MX": { + "stringUnit": { + "state": "translated", + "value": "Repeat" + } + }, + "fr-CA": { + "stringUnit": { + "state": "translated", + "value": "Repeat" + } + } + } + }, + "alarm.editor.snooze": { + "localizations": { + "en": { + "stringUnit": { + "state": "translated", + "value": "Snooze" + } + }, + "es-MX": { + "stringUnit": { + "state": "translated", + "value": "Snooze" + } + }, + "fr-CA": { + "stringUnit": { + "state": "translated", + "value": "Snooze" + } + } + } + }, + "alarm.editor.sound": { + "localizations": { + "en": { + "stringUnit": { + "state": "translated", + "value": "Sound" + } + }, + "es-MX": { + "stringUnit": { + "state": "translated", + "value": "Sound" + } + }, + "fr-CA": { + "stringUnit": { + "state": "translated", + "value": "Sound" + } + } + } + }, + "alarm.error.title": { + "localizations": { + "en": { + "stringUnit": { + "state": "translated", + "value": "Alarm Error" + } + }, + "es-MX": { + "stringUnit": { + "state": "translated", + "value": "Alarm Error" + } + }, + "fr-CA": { + "stringUnit": { + "state": "translated", + "value": "Alarm Error" + } + } + } + }, + "alarm.label.footer": { + "localizations": { + "en": { + "stringUnit": { + "state": "translated", + "value": "Enter a name for your alarm." + } + }, + "es-MX": { + "stringUnit": { + "state": "translated", + "value": "Enter a name for your alarm." + } + }, + "fr-CA": { + "stringUnit": { + "state": "translated", + "value": "Enter a name for your alarm." + } + } + } + }, + "alarm.label.navigation_title": { + "localizations": { + "en": { + "stringUnit": { + "state": "translated", + "value": "Label" + } + }, + "es-MX": { + "stringUnit": { + "state": "translated", + "value": "Label" + } + }, + "fr-CA": { + "stringUnit": { + "state": "translated", + "value": "Label" + } + } + } + }, + "alarm.label.placeholder": { + "localizations": { + "en": { + "stringUnit": { + "state": "translated", + "value": "Alarm Label" + } + }, + "es-MX": { + "stringUnit": { + "state": "translated", + "value": "Alarm Label" + } + }, + "fr-CA": { + "stringUnit": { + "state": "translated", + "value": "Alarm Label" + } + } + } + }, + "alarm.message.footer": { + "localizations": { + "en": { + "stringUnit": { + "state": "translated", + "value": "This message will appear when the alarm rings." + } + }, + "es-MX": { + "stringUnit": { + "state": "translated", + "value": "Este mensaje aparecerá cuando suene la alarma." + } + }, + "fr-CA": { + "stringUnit": { + "state": "translated", + "value": "Ce message s'affichera lorsque l'alarme sonnera." + } + } + } + }, + "alarm.message.navigation_title": { + "localizations": { + "en": { + "stringUnit": { + "state": "translated", + "value": "Message" + } + }, + "es-MX": { + "stringUnit": { + "state": "translated", + "value": "Message" + } + }, + "fr-CA": { + "stringUnit": { + "state": "translated", + "value": "Message" + } + } + } + }, + "alarm.operation.error_message": { + "localizations": { + "en": { + "stringUnit": { + "state": "translated", + "value": "Unable to %@ alarm. %@" + } + }, + "es-MX": { + "stringUnit": { + "state": "translated", + "value": "No se pudo %@ la alarma. %@" + } + }, + "fr-CA": { + "stringUnit": { + "state": "translated", + "value": "Impossible de %@ l’alarme. %@" + } + } + } + }, + "alarm.repeat.every_day": { + "localizations": { + "en": { + "stringUnit": { + "state": "translated", + "value": "Every day" + } + }, + "es-MX": { + "stringUnit": { + "state": "translated", + "value": "Todos los días" + } + }, + "fr-CA": { + "stringUnit": { + "state": "translated", + "value": "Tous les jours" + } + } + } + }, + "alarm.repeat.navigation_title": { + "localizations": { + "en": { + "stringUnit": { + "state": "translated", + "value": "Repeat" + } + }, + "es-MX": { + "stringUnit": { + "state": "translated", + "value": "Repeat" + } + }, + "fr-CA": { + "stringUnit": { + "state": "translated", + "value": "Repeat" + } + } + } + }, + "alarm.repeat.once": { + "localizations": { + "en": { + "stringUnit": { + "state": "translated", + "value": "Once" + } + }, + "es-MX": { + "stringUnit": { + "state": "translated", + "value": "Una vez" + } + }, + "fr-CA": { + "stringUnit": { + "state": "translated", + "value": "Une fois" + } + } + } + }, + "alarm.repeat.quick_picks_section": { + "localizations": { + "en": { + "stringUnit": { + "state": "translated", + "value": "Quick Picks" + } + }, + "es-MX": { + "stringUnit": { + "state": "translated", + "value": "Quick Picks" + } + }, + "fr-CA": { + "stringUnit": { + "state": "translated", + "value": "Quick Picks" + } + } + } + }, + "alarm.repeat.repeat_on_section": { + "localizations": { + "en": { + "stringUnit": { + "state": "translated", + "value": "Repeat On" + } + }, + "es-MX": { + "stringUnit": { + "state": "translated", + "value": "Repeat On" + } + }, + "fr-CA": { + "stringUnit": { + "state": "translated", + "value": "Repeat On" + } + } + } + }, + "alarm.repeat.weekdays": { + "localizations": { + "en": { + "stringUnit": { + "state": "translated", + "value": "Weekdays" + } + }, + "es-MX": { + "stringUnit": { + "state": "translated", + "value": "Días laborables" + } + }, + "fr-CA": { + "stringUnit": { + "state": "translated", + "value": "Jours de semaine" + } + } + } + }, + "alarm.repeat.weekends": { + "localizations": { + "en": { + "stringUnit": { + "state": "translated", + "value": "Weekends" + } + }, + "es-MX": { + "stringUnit": { + "state": "translated", + "value": "Fines de semana" + } + }, + "fr-CA": { + "stringUnit": { + "state": "translated", + "value": "Fins de semaine" + } + } + } + }, + "alarm.row.accessibility_label": { + "localizations": { + "en": { + "stringUnit": { + "state": "translated", + "value": "%@, %@" + } + }, + "es-MX": { + "stringUnit": { + "state": "translated", + "value": "%@, %@" + } + }, + "fr-CA": { + "stringUnit": { + "state": "translated", + "value": "%@, %@" + } + } + } + }, + "alarm.row.keep_awake_warning": { + "localizations": { + "en": { + "stringUnit": { + "state": "translated", + "value": "Foreground only for full alarm sound" + } + }, + "es-MX": { + "stringUnit": { + "state": "translated", + "value": "Foreground only for full alarm sound" + } + }, + "fr-CA": { + "stringUnit": { + "state": "translated", + "value": "Foreground only for full alarm sound" + } + } + } + }, + "alarm.snooze.duration_section": { + "localizations": { + "en": { + "stringUnit": { + "state": "translated", + "value": "Snooze Duration" + } + }, + "es-MX": { + "stringUnit": { + "state": "translated", + "value": "Snooze Duration" + } + }, + "fr-CA": { + "stringUnit": { + "state": "translated", + "value": "Snooze Duration" + } + } + } + }, + "alarm.snooze.navigation_title": { + "localizations": { + "en": { + "stringUnit": { + "state": "translated", + "value": "Snooze" + } + }, + "es-MX": { + "stringUnit": { + "state": "translated", + "value": "Snooze" + } + }, + "fr-CA": { + "stringUnit": { + "state": "translated", + "value": "Snooze" + } + } + } + }, + "alarm.sound.navigation_title": { + "localizations": { + "en": { + "stringUnit": { + "state": "translated", + "value": "Sound" + } + }, + "es-MX": { + "stringUnit": { + "state": "translated", + "value": "Sound" + } + }, + "fr-CA": { + "stringUnit": { + "state": "translated", + "value": "Sound" + } + } + } + }, + "alarm.sound.section_title": { + "localizations": { + "en": { + "stringUnit": { + "state": "translated", + "value": "Alarm Sounds" + } + }, + "es-MX": { + "stringUnit": { + "state": "translated", + "value": "Alarm Sounds" + } + }, + "fr-CA": { + "stringUnit": { + "state": "translated", + "value": "Alarm Sounds" + } + } + } + }, + "alarm.time_until.hours_minutes": { + "localizations": { + "en": { + "stringUnit": { + "state": "translated", + "value": "Will turn on in %lldh %lldm" + } + }, + "es-MX": { + "stringUnit": { + "state": "translated", + "value": "Se activará en %lld h %lld min" + } + }, + "fr-CA": { + "stringUnit": { + "state": "translated", + "value": "S’activera dans %lld h %lld min" + } + } + } + }, + "alarm.time_until.minutes": { + "localizations": { + "en": { + "stringUnit": { + "state": "translated", + "value": "Will turn on in %lldm" + } + }, + "es-MX": { + "stringUnit": { + "state": "translated", + "value": "Se activará en %lld min" + } + }, + "fr-CA": { + "stringUnit": { + "state": "translated", + "value": "S’activera dans %lld min" + } + } + } + }, + "alarm.time_until.now": { + "localizations": { + "en": { + "stringUnit": { + "state": "translated", + "value": "Will turn on now" + } + }, + "es-MX": { + "stringUnit": { + "state": "translated", + "value": "Se activará ahora" + } + }, + "fr-CA": { + "stringUnit": { + "state": "translated", + "value": "S’active maintenant" + } + } + } + }, + "alarm.time_until.tomorrow_fallback": { + "localizations": { + "en": { + "stringUnit": { + "state": "translated", + "value": "Will turn on tomorrow" + } + }, + "es-MX": { + "stringUnit": { + "state": "translated", + "value": "Se activará mañana" + } + }, + "fr-CA": { + "stringUnit": { + "state": "translated", + "value": "S’activera demain" + } + } + } + }, + "alarm_intent.error.alarm_not_found": { + "localizations": { + "en": { + "stringUnit": { + "state": "translated", + "value": "Alarm not found" + } + }, + "es-MX": { + "stringUnit": { + "state": "translated", + "value": "Alarma no encontrada" + } + }, + "fr-CA": { + "stringUnit": { + "state": "translated", + "value": "Alarme introuvable" + } + } + } + }, + "alarm_intent.error.invalid_alarm_id": { + "localizations": { + "en": { + "stringUnit": { + "state": "translated", + "value": "Invalid alarm ID" + } + }, + "es-MX": { + "stringUnit": { + "state": "translated", + "value": "ID de alarma no válido" + } + }, + "fr-CA": { + "stringUnit": { + "state": "translated", + "value": "ID d’alarme invalide" + } + } + } + }, + "alarm_intent.open_app.title": { + "localizations": { + "en": { + "stringUnit": { + "state": "translated", + "value": "Open TheNoiseClock" + } + }, + "es-MX": { + "stringUnit": { + "state": "translated", + "value": "Open TheNoiseClock" + } + }, + "fr-CA": { + "stringUnit": { + "state": "translated", + "value": "Open TheNoiseClock" + } + } + } + }, + "alarm_intent.parameter.alarm_id": { + "localizations": { + "en": { + "stringUnit": { + "state": "translated", + "value": "Alarm ID" + } + }, + "es-MX": { + "stringUnit": { + "state": "translated", + "value": "Alarm ID" + } + }, + "fr-CA": { + "stringUnit": { + "state": "translated", + "value": "Alarm ID" + } + } + } + }, + "alarm_intent.snooze.title": { + "localizations": { + "en": { + "stringUnit": { + "state": "translated", + "value": "Snooze Alarm" + } + }, + "es-MX": { + "stringUnit": { + "state": "translated", + "value": "Snooze Alarm" + } + }, + "fr-CA": { + "stringUnit": { + "state": "translated", + "value": "Snooze Alarm" + } + } + } + }, + "alarm_intent.stop.title": { + "localizations": { + "en": { + "stringUnit": { + "state": "translated", + "value": "Stop Alarm" + } + }, + "es-MX": { + "stringUnit": { + "state": "translated", + "value": "Stop Alarm" + } + }, + "fr-CA": { + "stringUnit": { + "state": "translated", + "value": "Stop Alarm" + } + } + } + }, + "alarmkit.error.not_authorized": { + "localizations": { + "en": { + "stringUnit": { + "state": "translated", + "value": "AlarmKit is not authorized. Please enable alarm permissions in Settings." + } + }, + "es-MX": { + "stringUnit": { + "state": "translated", + "value": "AlarmKit no está autorizado. Activa los permisos de alarma en Configuración." + } + }, + "fr-CA": { + "stringUnit": { + "state": "translated", + "value": "AlarmKit n’est pas autorisé. Activez les permissions d’alarme dans Réglages." + } + } + } + }, + "alarmkit.error.scheduling_failed": { + "localizations": { + "en": { + "stringUnit": { + "state": "translated", + "value": "Failed to schedule alarm: %@" + } + }, + "es-MX": { + "stringUnit": { + "state": "translated", + "value": "No se pudo programar la alarma: %@" + } + }, + "fr-CA": { + "stringUnit": { + "state": "translated", + "value": "Échec de planification de l’alarme : %@" + } + } + } + }, + "alarms.empty.cta": { + "localizations": { + "en": { + "stringUnit": { + "state": "translated", + "value": "Add Your First Alarm" + } + }, + "es-MX": { + "stringUnit": { + "state": "translated", + "value": "Add Your First Alarm" + } + }, + "fr-CA": { + "stringUnit": { + "state": "translated", + "value": "Add Your First Alarm" + } + } + } + }, + "alarms.empty.subtitle": { + "localizations": { + "en": { + "stringUnit": { + "state": "translated", + "value": "Create an alarm to wake up gently on your own terms." + } + }, + "es-MX": { + "stringUnit": { + "state": "translated", + "value": "Create an alarm to wake up gently on your own terms." + } + }, + "fr-CA": { + "stringUnit": { + "state": "translated", + "value": "Create an alarm to wake up gently on your own terms." + } + } + } + }, + "alarms.empty.title": { + "localizations": { + "en": { + "stringUnit": { + "state": "translated", + "value": "No Alarms Set" + } + }, + "es-MX": { + "stringUnit": { + "state": "translated", + "value": "No Alarms Set" + } + }, + "fr-CA": { + "stringUnit": { + "state": "translated", + "value": "No Alarms Set" + } + } + } + }, + "alarms.navigation_title": { + "localizations": { + "en": { + "stringUnit": { + "state": "translated", + "value": "Alarms" + } + }, + "es-MX": { + "stringUnit": { + "state": "translated", + "value": "Alarms" + } + }, + "fr-CA": { + "stringUnit": { + "state": "translated", + "value": "Alarms" + } + } + } + }, + "calendar.today": { + "localizations": { + "en": { + "stringUnit": { + "state": "translated", + "value": "Today" + } + }, + "es-MX": { + "stringUnit": { + "state": "translated", + "value": "Hoy" + } + }, + "fr-CA": { + "stringUnit": { + "state": "translated", + "value": "Aujourd’hui" + } + } + } + }, + "calendar.tomorrow": { + "localizations": { + "en": { + "stringUnit": { + "state": "translated", + "value": "Tomorrow" + } + }, + "es-MX": { + "stringUnit": { + "state": "translated", + "value": "Mañana" + } + }, + "fr-CA": { + "stringUnit": { + "state": "translated", + "value": "Demain" + } + } + } + }, + "clock.accessibility.current_time": { + "localizations": { + "en": { + "stringUnit": { + "state": "translated", + "value": "Current time" + } + }, + "es-MX": { + "stringUnit": { + "state": "translated", + "value": "Hora actual" + } + }, + "fr-CA": { + "stringUnit": { + "state": "translated", + "value": "Heure actuelle" + } + } + } + }, + "clock.debug.font_family": { + "localizations": { + "en": { + "stringUnit": { + "state": "translated", + "value": "Family: \\(fontFamily.rawValue)" + } + }, + "es-MX": { + "stringUnit": { + "state": "translated", + "value": "Family: \\(fontFamily.rawValue)" + } + }, + "fr-CA": { + "stringUnit": { + "state": "translated", + "value": "Family: \\(fontFamily.rawValue)" + } + } + } + }, + "clock.debug.font_size": { + "localizations": { + "en": { + "stringUnit": { + "state": "translated", + "value": "Font Size: \\(Int(fontSize))" + } + }, + "es-MX": { + "stringUnit": { + "state": "translated", + "value": "Font Size: \\(Int(fontSize))" + } + }, + "fr-CA": { + "stringUnit": { + "state": "translated", + "value": "Font Size: \\(Int(fontSize))" + } + } + } + }, + "clock.debug.grid": { + "localizations": { + "en": { + "stringUnit": { + "state": "translated", + "value": "Cols: \\(Int(digitColumns)) Rows: \\(Int(digitRows))" + } + }, + "es-MX": { + "stringUnit": { + "state": "translated", + "value": "Cols: \\(Int(digitColumns)) Rows: \\(Int(digitRows))" + } + }, + "fr-CA": { + "stringUnit": { + "state": "translated", + "value": "Cols: \\(Int(digitColumns)) Rows: \\(Int(digitRows))" + } + } + } + }, + "clock.debug.screen": { + "localizations": { + "en": { + "stringUnit": { + "state": "translated", + "value": "Screen: \\(Int(size.width))×\\(Int(size.height))" + } + }, + "es-MX": { + "stringUnit": { + "state": "translated", + "value": "Screen: \\(Int(size.width))×\\(Int(size.height))" + } + }, + "fr-CA": { + "stringUnit": { + "state": "translated", + "value": "Screen: \\(Int(size.width))×\\(Int(size.height))" + } + } + } + }, + "clock.debug.symmetric_inset": { + "localizations": { + "en": { + "stringUnit": { + "state": "translated", + "value": "Symmetric Inset: \\(Int(symmetricInset))" + } + }, + "es-MX": { + "stringUnit": { + "state": "translated", + "value": "Symmetric Inset: \\(Int(symmetricInset))" + } + }, + "fr-CA": { + "stringUnit": { + "state": "translated", + "value": "Symmetric Inset: \\(Int(symmetricInset))" + } + } + } + }, + "clock.time.colon": { + "localizations": { + "en": { + "stringUnit": { + "state": "translated", + "value": ":" + } + }, + "es-MX": { + "stringUnit": { + "state": "translated", + "value": ":" + } + }, + "fr-CA": { + "stringUnit": { + "state": "translated", + "value": ":" + } + } + } + }, + "common.cancel": { + "localizations": { + "en": { + "stringUnit": { + "state": "translated", + "value": "Cancel" + } + }, + "es-MX": { + "stringUnit": { + "state": "translated", + "value": "Cancel" + } + }, + "fr-CA": { + "stringUnit": { + "state": "translated", + "value": "Cancel" + } + } + } + }, + "common.delete": { + "localizations": { + "en": { + "stringUnit": { + "state": "translated", + "value": "Delete" + } + }, + "es-MX": { + "stringUnit": { + "state": "translated", + "value": "Delete" + } + }, + "fr-CA": { + "stringUnit": { + "state": "translated", + "value": "Delete" + } + } + } + }, + "common.disabled": { + "localizations": { + "en": { + "stringUnit": { + "state": "translated", + "value": "Disabled" + } + }, + "es-MX": { + "stringUnit": { + "state": "translated", + "value": "Desactivada" + } + }, + "fr-CA": { + "stringUnit": { + "state": "translated", + "value": "Désactivée" + } + } + } + }, + "common.enabled": { + "localizations": { + "en": { + "stringUnit": { + "state": "translated", + "value": "Enabled" + } + }, + "es-MX": { + "stringUnit": { + "state": "translated", + "value": "Activada" + } + }, + "fr-CA": { + "stringUnit": { + "state": "translated", + "value": "Activée" + } + } + } + }, + "common.not_now": { + "localizations": { + "en": { + "stringUnit": { + "state": "translated", + "value": "Not Now" + } + }, + "es-MX": { + "stringUnit": { + "state": "translated", + "value": "Not Now" + } + }, + "fr-CA": { + "stringUnit": { + "state": "translated", + "value": "Not Now" + } + } + } + }, + "common.ok": { + "localizations": { + "en": { + "stringUnit": { + "state": "translated", + "value": "OK" + } + }, + "es-MX": { + "stringUnit": { + "state": "translated", + "value": "OK" + } + }, + "fr-CA": { + "stringUnit": { + "state": "translated", + "value": "OK" + } + } + } + }, + "common.save": { + "localizations": { + "en": { + "stringUnit": { + "state": "translated", + "value": "Save" + } + }, + "es-MX": { + "stringUnit": { + "state": "translated", + "value": "Save" + } + }, + "fr-CA": { + "stringUnit": { + "state": "translated", + "value": "Save" + } + } + } + }, + "common.saving": { + "localizations": { + "en": { + "stringUnit": { + "state": "translated", + "value": "Saving..." + } + }, + "es-MX": { + "stringUnit": { + "state": "translated", + "value": "Saving..." + } + }, + "fr-CA": { + "stringUnit": { + "state": "translated", + "value": "Saving..." + } + } + } + }, + "digit_animation.bounce.display_name": { + "localizations": { + "en": { + "stringUnit": { + "state": "translated", + "value": "Bounce" + } + }, + "es-MX": { + "stringUnit": { + "state": "translated", + "value": "Rebote" + } + }, + "fr-CA": { + "stringUnit": { + "state": "translated", + "value": "Rebond" + } + } + } + }, + "digit_animation.glitch.display_name": { + "localizations": { + "en": { + "stringUnit": { + "state": "translated", + "value": "Glitch" + } + }, + "es-MX": { + "stringUnit": { + "state": "translated", + "value": "Glitch" + } + }, + "fr-CA": { + "stringUnit": { + "state": "translated", + "value": "Glitch" + } + } + } + }, + "digit_animation.none.display_name": { + "localizations": { + "en": { + "stringUnit": { + "state": "translated", + "value": "None" + } + }, + "es-MX": { + "stringUnit": { + "state": "translated", + "value": "Ninguno" + } + }, + "fr-CA": { + "stringUnit": { + "state": "translated", + "value": "Aucune" + } + } + } + }, + "digit_animation.spring.display_name": { + "localizations": { + "en": { + "stringUnit": { + "state": "translated", + "value": "Spring" + } + }, + "es-MX": { + "stringUnit": { + "state": "translated", + "value": "Resorte" + } + }, + "fr-CA": { + "stringUnit": { + "state": "translated", + "value": "Ressort" + } + } + } + }, + "keep_awake.prompt.enable": { + "localizations": { + "en": { + "stringUnit": { + "state": "translated", + "value": "Enable Keep Awake" + } + }, + "es-MX": { + "stringUnit": { + "state": "translated", + "value": "Enable Keep Awake" + } + }, + "fr-CA": { + "stringUnit": { + "state": "translated", + "value": "Enable Keep Awake" + } + } + } + }, + "keep_awake.prompt.subtitle": { + "localizations": { + "en": { + "stringUnit": { + "state": "translated", + "value": "Enable Keep Awake so your alarm can play loudly and show the full screen while TheNoiseClock stays open." + } + }, + "es-MX": { + "stringUnit": { + "state": "translated", + "value": "Enable Keep Awake so your alarm can play loudly and show the full screen while TheNoiseClock stays open." + } + }, + "fr-CA": { + "stringUnit": { + "state": "translated", + "value": "Enable Keep Awake so your alarm can play loudly and show the full screen while TheNoiseClock stays open." + } + } + } + }, + "keep_awake.prompt.title": { + "localizations": { + "en": { + "stringUnit": { + "state": "translated", + "value": "Keep Awake for Alarms" + } + }, + "es-MX": { + "stringUnit": { + "state": "translated", + "value": "Keep Awake for Alarms" + } + }, + "fr-CA": { + "stringUnit": { + "state": "translated", + "value": "Keep Awake for Alarms" + } + } + } + }, + "noise.ambient_sounds": { + "localizations": { + "en": { + "stringUnit": { + "state": "translated", + "value": "Ambient Sounds" + } + }, + "es-MX": { + "stringUnit": { + "state": "translated", + "value": "Ambient Sounds" + } + }, + "fr-CA": { + "stringUnit": { + "state": "translated", + "value": "Ambient Sounds" + } + } + } + }, + "noise.control.play_sound": { + "localizations": { + "en": { + "stringUnit": { + "state": "translated", + "value": "Play Sound" + } + }, + "es-MX": { + "stringUnit": { + "state": "translated", + "value": "Reproducir sonido" + } + }, + "fr-CA": { + "stringUnit": { + "state": "translated", + "value": "Lire le son" + } + } + } + }, + "noise.control.stop_sound": { + "localizations": { + "en": { + "stringUnit": { + "state": "translated", + "value": "Stop Sound" + } + }, + "es-MX": { + "stringUnit": { + "state": "translated", + "value": "Detener sonido" + } + }, + "fr-CA": { + "stringUnit": { + "state": "translated", + "value": "Arrêter le son" + } + } + } + }, + "noise.empty.subtitle_landscape": { + "localizations": { + "en": { + "stringUnit": { + "state": "translated", + "value": "Select a soothing sound to begin." + } + }, + "es-MX": { + "stringUnit": { + "state": "translated", + "value": "Select a soothing sound to begin." + } + }, + "fr-CA": { + "stringUnit": { + "state": "translated", + "value": "Select a soothing sound to begin." + } + } + } + }, + "noise.empty.title": { + "localizations": { + "en": { + "stringUnit": { + "state": "translated", + "value": "Ready for Sleep?" + } + }, + "es-MX": { + "stringUnit": { + "state": "translated", + "value": "Ready for Sleep?" + } + }, + "fr-CA": { + "stringUnit": { + "state": "translated", + "value": "Ready for Sleep?" + } + } + } + }, + "noise.navigation_title": { + "localizations": { + "en": { + "stringUnit": { + "state": "translated", + "value": "Noise" + } + }, + "es-MX": { + "stringUnit": { + "state": "translated", + "value": "Noise" + } + }, + "fr-CA": { + "stringUnit": { + "state": "translated", + "value": "Noise" + } + } + } + }, + "noise.search.placeholder": { + "localizations": { + "en": { + "stringUnit": { + "state": "translated", + "value": "Search sounds" + } + }, + "es-MX": { + "stringUnit": { + "state": "translated", + "value": "Search sounds" + } + }, + "fr-CA": { + "stringUnit": { + "state": "translated", + "value": "Search sounds" + } + } + } + }, + "onboarding.get_started.feature_alert_options": { + "localizations": { + "en": { + "stringUnit": { + "state": "translated", + "value": "Customize vibration, flash, and volume" + } + }, + "es-MX": { + "stringUnit": { + "state": "translated", + "value": "Customize vibration, flash, and volume" + } + }, + "fr-CA": { + "stringUnit": { + "state": "translated", + "value": "Customize vibration, flash, and volume" + } + } + } + }, + "onboarding.get_started.feature_first_alarm": { + "localizations": { + "en": { + "stringUnit": { + "state": "translated", + "value": "Create your first alarm" + } + }, + "es-MX": { + "stringUnit": { + "state": "translated", + "value": "Create your first alarm" + } + }, + "fr-CA": { + "stringUnit": { + "state": "translated", + "value": "Create your first alarm" + } + } + } + }, + "onboarding.get_started.feature_full_screen": { + "localizations": { + "en": { + "stringUnit": { + "state": "translated", + "value": "Wait 5s for full screen" + } + }, + "es-MX": { + "stringUnit": { + "state": "translated", + "value": "Wait 5s for full screen" + } + }, + "fr-CA": { + "stringUnit": { + "state": "translated", + "value": "Wait 5s for full screen" + } + } + } + }, + "onboarding.get_started.feature_noise": { + "localizations": { + "en": { + "stringUnit": { + "state": "translated", + "value": "Tap Noise to play sounds" + } + }, + "es-MX": { + "stringUnit": { + "state": "translated", + "value": "Tap Noise to play sounds" + } + }, + "fr-CA": { + "stringUnit": { + "state": "translated", + "value": "Tap Noise to play sounds" + } + } + } + }, + "onboarding.get_started.feature_repeat_days": { + "localizations": { + "en": { + "stringUnit": { + "state": "translated", + "value": "Set repeat days (weekdays/weekends)" + } + }, + "es-MX": { + "stringUnit": { + "state": "translated", + "value": "Set repeat days (weekdays/weekends)" + } + }, + "fr-CA": { + "stringUnit": { + "state": "translated", + "value": "Set repeat days (weekdays/weekends)" + } + } + } + }, + "onboarding.get_started.subtitle": { + "localizations": { + "en": { + "stringUnit": { + "state": "translated", + "value": "Your alarms will work even in silent mode and Focus mode. The interface will automatically fade out to give you a clean view of the time!" + } + }, + "es-MX": { + "stringUnit": { + "state": "translated", + "value": "Tus alarmas sonarán incluso en modo silencio y en modo Concentración. La interfaz se atenuará automáticamente para ofrecerte una vista limpia de la hora." + } + }, + "fr-CA": { + "stringUnit": { + "state": "translated", + "value": "Vos alarmes sonneront même en mode silencieux et en mode Concentration. L'interface s'estompera automatiquement pour vous offrir une vue claire de l'heure." + } + } + } + }, + "onboarding.get_started.title": { + "localizations": { + "en": { + "stringUnit": { + "state": "translated", + "value": "You're ready!" + } + }, + "es-MX": { + "stringUnit": { + "state": "translated", + "value": "You're ready!" + } + }, + "fr-CA": { + "stringUnit": { + "state": "translated", + "value": "You're ready!" + } + } + } + }, + "onboarding.permissions.enable_alarms": { + "localizations": { + "en": { + "stringUnit": { + "state": "translated", + "value": "Enable Alarms" + } + }, + "es-MX": { + "stringUnit": { + "state": "translated", + "value": "Enable Alarms" + } + }, + "fr-CA": { + "stringUnit": { + "state": "translated", + "value": "Enable Alarms" + } + } + } + }, + "onboarding.permissions.enable_keep_awake": { + "localizations": { + "en": { + "stringUnit": { + "state": "translated", + "value": "Enable Keep Awake" + } + }, + "es-MX": { + "stringUnit": { + "state": "translated", + "value": "Enable Keep Awake" + } + }, + "fr-CA": { + "stringUnit": { + "state": "translated", + "value": "Enable Keep Awake" + } + } + } + }, + "onboarding.permissions.enabled": { + "localizations": { + "en": { + "stringUnit": { + "state": "translated", + "value": "Alarms enabled!" + } + }, + "es-MX": { + "stringUnit": { + "state": "translated", + "value": "Alarms enabled!" + } + }, + "fr-CA": { + "stringUnit": { + "state": "translated", + "value": "Alarms enabled!" + } + } + } + }, + "onboarding.permissions.feature_app_closed": { + "localizations": { + "en": { + "stringUnit": { + "state": "translated", + "value": "Works when app is closed" + } + }, + "es-MX": { + "stringUnit": { + "state": "translated", + "value": "Works when app is closed" + } + }, + "fr-CA": { + "stringUnit": { + "state": "translated", + "value": "Works when app is closed" + } + } + } + }, + "onboarding.permissions.feature_dnd": { + "localizations": { + "en": { + "stringUnit": { + "state": "translated", + "value": "Cuts through Do Not Disturb" + } + }, + "es-MX": { + "stringUnit": { + "state": "translated", + "value": "Cuts through Do Not Disturb" + } + }, + "fr-CA": { + "stringUnit": { + "state": "translated", + "value": "Cuts through Do Not Disturb" + } + } + } + }, + "onboarding.permissions.feature_lock_screen": { + "localizations": { + "en": { + "stringUnit": { + "state": "translated", + "value": "Shows countdown on Lock Screen" + } + }, + "es-MX": { + "stringUnit": { + "state": "translated", + "value": "Shows countdown on Lock Screen" + } + }, + "fr-CA": { + "stringUnit": { + "state": "translated", + "value": "Shows countdown on Lock Screen" + } + } + } + }, + "onboarding.permissions.keep_awake_enabled": { + "localizations": { + "en": { + "stringUnit": { + "state": "translated", + "value": "Keep Awake Enabled" + } + }, + "es-MX": { + "stringUnit": { + "state": "translated", + "value": "Keep Awake Enabled" + } + }, + "fr-CA": { + "stringUnit": { + "state": "translated", + "value": "Keep Awake Enabled" + } + } + } + }, + "onboarding.permissions.keep_awake_prompt": { + "localizations": { + "en": { + "stringUnit": { + "state": "translated", + "value": "Want the clock always visible?" + } + }, + "es-MX": { + "stringUnit": { + "state": "translated", + "value": "Want the clock always visible?" + } + }, + "fr-CA": { + "stringUnit": { + "state": "translated", + "value": "Want the clock always visible?" + } + } + } + }, + "onboarding.permissions.subtitle": { + "localizations": { + "en": { + "stringUnit": { + "state": "translated", + "value": "Works in silent mode, Focus mode, and even when your phone is locked. You can then set repeat days and customize alert behavior." + } + }, + "es-MX": { + "stringUnit": { + "state": "translated", + "value": "Funciona en modo silencio, modo Concentración e incluso cuando tu teléfono está bloqueado. Luego puedes configurar días de repetición y personalizar el comportamiento de la alerta." + } + }, + "fr-CA": { + "stringUnit": { + "state": "translated", + "value": "Fonctionne en mode silencieux, en mode Concentration et même lorsque votre téléphone est verrouillé. Vous pouvez ensuite définir des jours de répétition et personnaliser le comportement de l'alerte." + } + } + } + }, + "onboarding.permissions.title": { + "localizations": { + "en": { + "stringUnit": { + "state": "translated", + "value": "Alarms that actually work" + } + }, + "es-MX": { + "stringUnit": { + "state": "translated", + "value": "Alarms that actually work" + } + }, + "fr-CA": { + "stringUnit": { + "state": "translated", + "value": "Alarms that actually work" + } + } + } + }, + "onboarding.welcome.feature_custom_alarms": { + "localizations": { + "en": { + "stringUnit": { + "state": "translated", + "value": "Wake up your way with custom alarms" + } + }, + "es-MX": { + "stringUnit": { + "state": "translated", + "value": "Wake up your way with custom alarms" + } + }, + "fr-CA": { + "stringUnit": { + "state": "translated", + "value": "Wake up your way with custom alarms" + } + } + } + }, + "onboarding.welcome.feature_full_screen": { + "localizations": { + "en": { + "stringUnit": { + "state": "translated", + "value": "Automatic full-screen display" + } + }, + "es-MX": { + "stringUnit": { + "state": "translated", + "value": "Automatic full-screen display" + } + }, + "fr-CA": { + "stringUnit": { + "state": "translated", + "value": "Automatic full-screen display" + } + } + } + }, + "onboarding.welcome.feature_sleep_sounds": { + "localizations": { + "en": { + "stringUnit": { + "state": "translated", + "value": "Fall asleep to soothing sounds" + } + }, + "es-MX": { + "stringUnit": { + "state": "translated", + "value": "Fall asleep to soothing sounds" + } + }, + "fr-CA": { + "stringUnit": { + "state": "translated", + "value": "Fall asleep to soothing sounds" + } + } + } + }, + "onboarding.welcome.subtitle": { + "localizations": { + "en": { + "stringUnit": { + "state": "translated", + "value": "Your beautiful bedside companion" + } + }, + "es-MX": { + "stringUnit": { + "state": "translated", + "value": "Your beautiful bedside companion" + } + }, + "fr-CA": { + "stringUnit": { + "state": "translated", + "value": "Your beautiful bedside companion" + } + } + } + }, + "onboarding.welcome.title": { + "localizations": { + "en": { + "stringUnit": { + "state": "translated", + "value": "The Noise Clock" + } + }, + "es-MX": { + "stringUnit": { + "state": "translated", + "value": "The Noise Clock" + } + }, + "fr-CA": { + "stringUnit": { + "state": "translated", + "value": "The Noise Clock" + } + } + } + }, + "settings.advanced_appearance.clock_opacity.subtitle": { + "localizations": { + "en": { + "stringUnit": { + "state": "translated", + "value": "Set the clock transparency" + } + }, + "es-MX": { + "stringUnit": { + "state": "translated", + "value": "Set the clock transparency" + } + }, + "fr-CA": { + "stringUnit": { + "state": "translated", + "value": "Set the clock transparency" + } + } + } + }, + "settings.advanced_appearance.clock_opacity.title": { + "localizations": { + "en": { + "stringUnit": { + "state": "translated", + "value": "Clock Opacity" + } + }, + "es-MX": { + "stringUnit": { + "state": "translated", + "value": "Clock Opacity" + } + }, + "fr-CA": { + "stringUnit": { + "state": "translated", + "value": "Clock Opacity" + } + } + } + }, + "settings.advanced_appearance.digit_animation.title": { + "localizations": { + "en": { + "stringUnit": { + "state": "translated", + "value": "Digit Animation" + } + }, + "es-MX": { + "stringUnit": { + "state": "translated", + "value": "Digit Animation" + } + }, + "fr-CA": { + "stringUnit": { + "state": "translated", + "value": "Digit Animation" + } + } + } + }, + "settings.advanced_appearance.footer": { + "localizations": { + "en": { + "stringUnit": { + "state": "translated", + "value": "Fine-tune the visual appearance of your clock." + } + }, + "es-MX": { + "stringUnit": { + "state": "translated", + "value": "Fine-tune the visual appearance of your clock." + } + }, + "fr-CA": { + "stringUnit": { + "state": "translated", + "value": "Fine-tune the visual appearance of your clock." + } + } + } + }, + "settings.advanced_appearance.glow.subtitle": { + "localizations": { + "en": { + "stringUnit": { + "state": "translated", + "value": "Adjust the glow intensity" + } + }, + "es-MX": { + "stringUnit": { + "state": "translated", + "value": "Adjust the glow intensity" + } + }, + "fr-CA": { + "stringUnit": { + "state": "translated", + "value": "Adjust the glow intensity" + } + } + } + }, + "settings.advanced_appearance.glow.title": { + "localizations": { + "en": { + "stringUnit": { + "state": "translated", + "value": "Glow" + } + }, + "es-MX": { + "stringUnit": { + "state": "translated", + "value": "Glow" + } + }, + "fr-CA": { + "stringUnit": { + "state": "translated", + "value": "Glow" + } + } + } + }, + "settings.advanced_appearance.randomize_color.subtitle": { + "localizations": { + "en": { + "stringUnit": { + "state": "translated", + "value": "Shift the color every minute" + } + }, + "es-MX": { + "stringUnit": { + "state": "translated", + "value": "Shift the color every minute" + } + }, + "fr-CA": { + "stringUnit": { + "state": "translated", + "value": "Shift the color every minute" + } + } + } + }, + "settings.advanced_appearance.randomize_color.title": { + "localizations": { + "en": { + "stringUnit": { + "state": "translated", + "value": "Randomize Color" + } + }, + "es-MX": { + "stringUnit": { + "state": "translated", + "value": "Randomize Color" + } + }, + "fr-CA": { + "stringUnit": { + "state": "translated", + "value": "Randomize Color" + } + } + } + }, + "settings.advanced_appearance.section_title": { + "localizations": { + "en": { + "stringUnit": { + "state": "translated", + "value": "Advanced Appearance" + } + }, + "es-MX": { + "stringUnit": { + "state": "translated", + "value": "Advanced Appearance" + } + }, + "fr-CA": { + "stringUnit": { + "state": "translated", + "value": "Advanced Appearance" + } + } + } + }, + "settings.advanced_display.current_brightness": { + "localizations": { + "en": { + "stringUnit": { + "state": "translated", + "value": "Current Brightness" + } + }, + "es-MX": { + "stringUnit": { + "state": "translated", + "value": "Current Brightness" + } + }, + "fr-CA": { + "stringUnit": { + "state": "translated", + "value": "Current Brightness" + } + } + } + }, + "settings.advanced_display.footer": { + "localizations": { + "en": { + "stringUnit": { + "state": "translated", + "value": "Advanced display and system integration settings. Keep Awake helps alarms stay active while the app remains open." + } + }, + "es-MX": { + "stringUnit": { + "state": "translated", + "value": "Advanced display and system integration settings. Keep Awake helps alarms stay active while the app remains open." + } + }, + "fr-CA": { + "stringUnit": { + "state": "translated", + "value": "Advanced display and system integration settings. Keep Awake helps alarms stay active while the app remains open." + } + } + } + }, + "settings.advanced_display.keep_awake.subtitle": { + "localizations": { + "en": { + "stringUnit": { + "state": "translated", + "value": "Prevent sleep in display mode" + } + }, + "es-MX": { + "stringUnit": { + "state": "translated", + "value": "Prevent sleep in display mode" + } + }, + "fr-CA": { + "stringUnit": { + "state": "translated", + "value": "Prevent sleep in display mode" + } + } + } + }, + "settings.advanced_display.keep_awake.title": { + "localizations": { + "en": { + "stringUnit": { + "state": "translated", + "value": "Keep Awake" + } + }, + "es-MX": { + "stringUnit": { + "state": "translated", + "value": "Keep Awake" + } + }, + "fr-CA": { + "stringUnit": { + "state": "translated", + "value": "Keep Awake" + } + } + } + }, + "settings.advanced_display.section_title": { + "localizations": { + "en": { + "stringUnit": { + "state": "translated", + "value": "Advanced Display" + } + }, + "es-MX": { + "stringUnit": { + "state": "translated", + "value": "Advanced Display" + } + }, + "fr-CA": { + "stringUnit": { + "state": "translated", + "value": "Advanced Display" + } + } + } + }, + "settings.colors.background_color": { + "localizations": { + "en": { + "stringUnit": { + "state": "translated", + "value": "Background Color" + } + }, + "es-MX": { + "stringUnit": { + "state": "translated", + "value": "Background Color" + } + }, + "fr-CA": { + "stringUnit": { + "state": "translated", + "value": "Background Color" + } + } + } + }, + "settings.colors.digit_color": { + "localizations": { + "en": { + "stringUnit": { + "state": "translated", + "value": "Digit Color" + } + }, + "es-MX": { + "stringUnit": { + "state": "translated", + "value": "Digit Color" + } + }, + "fr-CA": { + "stringUnit": { + "state": "translated", + "value": "Digit Color" + } + } + } + }, + "settings.colors.footer": { + "localizations": { + "en": { + "stringUnit": { + "state": "translated", + "value": "Choose your favorite color theme or create a custom look." + } + }, + "es-MX": { + "stringUnit": { + "state": "translated", + "value": "Choose your favorite color theme or create a custom look." + } + }, + "fr-CA": { + "stringUnit": { + "state": "translated", + "value": "Choose your favorite color theme or create a custom look." + } + } + } + }, + "settings.colors.section_title": { + "localizations": { + "en": { + "stringUnit": { + "state": "translated", + "value": "Colors" + } + }, + "es-MX": { + "stringUnit": { + "state": "translated", + "value": "Colors" + } + }, + "fr-CA": { + "stringUnit": { + "state": "translated", + "value": "Colors" + } + } + } + }, + "settings.colors.theme.blue": { + "localizations": { + "en": { + "stringUnit": { + "state": "translated", + "value": "Blue" + } + }, + "es-MX": { + "stringUnit": { + "state": "translated", + "value": "Blue" + } + }, + "fr-CA": { + "stringUnit": { + "state": "translated", + "value": "Blue" + } + } + } + }, + "settings.colors.theme.custom": { + "localizations": { + "en": { + "stringUnit": { + "state": "translated", + "value": "Custom" + } + }, + "es-MX": { + "stringUnit": { + "state": "translated", + "value": "Custom" + } + }, + "fr-CA": { + "stringUnit": { + "state": "translated", + "value": "Custom" + } + } + } + }, + "settings.colors.theme.day": { + "localizations": { + "en": { + "stringUnit": { + "state": "translated", + "value": "Day" + } + }, + "es-MX": { + "stringUnit": { + "state": "translated", + "value": "Day" + } + }, + "fr-CA": { + "stringUnit": { + "state": "translated", + "value": "Day" + } + } + } + }, + "settings.colors.theme.green": { + "localizations": { + "en": { + "stringUnit": { + "state": "translated", + "value": "Green" + } + }, + "es-MX": { + "stringUnit": { + "state": "translated", + "value": "Green" + } + }, + "fr-CA": { + "stringUnit": { + "state": "translated", + "value": "Green" + } + } + } + }, + "settings.colors.theme.night": { + "localizations": { + "en": { + "stringUnit": { + "state": "translated", + "value": "Night" + } + }, + "es-MX": { + "stringUnit": { + "state": "translated", + "value": "Night" + } + }, + "fr-CA": { + "stringUnit": { + "state": "translated", + "value": "Night" + } + } + } + }, + "settings.colors.theme.orange": { + "localizations": { + "en": { + "stringUnit": { + "state": "translated", + "value": "Orange" + } + }, + "es-MX": { + "stringUnit": { + "state": "translated", + "value": "Orange" + } + }, + "fr-CA": { + "stringUnit": { + "state": "translated", + "value": "Orange" + } + } + } + }, + "settings.colors.theme.pink": { + "localizations": { + "en": { + "stringUnit": { + "state": "translated", + "value": "Pink" + } + }, + "es-MX": { + "stringUnit": { + "state": "translated", + "value": "Pink" + } + }, + "fr-CA": { + "stringUnit": { + "state": "translated", + "value": "Pink" + } + } + } + }, + "settings.colors.theme.purple": { + "localizations": { + "en": { + "stringUnit": { + "state": "translated", + "value": "Purple" + } + }, + "es-MX": { + "stringUnit": { + "state": "translated", + "value": "Purple" + } + }, + "fr-CA": { + "stringUnit": { + "state": "translated", + "value": "Purple" + } + } + } + }, + "settings.colors.theme.red": { + "localizations": { + "en": { + "stringUnit": { + "state": "translated", + "value": "Red" + } + }, + "es-MX": { + "stringUnit": { + "state": "translated", + "value": "Red" + } + }, + "fr-CA": { + "stringUnit": { + "state": "translated", + "value": "Red" + } + } + } + }, + "settings.colors.theme.title": { + "localizations": { + "en": { + "stringUnit": { + "state": "translated", + "value": "Color Theme" + } + }, + "es-MX": { + "stringUnit": { + "state": "translated", + "value": "Color Theme" + } + }, + "fr-CA": { + "stringUnit": { + "state": "translated", + "value": "Color Theme" + } + } + } + }, + "settings.colors.theme.white": { + "localizations": { + "en": { + "stringUnit": { + "state": "translated", + "value": "White" + } + }, + "es-MX": { + "stringUnit": { + "state": "translated", + "value": "White" + } + }, + "fr-CA": { + "stringUnit": { + "state": "translated", + "value": "White" + } + } + } + }, + "settings.colors.theme.yellow": { + "localizations": { + "en": { + "stringUnit": { + "state": "translated", + "value": "Yellow" + } + }, + "es-MX": { + "stringUnit": { + "state": "translated", + "value": "Yellow" + } + }, + "fr-CA": { + "stringUnit": { + "state": "translated", + "value": "Yellow" + } + } + } + }, + "settings.debug.branding_preview.subtitle": { + "localizations": { + "en": { + "stringUnit": { + "state": "translated", + "value": "Preview icon and launch screen" + } + }, + "es-MX": { + "stringUnit": { + "state": "translated", + "value": "Preview icon and launch screen" + } + }, + "fr-CA": { + "stringUnit": { + "state": "translated", + "value": "Preview icon and launch screen" + } + } + } + }, + "settings.debug.branding_preview.title": { + "localizations": { + "en": { + "stringUnit": { + "state": "translated", + "value": "Branding Preview" + } + }, + "es-MX": { + "stringUnit": { + "state": "translated", + "value": "Branding Preview" + } + }, + "fr-CA": { + "stringUnit": { + "state": "translated", + "value": "Branding Preview" + } + } + } + }, + "settings.debug.icon_generator.subtitle": { + "localizations": { + "en": { + "stringUnit": { + "state": "translated", + "value": "Generate and save app icon" + } + }, + "es-MX": { + "stringUnit": { + "state": "translated", + "value": "Generate and save app icon" + } + }, + "fr-CA": { + "stringUnit": { + "state": "translated", + "value": "Generate and save app icon" + } + } + } + }, + "settings.debug.icon_generator.title": { + "localizations": { + "en": { + "stringUnit": { + "state": "translated", + "value": "Icon Generator" + } + }, + "es-MX": { + "stringUnit": { + "state": "translated", + "value": "Icon Generator" + } + }, + "fr-CA": { + "stringUnit": { + "state": "translated", + "value": "Icon Generator" + } + } + } + }, + "settings.debug.reset_onboarding.subtitle": { + "localizations": { + "en": { + "stringUnit": { + "state": "translated", + "value": "Show onboarding screens again on next launch" + } + }, + "es-MX": { + "stringUnit": { + "state": "translated", + "value": "Show onboarding screens again on next launch" + } + }, + "fr-CA": { + "stringUnit": { + "state": "translated", + "value": "Show onboarding screens again on next launch" + } + } + } + }, + "settings.debug.reset_onboarding.title": { + "localizations": { + "en": { + "stringUnit": { + "state": "translated", + "value": "Reset Onboarding" + } + }, + "es-MX": { + "stringUnit": { + "state": "translated", + "value": "Reset Onboarding" + } + }, + "fr-CA": { + "stringUnit": { + "state": "translated", + "value": "Reset Onboarding" + } + } + } + }, + "settings.debug.section_title": { + "localizations": { + "en": { + "stringUnit": { + "state": "translated", + "value": "Debug" + } + }, + "es-MX": { + "stringUnit": { + "state": "translated", + "value": "Debug" + } + }, + "fr-CA": { + "stringUnit": { + "state": "translated", + "value": "Debug" + } + } + } + }, + "settings.display.auto_brightness.subtitle": { + "localizations": { + "en": { + "stringUnit": { + "state": "translated", + "value": "Adapt brightness to ambient light" + } + }, + "es-MX": { + "stringUnit": { + "state": "translated", + "value": "Adapt brightness to ambient light" + } + }, + "fr-CA": { + "stringUnit": { + "state": "translated", + "value": "Adapt brightness to ambient light" + } + } + } + }, + "settings.display.auto_brightness.title": { + "localizations": { + "en": { + "stringUnit": { + "state": "translated", + "value": "Auto Brightness" + } + }, + "es-MX": { + "stringUnit": { + "state": "translated", + "value": "Auto Brightness" + } + }, + "fr-CA": { + "stringUnit": { + "state": "translated", + "value": "Auto Brightness" + } + } + } + }, + "settings.display.footer": { + "localizations": { + "en": { + "stringUnit": { + "state": "translated", + "value": "Basic display settings for your clock." + } + }, + "es-MX": { + "stringUnit": { + "state": "translated", + "value": "Basic display settings for your clock." + } + }, + "fr-CA": { + "stringUnit": { + "state": "translated", + "value": "Basic display settings for your clock." + } + } + } + }, + "settings.display.horizontal_mode.subtitle": { + "localizations": { + "en": { + "stringUnit": { + "state": "translated", + "value": "Force a wide layout in portrait" + } + }, + "es-MX": { + "stringUnit": { + "state": "translated", + "value": "Force a wide layout in portrait" + } + }, + "fr-CA": { + "stringUnit": { + "state": "translated", + "value": "Force a wide layout in portrait" + } + } + } + }, + "settings.display.horizontal_mode.title": { + "localizations": { + "en": { + "stringUnit": { + "state": "translated", + "value": "Horizontal Mode" + } + }, + "es-MX": { + "stringUnit": { + "state": "translated", + "value": "Horizontal Mode" + } + }, + "fr-CA": { + "stringUnit": { + "state": "translated", + "value": "Horizontal Mode" + } + } + } + }, + "settings.display.section_title": { + "localizations": { + "en": { + "stringUnit": { + "state": "translated", + "value": "Display" + } + }, + "es-MX": { + "stringUnit": { + "state": "translated", + "value": "Display" + } + }, + "fr-CA": { + "stringUnit": { + "state": "translated", + "value": "Display" + } + } + } + }, + "settings.display.show_ampm.subtitle": { + "localizations": { + "en": { + "stringUnit": { + "state": "translated", + "value": "Add an AM/PM indicator" + } + }, + "es-MX": { + "stringUnit": { + "state": "translated", + "value": "Add an AM/PM indicator" + } + }, + "fr-CA": { + "stringUnit": { + "state": "translated", + "value": "Add an AM/PM indicator" + } + } + } + }, + "settings.display.show_ampm.title": { + "localizations": { + "en": { + "stringUnit": { + "state": "translated", + "value": "Show AM/PM" + } + }, + "es-MX": { + "stringUnit": { + "state": "translated", + "value": "Show AM/PM" + } + }, + "fr-CA": { + "stringUnit": { + "state": "translated", + "value": "Show AM/PM" + } + } + } + }, + "settings.display.show_seconds.subtitle": { + "localizations": { + "en": { + "stringUnit": { + "state": "translated", + "value": "Display seconds in the clock" + } + }, + "es-MX": { + "stringUnit": { + "state": "translated", + "value": "Display seconds in the clock" + } + }, + "fr-CA": { + "stringUnit": { + "state": "translated", + "value": "Display seconds in the clock" + } + } + } + }, + "settings.display.show_seconds.title": { + "localizations": { + "en": { + "stringUnit": { + "state": "translated", + "value": "Show Seconds" + } + }, + "es-MX": { + "stringUnit": { + "state": "translated", + "value": "Show Seconds" + } + }, + "fr-CA": { + "stringUnit": { + "state": "translated", + "value": "Show Seconds" + } + } + } + }, + "settings.display.use_24_hour.subtitle": { + "localizations": { + "en": { + "stringUnit": { + "state": "translated", + "value": "Use military time" + } + }, + "es-MX": { + "stringUnit": { + "state": "translated", + "value": "Use military time" + } + }, + "fr-CA": { + "stringUnit": { + "state": "translated", + "value": "Use military time" + } + } + } + }, + "settings.display.use_24_hour.title": { + "localizations": { + "en": { + "stringUnit": { + "state": "translated", + "value": "24‑Hour Format" + } + }, + "es-MX": { + "stringUnit": { + "state": "translated", + "value": "24‑Hour Format" + } + }, + "fr-CA": { + "stringUnit": { + "state": "translated", + "value": "24‑Hour Format" + } + } + } + }, + "settings.focus_modes.footer": { + "localizations": { + "en": { + "stringUnit": { + "state": "translated", + "value": "Control how the app behaves when Focus modes are active." + } + }, + "es-MX": { + "stringUnit": { + "state": "translated", + "value": "Control how the app behaves when Focus modes are active." + } + }, + "fr-CA": { + "stringUnit": { + "state": "translated", + "value": "Control how the app behaves when Focus modes are active." + } + } + } + }, + "settings.focus_modes.respect_focus.subtitle": { + "localizations": { + "en": { + "stringUnit": { + "state": "translated", + "value": "Follow Do Not Disturb rules" + } + }, + "es-MX": { + "stringUnit": { + "state": "translated", + "value": "Follow Do Not Disturb rules" + } + }, + "fr-CA": { + "stringUnit": { + "state": "translated", + "value": "Follow Do Not Disturb rules" + } + } + } + }, + "settings.focus_modes.respect_focus.title": { + "localizations": { + "en": { + "stringUnit": { + "state": "translated", + "value": "Respect Focus Modes" + } + }, + "es-MX": { + "stringUnit": { + "state": "translated", + "value": "Respect Focus Modes" + } + }, + "fr-CA": { + "stringUnit": { + "state": "translated", + "value": "Respect Focus Modes" + } + } + } + }, + "settings.focus_modes.section_title": { + "localizations": { + "en": { + "stringUnit": { + "state": "translated", + "value": "Focus Modes" + } + }, + "es-MX": { + "stringUnit": { + "state": "translated", + "value": "Focus Modes" + } + }, + "fr-CA": { + "stringUnit": { + "state": "translated", + "value": "Focus Modes" + } + } + } + }, + "settings.font.design.selection_title": { + "localizations": { + "en": { + "stringUnit": { + "state": "translated", + "value": "Font Design" + } + }, + "es-MX": { + "stringUnit": { + "state": "translated", + "value": "Font Design" + } + }, + "fr-CA": { + "stringUnit": { + "state": "translated", + "value": "Font Design" + } + } + } + }, + "settings.font.design.title": { + "localizations": { + "en": { + "stringUnit": { + "state": "translated", + "value": "Design" + } + }, + "es-MX": { + "stringUnit": { + "state": "translated", + "value": "Design" + } + }, + "fr-CA": { + "stringUnit": { + "state": "translated", + "value": "Design" + } + } + } + }, + "settings.font.family.selection_title": { + "localizations": { + "en": { + "stringUnit": { + "state": "translated", + "value": "Font Family" + } + }, + "es-MX": { + "stringUnit": { + "state": "translated", + "value": "Font Family" + } + }, + "fr-CA": { + "stringUnit": { + "state": "translated", + "value": "Font Family" + } + } + } + }, + "settings.font.family.title": { + "localizations": { + "en": { + "stringUnit": { + "state": "translated", + "value": "Family" + } + }, + "es-MX": { + "stringUnit": { + "state": "translated", + "value": "Family" + } + }, + "fr-CA": { + "stringUnit": { + "state": "translated", + "value": "Family" + } + } + } + }, + "settings.font.preview.sample_time": { + "localizations": { + "en": { + "stringUnit": { + "state": "translated", + "value": "12:34" + } + }, + "es-MX": { + "stringUnit": { + "state": "translated", + "value": "12:34" + } + }, + "fr-CA": { + "stringUnit": { + "state": "translated", + "value": "12:34" + } + } + } + }, + "settings.font.preview.title": { + "localizations": { + "en": { + "stringUnit": { + "state": "translated", + "value": "Preview" + } + }, + "es-MX": { + "stringUnit": { + "state": "translated", + "value": "Preview" + } + }, + "fr-CA": { + "stringUnit": { + "state": "translated", + "value": "Preview" + } + } + } + }, + "settings.font.section_title": { + "localizations": { + "en": { + "stringUnit": { + "state": "translated", + "value": "Font" + } + }, + "es-MX": { + "stringUnit": { + "state": "translated", + "value": "Font" + } + }, + "fr-CA": { + "stringUnit": { + "state": "translated", + "value": "Font" + } + } + } + }, + "settings.font.weight.selection_title": { + "localizations": { + "en": { + "stringUnit": { + "state": "translated", + "value": "Font Weight" + } + }, + "es-MX": { + "stringUnit": { + "state": "translated", + "value": "Font Weight" + } + }, + "fr-CA": { + "stringUnit": { + "state": "translated", + "value": "Font Weight" + } + } + } + }, + "settings.font.weight.title": { + "localizations": { + "en": { + "stringUnit": { + "state": "translated", + "value": "Weight" + } + }, + "es-MX": { + "stringUnit": { + "state": "translated", + "value": "Weight" + } + }, + "fr-CA": { + "stringUnit": { + "state": "translated", + "value": "Weight" + } + } + } + }, + "settings.navigation_title": { + "localizations": { + "en": { + "stringUnit": { + "state": "translated", + "value": "Settings" + } + }, + "es-MX": { + "stringUnit": { + "state": "translated", + "value": "Settings" + } + }, + "fr-CA": { + "stringUnit": { + "state": "translated", + "value": "Settings" + } + } + } + }, + "settings.night_mode.active": { + "localizations": { + "en": { + "stringUnit": { + "state": "translated", + "value": "Night Mode Active" + } + }, + "es-MX": { + "stringUnit": { + "state": "translated", + "value": "Night Mode Active" + } + }, + "fr-CA": { + "stringUnit": { + "state": "translated", + "value": "Night Mode Active" + } + } + } + }, + "settings.night_mode.auto.subtitle": { + "localizations": { + "en": { + "stringUnit": { + "state": "translated", + "value": "Trigger based on ambient light" + } + }, + "es-MX": { + "stringUnit": { + "state": "translated", + "value": "Trigger based on ambient light" + } + }, + "fr-CA": { + "stringUnit": { + "state": "translated", + "value": "Trigger based on ambient light" + } + } + } + }, + "settings.night_mode.auto.title": { + "localizations": { + "en": { + "stringUnit": { + "state": "translated", + "value": "Auto Night Mode" + } + }, + "es-MX": { + "stringUnit": { + "state": "translated", + "value": "Auto Night Mode" + } + }, + "fr-CA": { + "stringUnit": { + "state": "translated", + "value": "Auto Night Mode" + } + } + } + }, + "settings.night_mode.enable.subtitle": { + "localizations": { + "en": { + "stringUnit": { + "state": "translated", + "value": "Use a red clock for low light" + } + }, + "es-MX": { + "stringUnit": { + "state": "translated", + "value": "Use a red clock for low light" + } + }, + "fr-CA": { + "stringUnit": { + "state": "translated", + "value": "Use a red clock for low light" + } + } + } + }, + "settings.night_mode.enable.title": { + "localizations": { + "en": { + "stringUnit": { + "state": "translated", + "value": "Enable Night Mode" + } + }, + "es-MX": { + "stringUnit": { + "state": "translated", + "value": "Enable Night Mode" + } + }, + "fr-CA": { + "stringUnit": { + "state": "translated", + "value": "Enable Night Mode" + } + } + } + }, + "settings.night_mode.end_time": { + "localizations": { + "en": { + "stringUnit": { + "state": "translated", + "value": "End Time" + } + }, + "es-MX": { + "stringUnit": { + "state": "translated", + "value": "End Time" + } + }, + "fr-CA": { + "stringUnit": { + "state": "translated", + "value": "End Time" + } + } + } + }, + "settings.night_mode.footer": { + "localizations": { + "en": { + "stringUnit": { + "state": "translated", + "value": "Night mode displays the clock in red to reduce eye strain in low light environments." + } + }, + "es-MX": { + "stringUnit": { + "state": "translated", + "value": "Night mode displays the clock in red to reduce eye strain in low light environments." + } + }, + "fr-CA": { + "stringUnit": { + "state": "translated", + "value": "Night mode displays the clock in red to reduce eye strain in low light environments." + } + } + } + }, + "settings.night_mode.light_threshold.subtitle": { + "localizations": { + "en": { + "stringUnit": { + "state": "translated", + "value": "Lower values activate sooner" + } + }, + "es-MX": { + "stringUnit": { + "state": "translated", + "value": "Lower values activate sooner" + } + }, + "fr-CA": { + "stringUnit": { + "state": "translated", + "value": "Lower values activate sooner" + } + } + } + }, + "settings.night_mode.light_threshold.title": { + "localizations": { + "en": { + "stringUnit": { + "state": "translated", + "value": "Light Threshold" + } + }, + "es-MX": { + "stringUnit": { + "state": "translated", + "value": "Light Threshold" + } + }, + "fr-CA": { + "stringUnit": { + "state": "translated", + "value": "Light Threshold" + } + } + } + }, + "settings.night_mode.scheduled.subtitle": { + "localizations": { + "en": { + "stringUnit": { + "state": "translated", + "value": "Enable on a daily schedule" + } + }, + "es-MX": { + "stringUnit": { + "state": "translated", + "value": "Enable on a daily schedule" + } + }, + "fr-CA": { + "stringUnit": { + "state": "translated", + "value": "Enable on a daily schedule" + } + } + } + }, + "settings.night_mode.scheduled.title": { + "localizations": { + "en": { + "stringUnit": { + "state": "translated", + "value": "Scheduled Night Mode" + } + }, + "es-MX": { + "stringUnit": { + "state": "translated", + "value": "Scheduled Night Mode" + } + }, + "fr-CA": { + "stringUnit": { + "state": "translated", + "value": "Scheduled Night Mode" + } + } + } + }, + "settings.night_mode.section_title": { + "localizations": { + "en": { + "stringUnit": { + "state": "translated", + "value": "Night Mode" + } + }, + "es-MX": { + "stringUnit": { + "state": "translated", + "value": "Night Mode" + } + }, + "fr-CA": { + "stringUnit": { + "state": "translated", + "value": "Night Mode" + } + } + } + }, + "settings.night_mode.start_time": { + "localizations": { + "en": { + "stringUnit": { + "state": "translated", + "value": "Start Time" + } + }, + "es-MX": { + "stringUnit": { + "state": "translated", + "value": "Start Time" + } + }, + "fr-CA": { + "stringUnit": { + "state": "translated", + "value": "Start Time" + } + } + } + }, + "settings.overlays.battery_level.subtitle": { + "localizations": { + "en": { + "stringUnit": { + "state": "translated", + "value": "Show battery percentage" + } + }, + "es-MX": { + "stringUnit": { + "state": "translated", + "value": "Show battery percentage" + } + }, + "fr-CA": { + "stringUnit": { + "state": "translated", + "value": "Show battery percentage" + } + } + } + }, + "settings.overlays.battery_level.title": { + "localizations": { + "en": { + "stringUnit": { + "state": "translated", + "value": "Battery Level" + } + }, + "es-MX": { + "stringUnit": { + "state": "translated", + "value": "Battery Level" + } + }, + "fr-CA": { + "stringUnit": { + "state": "translated", + "value": "Battery Level" + } + } + } + }, + "settings.overlays.date.subtitle": { + "localizations": { + "en": { + "stringUnit": { + "state": "translated", + "value": "Display the current date" + } + }, + "es-MX": { + "stringUnit": { + "state": "translated", + "value": "Display the current date" + } + }, + "fr-CA": { + "stringUnit": { + "state": "translated", + "value": "Display the current date" + } + } + } + }, + "settings.overlays.date.title": { + "localizations": { + "en": { + "stringUnit": { + "state": "translated", + "value": "Date" + } + }, + "es-MX": { + "stringUnit": { + "state": "translated", + "value": "Date" + } + }, + "fr-CA": { + "stringUnit": { + "state": "translated", + "value": "Date" + } + } + } + }, + "settings.overlays.date_format.title": { + "localizations": { + "en": { + "stringUnit": { + "state": "translated", + "value": "Date Format" + } + }, + "es-MX": { + "stringUnit": { + "state": "translated", + "value": "Date Format" + } + }, + "fr-CA": { + "stringUnit": { + "state": "translated", + "value": "Date Format" + } + } + } + }, + "settings.overlays.next_alarm.subtitle": { + "localizations": { + "en": { + "stringUnit": { + "state": "translated", + "value": "Show your next scheduled alarm" + } + }, + "es-MX": { + "stringUnit": { + "state": "translated", + "value": "Show your next scheduled alarm" + } + }, + "fr-CA": { + "stringUnit": { + "state": "translated", + "value": "Show your next scheduled alarm" + } + } + } + }, + "settings.overlays.next_alarm.title": { + "localizations": { + "en": { + "stringUnit": { + "state": "translated", + "value": "Next Alarm" + } + }, + "es-MX": { + "stringUnit": { + "state": "translated", + "value": "Next Alarm" + } + }, + "fr-CA": { + "stringUnit": { + "state": "translated", + "value": "Next Alarm" + } + } + } + }, + "settings.overlays.noise_controls.subtitle": { + "localizations": { + "en": { + "stringUnit": { + "state": "translated", + "value": "Mini-player for white noise" + } + }, + "es-MX": { + "stringUnit": { + "state": "translated", + "value": "Mini-player for white noise" + } + }, + "fr-CA": { + "stringUnit": { + "state": "translated", + "value": "Mini-player for white noise" + } + } + } + }, + "settings.overlays.noise_controls.title": { + "localizations": { + "en": { + "stringUnit": { + "state": "translated", + "value": "Noise Controls" + } + }, + "es-MX": { + "stringUnit": { + "state": "translated", + "value": "Noise Controls" + } + }, + "fr-CA": { + "stringUnit": { + "state": "translated", + "value": "Noise Controls" + } + } + } + }, + "settings.overlays.section_title": { + "localizations": { + "en": { + "stringUnit": { + "state": "translated", + "value": "Overlays" + } + }, + "es-MX": { + "stringUnit": { + "state": "translated", + "value": "Overlays" + } + }, + "fr-CA": { + "stringUnit": { + "state": "translated", + "value": "Overlays" + } + } + } + }, + "sound_category.alarm.description": { + "localizations": { + "en": { + "stringUnit": { + "state": "translated", + "value": "Wake-up and notification alarm sounds" + } + }, + "es-MX": { + "stringUnit": { + "state": "translated", + "value": "Sonidos de alarma para despertar y notificaciones" + } + }, + "fr-CA": { + "stringUnit": { + "state": "translated", + "value": "Sons d’alarme pour réveil et notifications" + } + } + } + }, + "sound_category.alarm.display_name": { + "localizations": { + "en": { + "stringUnit": { + "state": "translated", + "value": "Alarm Sounds" + } + }, + "es-MX": { + "stringUnit": { + "state": "translated", + "value": "Sonidos de alarma" + } + }, + "fr-CA": { + "stringUnit": { + "state": "translated", + "value": "Sons d’alarme" + } + } + } + }, + "sound_category.all.description": { + "localizations": { + "en": { + "stringUnit": { + "state": "translated", + "value": "All available sounds" + } + }, + "es-MX": { + "stringUnit": { + "state": "translated", + "value": "Todos los sonidos disponibles" + } + }, + "fr-CA": { + "stringUnit": { + "state": "translated", + "value": "Tous les sons disponibles" + } + } + } + }, + "sound_category.all.display_name": { + "localizations": { + "en": { + "stringUnit": { + "state": "translated", + "value": "All" + } + }, + "es-MX": { + "stringUnit": { + "state": "translated", + "value": "Todos" + } + }, + "fr-CA": { + "stringUnit": { + "state": "translated", + "value": "Tous" + } + } + } + }, + "sound_category.ambient.description": { + "localizations": { + "en": { + "stringUnit": { + "state": "translated", + "value": "General ambient sounds" + } + }, + "es-MX": { + "stringUnit": { + "state": "translated", + "value": "Sonidos ambientales generales" + } + }, + "fr-CA": { + "stringUnit": { + "state": "translated", + "value": "Sons d’ambiance généraux" + } + } + } + }, + "sound_category.ambient.display_name": { + "localizations": { + "en": { + "stringUnit": { + "state": "translated", + "value": "Ambient" + } + }, + "es-MX": { + "stringUnit": { + "state": "translated", + "value": "Ambiental" + } + }, + "fr-CA": { + "stringUnit": { + "state": "translated", + "value": "Ambiant" + } + } + } + }, + "sound_category.colored.description": { + "localizations": { + "en": { + "stringUnit": { + "state": "translated", + "value": "Synthetic noise signals for focus, sleep, and relaxation" + } + }, + "es-MX": { + "stringUnit": { + "state": "translated", + "value": "Señales de ruido sintético para enfoque, sueño y relajación" + } + }, + "fr-CA": { + "stringUnit": { + "state": "translated", + "value": "Signaux de bruit synthétique pour la concentration, le sommeil et la détente" + } + } + } + }, + "sound_category.colored.display_name": { + "localizations": { + "en": { + "stringUnit": { + "state": "translated", + "value": "Colored" + } + }, + "es-MX": { + "stringUnit": { + "state": "translated", + "value": "Colorido" + } + }, + "fr-CA": { + "stringUnit": { + "state": "translated", + "value": "Coloré" + } + } + } + }, + "sound_category.mechanical.description": { + "localizations": { + "en": { + "stringUnit": { + "state": "translated", + "value": "Mechanical and electronic sounds" + } + }, + "es-MX": { + "stringUnit": { + "state": "translated", + "value": "Sonidos mecánicos y electrónicos" + } + }, + "fr-CA": { + "stringUnit": { + "state": "translated", + "value": "Sons mécaniques et électroniques" + } + } + } + }, + "sound_category.mechanical.display_name": { + "localizations": { + "en": { + "stringUnit": { + "state": "translated", + "value": "Mechanical" + } + }, + "es-MX": { + "stringUnit": { + "state": "translated", + "value": "Mecánico" + } + }, + "fr-CA": { + "stringUnit": { + "state": "translated", + "value": "Mécanique" + } + } + } + }, + "sound_category.nature.description": { + "localizations": { + "en": { + "stringUnit": { + "state": "translated", + "value": "Natural environmental sounds" + } + }, + "es-MX": { + "stringUnit": { + "state": "translated", + "value": "Sonidos naturales del entorno" + } + }, + "fr-CA": { + "stringUnit": { + "state": "translated", + "value": "Sons naturels de l’environnement" + } + } + } + }, + "sound_category.nature.display_name": { + "localizations": { + "en": { + "stringUnit": { + "state": "translated", + "value": "Nature" + } + }, + "es-MX": { + "stringUnit": { + "state": "translated", + "value": "Naturaleza" + } + }, + "fr-CA": { + "stringUnit": { + "state": "translated", + "value": "Nature" + } + } + } + } + }, + "version": "1.0" +} diff --git a/TheNoiseClock/Shared/Design/Animations/DigitAnimationStyle.swift b/TheNoiseClock/Shared/Design/Animations/DigitAnimationStyle.swift index e1da65b..b8c5398 100644 --- a/TheNoiseClock/Shared/Design/Animations/DigitAnimationStyle.swift +++ b/TheNoiseClock/Shared/Design/Animations/DigitAnimationStyle.swift @@ -16,10 +16,14 @@ public enum DigitAnimationStyle: String, CaseIterable, Codable { public var displayName: String { switch self { - case .none: return "None" - case .spring: return "Spring" - case .bounce: return "Bounce" - case .glitch: return "Glitch" + case .none: + return String(localized: "digit_animation.none.display_name", defaultValue: "None") + case .spring: + return String(localized: "digit_animation.spring.display_name", defaultValue: "Spring") + case .bounce: + return String(localized: "digit_animation.bounce.display_name", defaultValue: "Bounce") + case .glitch: + return String(localized: "digit_animation.glitch.display_name", defaultValue: "Glitch") } } } diff --git a/TheNoiseClock/Shared/Models/SoundCategory.swift b/TheNoiseClock/Shared/Models/SoundCategory.swift index 506bf77..3fccacc 100644 --- a/TheNoiseClock/Shared/Models/SoundCategory.swift +++ b/TheNoiseClock/Shared/Models/SoundCategory.swift @@ -21,12 +21,18 @@ public enum SoundCategory: String, CaseIterable, Identifiable { /// Display name for the category public var displayName: String { switch self { - case .all: return "All" - case .colored: return "Colored" - case .ambient: return "Ambient" - case .nature: return "Nature" - case .mechanical: return "Mechanical" - case .alarm: return "Alarm Sounds" + case .all: + return String(localized: "sound_category.all.display_name", defaultValue: "All") + case .colored: + return String(localized: "sound_category.colored.display_name", defaultValue: "Colored") + case .ambient: + return String(localized: "sound_category.ambient.display_name", defaultValue: "Ambient") + case .nature: + return String(localized: "sound_category.nature.display_name", defaultValue: "Nature") + case .mechanical: + return String(localized: "sound_category.mechanical.display_name", defaultValue: "Mechanical") + case .alarm: + return String(localized: "sound_category.alarm.display_name", defaultValue: "Alarm Sounds") } } @@ -57,12 +63,18 @@ public enum SoundCategory: String, CaseIterable, Identifiable { /// Description of the category public var description: String { switch self { - case .all: return "All available sounds" - case .colored: return "Synthetic noise signals for focus, sleep, and relaxation" - case .ambient: return "General ambient sounds" - case .nature: return "Natural environmental sounds" - case .mechanical: return "Mechanical and electronic sounds" - case .alarm: return "Wake-up and notification alarm sounds" + case .all: + return String(localized: "sound_category.all.description", defaultValue: "All available sounds") + case .colored: + return String(localized: "sound_category.colored.description", defaultValue: "Synthetic noise signals for focus, sleep, and relaxation") + case .ambient: + return String(localized: "sound_category.ambient.description", defaultValue: "General ambient sounds") + case .nature: + return String(localized: "sound_category.nature.description", defaultValue: "Natural environmental sounds") + case .mechanical: + return String(localized: "sound_category.mechanical.description", defaultValue: "Mechanical and electronic sounds") + case .alarm: + return String(localized: "sound_category.alarm.description", defaultValue: "Wake-up and notification alarm sounds") } } diff --git a/TheNoiseClock/Shared/Utilities/KeepAwakePrompt.swift b/TheNoiseClock/Shared/Utilities/KeepAwakePrompt.swift index cdd45a9..bc61d1c 100644 --- a/TheNoiseClock/Shared/Utilities/KeepAwakePrompt.swift +++ b/TheNoiseClock/Shared/Utilities/KeepAwakePrompt.swift @@ -20,12 +20,12 @@ struct KeepAwakePrompt: View { .symbolEffect(.bounce, options: .nonRepeating) VStack(spacing: Design.Spacing.small) { - Text("Keep Awake for Alarms") + Text(String(localized: "keep_awake.prompt.title", defaultValue: "Keep Awake for Alarms")) .typography(.title2Bold) .foregroundStyle(AppTextColors.primary) .multilineTextAlignment(.center) - Text("Enable Keep Awake so your alarm can play loudly and show the full screen while TheNoiseClock stays open.") + Text(String(localized: "keep_awake.prompt.subtitle", defaultValue: "Enable Keep Awake so your alarm can play loudly and show the full screen while TheNoiseClock stays open.")) .typography(.body) .foregroundStyle(AppTextColors.secondary) .multilineTextAlignment(.center) @@ -34,7 +34,7 @@ struct KeepAwakePrompt: View { VStack(spacing: Design.Spacing.medium) { Button(action: onEnable) { - Text("Enable Keep Awake") + Text(String(localized: "keep_awake.prompt.enable", defaultValue: "Enable Keep Awake")) .font(Typography.headingEmphasis.font) .foregroundStyle(.white) .frame(maxWidth: .infinity) @@ -44,7 +44,7 @@ struct KeepAwakePrompt: View { } Button(action: onDismiss) { - Text("Not Now") + Text(String(localized: "common.not_now", defaultValue: "Not Now")) .font(Typography.bodyEmphasis.font) .foregroundStyle(AppTextColors.secondary) .frame(maxWidth: .infinity)