Fix #23: Tour-Übersetzung – deutsche Strings als Keys, Lokalisierungen ergänzt
- TourCatalog.swift: Technische Keys (tour.onboarding.*) durch deutschen Klartext ersetzt (konform mit Projekt-xcstrings-Konvention) - TourCardView.swift: Ternary-Ausdrucks-Bug behoben (String statt LocalizedStringKey); Button-Labels mit deutschen Strings - SettingsView.swift: settings.tours.* durch deutsche Keys ersetzt - Localizable.xcstrings: Technische Keys entfernt, alle Tour-Strings als deutsche Keys mit EN-Übersetzungen hinzugefügt (19 neue Einträge) - TourCatalogTests: import Foundation ergänzt (LocalizedStringResource) - TourCoordinatorTests: import CoreGraphics ergänzt (CGRect) - StoreTests: Closure-Argument-Fehler behoben (_ in) Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
@@ -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",
|
||||
|
||||
@@ -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))
|
||||
|
||||
@@ -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)
|
||||
|
||||
@@ -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
|
||||
),
|
||||
|
||||
@@ -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)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,3 +1,4 @@
|
||||
import Foundation
|
||||
import Testing
|
||||
@testable import nahbar
|
||||
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
import Testing
|
||||
import CoreGraphics
|
||||
import Foundation
|
||||
import Testing
|
||||
@testable import nahbar
|
||||
|
||||
// MARK: - TourCoordinator Tests
|
||||
|
||||
Reference in New Issue
Block a user