diff --git a/nahbar/nahbar/Localizable.xcstrings b/nahbar/nahbar/Localizable.xcstrings index be6db62..de6b12a 100644 --- a/nahbar/nahbar/Localizable.xcstrings +++ b/nahbar/nahbar/Localizable.xcstrings @@ -228,6 +228,40 @@ } } }, + "%lld Schritte" : { + "comment" : "SettingsView – tour step count label (e.g. '6 Schritte')", + "localizations" : { + "en" : { + "variations" : { + "plural" : { + "one" : { + "stringUnit" : { + "state" : "translated", + "value" : "%lld step" + } + }, + "other" : { + "stringUnit" : { + "state" : "translated", + "value" : "%lld steps" + } + } + } + } + } + } + }, + "%lld von %lld" : { + "comment" : "TourCardView – step counter (e.g. '2 von 6')", + "localizations" : { + "en" : { + "stringUnit" : { + "state" : "translated", + "value" : "%lld of %lld" + } + } + } + }, "%lld von %lld — Maximum erreicht" : { "localizations" : { "de" : { @@ -859,6 +893,17 @@ } } }, + "App-Einführung" : { + "comment" : "TourCatalog – onboarding tour title shown in SettingsView", + "localizations" : { + "en" : { + "stringUnit" : { + "state" : "translated", + "value" : "App Introduction" + } + } + } + }, "App-Schutz" : { "comment" : "SettingsView – section header for app lock settings", "localizations" : { @@ -871,7 +916,15 @@ } }, "App-Touren" : { - + "comment" : "SettingsView – Tour section header", + "localizations" : { + "en" : { + "stringUnit" : { + "state" : "translated", + "value" : "App Tours" + } + } + } }, "Arbeit" : { "comment" : "PersonTag.work raw value", @@ -1467,6 +1520,17 @@ } } }, + "Deine Menschen im Mittelpunkt" : { + "comment" : "TourCatalog – onboarding step 2 title", + "localizations" : { + "en" : { + "stringUnit" : { + "state" : "translated", + "value" : "Your People at the Center" + } + } + } + }, "Details" : { "localizations" : { "en" : { @@ -1945,6 +2009,17 @@ } } }, + "Einblicke, wenn du willst" : { + "comment" : "TourCatalog – onboarding step 6 title", + "localizations" : { + "en" : { + "stringUnit" : { + "state" : "translated", + "value" : "Insights When You Want Them" + } + } + } + }, "Einrichten" : { "comment" : "CallWindowSetupView – setup button label when onboarding", "localizations" : { @@ -2034,6 +2109,17 @@ } } }, + "Erfasse Treffen, Nachrichten und Erlebnisse. So weißt du, worüber ihr das letzte Mal geredet habt." : { + "comment" : "TourCatalog – onboarding step 3 body", + "localizations" : { + "en" : { + "stringUnit" : { + "state" : "translated", + "value" : "Record meetings, messages, and experiences. So you know what you last talked about." + } + } + } + }, "Ergebnis bestätigen und fortfahren" : { "localizations" : { "en" : { @@ -2350,8 +2436,23 @@ } } }, + "Füge Personen hinzu, die dir wichtig sind. Notiere Interessen, Gesprächsthemen und was euch verbindet." : { + "comment" : "TourCatalog – onboarding step 2 body", + "localizations" : { + "en" : { + "stringUnit" : { + "state" : "translated", + "value" : "Add people who matter to you. Note interests, conversation topics, and what connects you." + } + } + } + }, "Füge zuerst Personen im Tab „Menschen“ hinzu." : { + + }, + "Füge zuerst Personen im Tab „Menschen” hinzu." : { "comment" : "TodayPersonPickerSheet – empty state hint when no contacts exist yet", + "extractionState" : "stale", "localizations" : { "en" : { "stringUnit" : { @@ -3270,6 +3371,17 @@ } } }, + "Leg Vorhaben an und erhalte eine Erinnerung – damit aus 'Wir müssen mal wieder…' ein echtes Treffen wird." : { + "comment" : "TourCatalog – onboarding step 4 body", + "localizations" : { + "en" : { + "stringUnit" : { + "state" : "translated", + "value" : "Create intentions and get a reminder – so 'We should catch up…' becomes a real meeting." + } + } + } + }, "Limit erreicht" : { "comment" : "LogbuchView – AI refresh button label when at request limit", "localizations" : { @@ -3335,6 +3447,17 @@ } } }, + "Loslegen" : { + "comment" : "TourCardView – finish button label on last tour step", + "localizations" : { + "en" : { + "stringUnit" : { + "state" : "translated", + "value" : "Let's go" + } + } + } + }, "Mag ich" : { "comment" : "IchView – likes preferences field label", "extractionState" : "stale", @@ -3589,6 +3712,17 @@ } } }, + "Momente festhalten" : { + "comment" : "TourCatalog – onboarding step 3 title", + "localizations" : { + "en" : { + "stringUnit" : { + "state" : "translated", + "value" : "Capture Moments" + } + } + } + }, "Momente planen und hinzufügen" : { "comment" : "TodayView – empty state CTA button subtitle", "localizations" : { @@ -3869,6 +4003,28 @@ } } }, + "nahbar erinnert dich, wenn du lange nichts von jemandem gehört hast. Du entscheidest, wie oft." : { + "comment" : "TourCatalog – onboarding step 5 body", + "localizations" : { + "en" : { + "stringUnit" : { + "state" : "translated", + "value" : "nahbar reminds you when you haven't heard from someone in a while. You decide how often." + } + } + } + }, + "nahbar hilft dir, echte Verbindungen zu pflegen – ohne Stress, ohne Algorithmen." : { + "comment" : "TourCatalog – onboarding step 1 body", + "localizations" : { + "en" : { + "stringUnit" : { + "state" : "translated", + "value" : "nahbar helps you nurture real connections – without stress, without algorithms." + } + } + } + }, "nahbar Max freischalten für KI-Analyse" : { "comment" : "LogbuchView – upsell button for AI analysis", "localizations" : { @@ -4288,6 +4444,17 @@ } } }, + "Optionale KI-Analyse zeigt Muster in deinen Verbindungen. Alles optional – deine Daten bleiben bei dir." : { + "comment" : "TourCatalog – onboarding step 6 body", + "localizations" : { + "en" : { + "stringUnit" : { + "state" : "translated", + "value" : "Optional AI analysis shows patterns in your connections. Everything optional – your data stays with you." + } + } + } + }, "Passend für dich" : { "extractionState" : "stale", "localizations" : { @@ -4384,6 +4551,17 @@ } } }, + "Plane das Nächste" : { + "comment" : "TourCatalog – onboarding step 4 title", + "localizations" : { + "en" : { + "stringUnit" : { + "state" : "translated", + "value" : "Plan What's Next" + } + } + } + }, "Plane Unternehmungen mit Erinnerung – nahbar erinnert dich zur richtigen Zeit." : { "comment" : "OnboardingContainerView – feature tour card description for Unternehmung (intention type)", "localizations" : { @@ -4546,6 +4724,17 @@ } } }, + "Sanfte Erinnerungen" : { + "comment" : "TourCatalog – onboarding step 5 title", + "localizations" : { + "en" : { + "stringUnit" : { + "state" : "translated", + "value" : "Gentle Reminders" + } + } + } + }, "Schließen" : { "comment" : "PersonDetailView / ShareExtensionView – close button", "localizations" : { @@ -4691,32 +4880,6 @@ } } }, - "settings.tours.start" : { - - }, - "settings.tours.stepCount %lld" : { - "comment" : "SettingsView – step count label (e.g. '6 Schritte')", - "localizations" : { - "en" : { - "variations" : { - "plural" : { - "one" : { - "stringUnit" : { - "state" : "translated", - "value" : "%lld step" - } - }, - "other" : { - "stringUnit" : { - "state" : "translated", - "value" : "%lld steps" - } - } - } - } - } - } - }, "Signal" : { "comment" : "MomentSource.signal raw value", "extractionState" : "stale", @@ -5036,69 +5199,38 @@ } } }, - "tour.common.back" : { - - }, - "tour.common.close" : { - - }, - "tour.common.finish" : { - - }, - "tour.common.next" : { - - }, - "tour.common.skip" : { - - }, - "tour.common.stepCounter %lld %lld" : { + "Tour schließen" : { + "comment" : "TourCardView – accessibility label for close button", "localizations" : { - "de" : { + "en" : { "stringUnit" : { - "state" : "new", - "value" : "tour.common.stepCounter %1$lld %2$lld" + "state" : "translated", + "value" : "Close tour" } } } }, - "tour.onboarding.step1.body" : { - + "Tour starten" : { + "comment" : "SettingsView – button to replay a tour", + "localizations" : { + "en" : { + "stringUnit" : { + "state" : "translated", + "value" : "Start tour" + } + } + } }, - "tour.onboarding.step1.title" : { - - }, - "tour.onboarding.step2.body" : { - - }, - "tour.onboarding.step2.title" : { - - }, - "tour.onboarding.step3.body" : { - - }, - "tour.onboarding.step3.title" : { - - }, - "tour.onboarding.step4.body" : { - - }, - "tour.onboarding.step4.title" : { - - }, - "tour.onboarding.step5.body" : { - - }, - "tour.onboarding.step5.title" : { - - }, - "tour.onboarding.step6.body" : { - - }, - "tour.onboarding.step6.title" : { - - }, - "tour.onboarding.title" : { - + "Tour überspringen" : { + "comment" : "TourCardView – skip button label", + "localizations" : { + "en" : { + "stringUnit" : { + "state" : "translated", + "value" : "Skip tour" + } + } + } }, "Treffen" : { "comment" : "MomentType.meeting rawValue + VisitHistorySection / SettingsView section header", @@ -6135,6 +6267,17 @@ } } }, + "Zurück" : { + "comment" : "TourCardView – back button label", + "localizations" : { + "en" : { + "stringUnit" : { + "state" : "translated", + "value" : "Back" + } + } + } + }, "Zusammen essen" : { "comment" : "PersonDetailView – activity suggestion: have a meal together (group)", "extractionState" : "stale", diff --git a/nahbar/nahbar/SettingsView.swift b/nahbar/nahbar/SettingsView.swift index 57e8577..6f9e10e 100644 --- a/nahbar/nahbar/SettingsView.swift +++ b/nahbar/nahbar/SettingsView.swift @@ -608,12 +608,12 @@ struct SettingsView: View { Text(tour.title) .font(.system(size: 15)) .foregroundStyle(theme.contentPrimary) - Text(String.localizedStringWithFormat(String(localized: "settings.tours.stepCount %lld"), Int64(tour.steps.count))) + Text(String.localizedStringWithFormat(String(localized: "%lld Schritte"), Int64(tour.steps.count))) .font(.system(size: 12)) .foregroundStyle(theme.contentTertiary) } Spacer() - Button(String(localized: "settings.tours.start")) { + Button("Tour starten") { tourCoordinator.start(tour.id) } .font(.system(size: 13, weight: .medium)) diff --git a/nahbar/nahbar/Tour/TourCardView.swift b/nahbar/nahbar/Tour/TourCardView.swift index 40b410b..43d5efa 100644 --- a/nahbar/nahbar/Tour/TourCardView.swift +++ b/nahbar/nahbar/Tour/TourCardView.swift @@ -25,7 +25,7 @@ struct TourCardView: View { .padding(8) .background(Color.secondary.opacity(0.12), in: Circle()) } - .accessibilityLabel(Text("tour.common.close")) + .accessibilityLabel("Tour schließen") } .padding(.horizontal, 20) .padding(.top, 18) @@ -56,14 +56,13 @@ struct TourCardView: View { Button { coordinator.skip() } label: { - Text("tour.common.skip") + Text("Tour überspringen") .font(.caption) .foregroundStyle(.tertiary) } - .accessibilityLabel(Text("tour.common.skip")) Text(verbatim: String.localizedStringWithFormat( - String(localized: "tour.common.stepCounter %lld %lld"), + String(localized: "%lld von %lld"), Int64(currentIndex + 1), Int64(totalSteps) )) @@ -78,18 +77,19 @@ struct TourCardView: View { Button { coordinator.previous() } label: { - Text("tour.common.back") + Text("Zurück") .font(.subheadline.weight(.medium)) .foregroundStyle(.secondary) } .padding(.trailing, 8) } - // Next / Finish button + // Next / Finish button — explicit LocalizedStringKey to ensure lookup + let nextLabel: LocalizedStringKey = coordinator.isLastStep ? "Loslegen" : "Weiter" Button { coordinator.next() } label: { - Text(coordinator.isLastStep ? "tour.common.finish" : "tour.common.next") + Text(nextLabel) .font(.subheadline.weight(.semibold)) .foregroundStyle(.white) .padding(.horizontal, 18) diff --git a/nahbar/nahbar/Tour/TourCatalog.swift b/nahbar/nahbar/Tour/TourCatalog.swift index 5582152..9bf6d80 100644 --- a/nahbar/nahbar/Tour/TourCatalog.swift +++ b/nahbar/nahbar/Tour/TourCatalog.swift @@ -3,6 +3,7 @@ import Foundation // MARK: - TourCatalog /// Static registry of all tours defined in the app. +/// Strings use German text as keys (consistent with project xcstrings convention). /// New tours are added as static properties and included in `all`. enum TourCatalog { @@ -10,41 +11,41 @@ enum TourCatalog { static let onboarding = Tour( id: .onboarding, - title: "tour.onboarding.title", + title: "App-Einführung", steps: [ TourStep( - title: "tour.onboarding.step1.title", - body: "tour.onboarding.step1.body", + title: "Willkommen bei nahbar", + body: "nahbar hilft dir, echte Verbindungen zu pflegen – ohne Stress, ohne Algorithmen.", target: nil, preferredCardPosition: .center ), TourStep( - title: "tour.onboarding.step2.title", - body: "tour.onboarding.step2.body", + title: "Deine Menschen im Mittelpunkt", + body: "Füge Personen hinzu, die dir wichtig sind. Notiere Interessen, Gesprächsthemen und was euch verbindet.", target: nil, preferredCardPosition: .center ), TourStep( - title: "tour.onboarding.step3.title", - body: "tour.onboarding.step3.body", + title: "Momente festhalten", + body: "Erfasse Treffen, Nachrichten und Erlebnisse. So weißt du, worüber ihr das letzte Mal geredet habt.", target: nil, preferredCardPosition: .center ), TourStep( - title: "tour.onboarding.step4.title", - body: "tour.onboarding.step4.body", + title: "Plane das Nächste", + body: "Leg Vorhaben an und erhalte eine Erinnerung – damit aus 'Wir müssen mal wieder…' ein echtes Treffen wird.", target: nil, preferredCardPosition: .center ), TourStep( - title: "tour.onboarding.step5.title", - body: "tour.onboarding.step5.body", + title: "Sanfte Erinnerungen", + body: "nahbar erinnert dich, wenn du lange nichts von jemandem gehört hast. Du entscheidest, wie oft.", target: nil, preferredCardPosition: .center ), TourStep( - title: "tour.onboarding.step6.title", - body: "tour.onboarding.step6.body", + title: "Einblicke, wenn du willst", + body: "Optionale KI-Analyse zeigt Muster in deinen Verbindungen. Alles optional – deine Daten bleiben bei dir.", target: nil, preferredCardPosition: .center ), diff --git a/nahbar/nahbarTests/StoreTests.swift b/nahbar/nahbarTests/StoreTests.swift index 6a036bf..9179b4e 100644 --- a/nahbar/nahbarTests/StoreTests.swift +++ b/nahbar/nahbarTests/StoreTests.swift @@ -483,7 +483,7 @@ struct InterestTagHelperSuggestionsTests { @Test("Duplikate werden dedupliziert") func deduplicates() { let result = InterestTagHelper.allSuggestions(from: [], likes: "Kino, Musik", dislikes: "Kino") - #expect(!result.contains { result.filter { $0 == "Kino" }.count > 1 }) + #expect(!result.contains { _ in result.filter { $0 == "Kino" }.count > 1 }) #expect(result.filter { $0 == "Kino" }.count == 1) } } diff --git a/nahbar/nahbarTests/Tour/TourCatalogTests.swift b/nahbar/nahbarTests/Tour/TourCatalogTests.swift index bb7d748..65e8d74 100644 --- a/nahbar/nahbarTests/Tour/TourCatalogTests.swift +++ b/nahbar/nahbarTests/Tour/TourCatalogTests.swift @@ -1,3 +1,4 @@ +import Foundation import Testing @testable import nahbar diff --git a/nahbar/nahbarTests/Tour/TourCoordinatorTests.swift b/nahbar/nahbarTests/Tour/TourCoordinatorTests.swift index f6c3607..23bce73 100644 --- a/nahbar/nahbarTests/Tour/TourCoordinatorTests.swift +++ b/nahbar/nahbarTests/Tour/TourCoordinatorTests.swift @@ -1,5 +1,6 @@ -import Testing +import CoreGraphics import Foundation +import Testing @testable import nahbar // MARK: - TourCoordinator Tests