diff --git a/RevenueCatUI/Data/ProcessedLocalizedConfiguration.swift b/RevenueCatUI/Data/ProcessedLocalizedConfiguration.swift index a9e4f42513..42785c8403 100644 --- a/RevenueCatUI/Data/ProcessedLocalizedConfiguration.swift +++ b/RevenueCatUI/Data/ProcessedLocalizedConfiguration.swift @@ -30,21 +30,24 @@ struct ProcessedLocalizedConfiguration: PaywallLocalizedConfiguration { init( _ configuration: PaywallData.LocalizedConfiguration, _ dataProvider: VariableDataProvider, + _ context: VariableHandler.Context, _ locale: Locale ) { self.init( - title: configuration.title.processed(with: dataProvider, locale: locale), - subtitle: configuration.subtitle?.processed(with: dataProvider, locale: locale), - callToAction: configuration.callToAction.processed(with: dataProvider, locale: locale), + title: configuration.title.processed(with: dataProvider, context: context, locale: locale), + subtitle: configuration.subtitle?.processed(with: dataProvider, context: context, locale: locale), + callToAction: configuration.callToAction.processed(with: dataProvider, context: context, locale: locale), callToActionWithIntroOffer: configuration.callToActionWithIntroOffer?.processed(with: dataProvider, + context: context, locale: locale), - offerDetails: configuration.offerDetails?.processed(with: dataProvider, locale: locale), + offerDetails: configuration.offerDetails?.processed(with: dataProvider, context: context, locale: locale), offerDetailsWithIntroOffer: configuration.offerDetailsWithIntroOffer?.processed(with: dataProvider, + context: context, locale: locale), - offerName: configuration.offerName?.processed(with: dataProvider, locale: locale), + offerName: configuration.offerName?.processed(with: dataProvider, context: context, locale: locale), features: configuration.features.map { - .init(title: $0.title.processed(with: dataProvider, locale: locale), - content: $0.content?.processed(with: dataProvider, locale: locale), + .init(title: $0.title.processed(with: dataProvider, context: context, locale: locale), + content: $0.content?.processed(with: dataProvider, context: context, locale: locale), iconID: $0.iconID) } ) diff --git a/RevenueCatUI/Data/TemplateViewConfiguration.swift b/RevenueCatUI/Data/TemplateViewConfiguration.swift index b0e507960c..78dffd2e70 100644 --- a/RevenueCatUI/Data/TemplateViewConfiguration.swift +++ b/RevenueCatUI/Data/TemplateViewConfiguration.swift @@ -121,18 +121,24 @@ extension TemplateViewConfiguration.PackageConfiguration { let filtered = TemplateViewConfiguration.filter(packages: packages, with: filter) let mostExpensivePricePerMonth = Self.mostExpensivePricePerMonth(in: filtered) - let filteredPackages = filtered + let filteredPackages: [TemplateViewConfiguration.Package] = filtered .map { package in - TemplateViewConfiguration.Package( + let discount = Self.discount( + from: package.storeProduct.pricePerMonth?.doubleValue, + relativeTo: mostExpensivePricePerMonth + ) + + return .init( content: package, - localization: localization.processVariables(with: package, locale: locale), + localization: localization.processVariables( + with: package, + context: .init(discountRelativeToMostExpensivePerMonth: discount), + locale: locale + ), currentlySubscribed: activelySubscribedProductIdentifiers.contains( package.storeProduct.productIdentifier ), - discountRelativeToMostExpensivePerMonth: Self.discount( - from: package.storeProduct.pricePerMonth?.doubleValue, - relativeTo: mostExpensivePricePerMonth - ) + discountRelativeToMostExpensivePerMonth: discount ) } diff --git a/RevenueCatUI/Data/Variables.swift b/RevenueCatUI/Data/Variables.swift index 7a0a4a56c3..fffc823a63 100644 --- a/RevenueCatUI/Data/Variables.swift +++ b/RevenueCatUI/Data/Variables.swift @@ -16,8 +16,12 @@ import RevenueCat @available(iOS 15.0, macOS 12.0, tvOS 15.0, *) extension PaywallData.LocalizedConfiguration { - func processVariables(with package: Package, locale: Locale = .current) -> ProcessedLocalizedConfiguration { - return .init(self, package, locale) + func processVariables( + with package: Package, + context: VariableHandler.Context, + locale: Locale = .current + ) -> ProcessedLocalizedConfiguration { + return .init(self, package, context, locale) } } @@ -38,6 +42,7 @@ protocol VariableDataProvider { func localizedPricePerPeriod(_ locale: Locale) -> String func localizedPriceAndPerMonth(_ locale: Locale) -> String + func localizedRelativeDiscount(_ discount: Double?, _ locale: Locale) -> String? } @@ -45,15 +50,29 @@ protocol VariableDataProvider { @available(iOS 15.0, macOS 12.0, tvOS 15.0, *) enum VariableHandler { + /// Information necessary for computing variables + struct Context { + + var discountRelativeToMostExpensivePerMonth: Double? + + } + static func processVariables( in string: String, with provider: VariableDataProvider, + context: Context, locale: Locale = .current ) -> String { if #available(iOS 16.0, macOS 13.0, tvOS 16.0, *) { - return VariableHandlerIOS16.processVariables(in: string, with: provider, locale: locale) + return VariableHandlerIOS16.processVariables(in: string, + with: provider, + context: context, + locale: locale) } else { - return VariableHandlerIOS15.processVariables(in: string, with: provider, locale: locale) + return VariableHandlerIOS15.processVariables(in: string, + with: provider, + context: context, + locale: locale) } } @@ -65,21 +84,25 @@ enum VariableHandler { ) } - fileprivate typealias ValueProvider = (VariableDataProvider, Locale) -> String? + fileprivate typealias ValueProvider = (VariableDataProvider, + VariableHandler.Context, + Locale) -> String? // swiftlint:disable:next cyclomatic_complexity fileprivate static func provider(for variableName: String) -> ValueProvider? { switch variableName { - case "app_name": return { (provider, _) in provider.applicationName } - case "price": return { (provider, _) in provider.localizedPrice } - case "price_per_period": return { $0.localizedPricePerPeriod($1) } - case "total_price_and_per_month": return { $0.localizedPriceAndPerMonth($1) } - case "product_name": return { (provider, _) in provider.productName } - case "sub_period": return { $0.periodName($1) } - case "sub_price_per_month": return { (provider, _) in provider.localizedPricePerMonth } - case "sub_duration": return { $0.subscriptionDuration($1) } - case "sub_offer_duration": return { $0.introductoryOfferDuration($1) } - case "sub_offer_price": return { (provider, _) in provider.localizedIntroductoryOfferPrice } + case "app_name": return { (provider, _, _) in provider.applicationName } + case "price": return { (provider, _, _) in provider.localizedPrice } + case "price_per_period": return { (provider, _, locale) in provider.localizedPricePerPeriod(locale) } + case "total_price_and_per_month": return { (provider, _, locale) in provider.localizedPriceAndPerMonth(locale) } + case "product_name": return { (provider, _, _) in provider.productName } + case "sub_period": return { (provider, _, locale) in provider.periodName(locale) } + case "sub_price_per_month": return { (provider, _, _) in provider.localizedPricePerMonth } + case "sub_duration": return { (provider, _, locale) in provider.subscriptionDuration(locale) } + case "sub_offer_duration": return { (provider, _, locale) in provider.introductoryOfferDuration(locale) } + case "sub_offer_price": return { (provider, _, _) in provider.localizedIntroductoryOfferPrice } + case "sub_relative_discount": return { $0.localizedRelativeDiscount($1.discountRelativeToMostExpensivePerMonth, + $2) } default: Logger.warning(Strings.unrecognized_variable_name(variableName: variableName)) @@ -92,8 +115,12 @@ enum VariableHandler { @available(iOS 15.0, macOS 12.0, tvOS 15.0, *) extension String { - func processed(with provider: VariableDataProvider, locale: Locale) -> Self { - return VariableHandler.processVariables(in: self, with: provider, locale: locale) + func processed( + with provider: VariableDataProvider, + context: VariableHandler.Context, + locale: Locale + ) -> Self { + return VariableHandler.processVariables(in: self, with: provider, context: context, locale: locale) } func unrecognizedVariables() -> Set { @@ -109,8 +136,12 @@ extension String { @available(iOS 15.0, macOS 12.0, tvOS 15.0, *) private extension VariableDataProvider { - func value(for variableName: String, locale: Locale) -> String { - VariableHandler.provider(for: variableName)?(self, locale) ?? "" + func value( + for variableName: String, + context: VariableHandler.Context, + locale: Locale + ) -> String { + VariableHandler.provider(for: variableName)?(self, context, locale) ?? "" } } @@ -123,13 +154,16 @@ private enum VariableHandlerIOS16 { static func processVariables( in string: String, with provider: VariableDataProvider, + context: VariableHandler.Context, locale: Locale = .current ) -> String { let matches = Self.extractVariables(from: string) var replacedString = string for variableMatch in matches.reversed() { - let replacementValue = provider.value(for: variableMatch.variable, locale: locale) + let replacementValue = provider.value(for: variableMatch.variable, + context: context, + locale: locale) replacedString = replacedString.replacingCharacters(in: variableMatch.range, with: replacementValue) } @@ -176,6 +210,7 @@ private enum VariableHandlerIOS15 { static func processVariables( in string: String, with provider: VariableDataProvider, + context: VariableHandler.Context, locale: Locale = .current ) -> String { var replacedString = string @@ -185,7 +220,9 @@ private enum VariableHandlerIOS15 { let variableNameRange = match.range(at: 1) if let variableNameRange = Range(variableNameRange, in: string) { let variableName = String(string[variableNameRange]) - let replacementValue = provider.value(for: variableName, locale: locale) + let replacementValue = provider.value(for: variableName, + context: context, + locale: locale) let adjustedRange = NSRange( location: variableNameRange.lowerBound.utf16Offset(in: string) - Self.pattern.count / 2, diff --git a/RevenueCatUI/Helpers/Package+VariableDataProvider.swift b/RevenueCatUI/Helpers/Package+VariableDataProvider.swift index e2f1700182..82f6b9b8c5 100644 --- a/RevenueCatUI/Helpers/Package+VariableDataProvider.swift +++ b/RevenueCatUI/Helpers/Package+VariableDataProvider.swift @@ -67,6 +67,12 @@ extension Package: VariableDataProvider { } } + func localizedRelativeDiscount(_ discount: Double?, _ locale: Locale) -> String? { + guard let discount else { return nil } + + return Localization.localized(discount: discount, locale: locale) + } + } // MARK: - Private diff --git a/RevenueCatUI/Resources/ar.lproj/Localizable.strings b/RevenueCatUI/Resources/ar.lproj/Localizable.strings index d96f5f63d2..0acc3b0fe0 100644 --- a/RevenueCatUI/Resources/ar.lproj/Localizable.strings +++ b/RevenueCatUI/Resources/ar.lproj/Localizable.strings @@ -14,3 +14,4 @@ "Monthly" = "شهريا"; "Weekly" = "أسبوعي"; "Lifetime" = "حياة"; +"%d%% off" = "خصم %d%%"; diff --git a/RevenueCatUI/Resources/bg.lproj/Localizable.strings b/RevenueCatUI/Resources/bg.lproj/Localizable.strings index 0ff7b0da84..d6d2a1d9d2 100644 --- a/RevenueCatUI/Resources/bg.lproj/Localizable.strings +++ b/RevenueCatUI/Resources/bg.lproj/Localizable.strings @@ -14,3 +14,4 @@ "Monthly" = "Месечно"; "Weekly" = "Ежеседмично"; "Lifetime" = "Живот"; +"%d%% off" = "%d%% намаление"; diff --git a/RevenueCatUI/Resources/ca.lproj/Localizable.strings b/RevenueCatUI/Resources/ca.lproj/Localizable.strings index d57658cc4d..c8e7f1503a 100644 --- a/RevenueCatUI/Resources/ca.lproj/Localizable.strings +++ b/RevenueCatUI/Resources/ca.lproj/Localizable.strings @@ -14,3 +14,4 @@ "Monthly" = "Mensual"; "Weekly" = "Setmanalment"; "Lifetime" = "Tota una vida"; +"%d%% off" = "%d%% de descompte"; diff --git a/RevenueCatUI/Resources/cs.lproj/Localizable.strings b/RevenueCatUI/Resources/cs.lproj/Localizable.strings index 06161f6334..7b0eab72d4 100644 --- a/RevenueCatUI/Resources/cs.lproj/Localizable.strings +++ b/RevenueCatUI/Resources/cs.lproj/Localizable.strings @@ -14,3 +14,4 @@ "Monthly" = "Měsíční"; "Weekly" = "Týdně"; "Lifetime" = "Život"; +"%d%% off" = "%d%% sleva"; diff --git a/RevenueCatUI/Resources/da.lproj/Localizable.strings b/RevenueCatUI/Resources/da.lproj/Localizable.strings index e071f8a85b..e03bf15989 100644 --- a/RevenueCatUI/Resources/da.lproj/Localizable.strings +++ b/RevenueCatUI/Resources/da.lproj/Localizable.strings @@ -14,3 +14,4 @@ "Monthly" = "Månedlige"; "Weekly" = "Ugentlig"; "Lifetime" = "Livstid"; +"%d%% off" = "%d%% rabat"; diff --git a/RevenueCatUI/Resources/de.lproj/Localizable.strings b/RevenueCatUI/Resources/de.lproj/Localizable.strings index 1b1c34e29e..5bcd8f5955 100644 --- a/RevenueCatUI/Resources/de.lproj/Localizable.strings +++ b/RevenueCatUI/Resources/de.lproj/Localizable.strings @@ -14,3 +14,4 @@ "Monthly" = "Monatlich"; "Weekly" = "Wöchentlich"; "Lifetime" = "Lebensdauer"; +"%d%% off" = "%d %% Rabatt"; diff --git a/RevenueCatUI/Resources/el.lproj/Localizable.strings b/RevenueCatUI/Resources/el.lproj/Localizable.strings index 97c6b050ce..5f6760b9b3 100644 --- a/RevenueCatUI/Resources/el.lproj/Localizable.strings +++ b/RevenueCatUI/Resources/el.lproj/Localizable.strings @@ -14,3 +14,4 @@ "Monthly" = "Μηνιαίο"; "Weekly" = "Εβδομαδιαίος"; "Lifetime" = "Διάρκεια Ζωής"; +"%d%% off" = "Έκπτωση %d%%"; diff --git a/RevenueCatUI/Resources/en.lproj/Localizable.strings b/RevenueCatUI/Resources/en.lproj/Localizable.strings index 88631ea105..079ac27f56 100644 --- a/RevenueCatUI/Resources/en.lproj/Localizable.strings +++ b/RevenueCatUI/Resources/en.lproj/Localizable.strings @@ -14,3 +14,4 @@ "Monthly" = "Monthly"; "Weekly" = "Weekly"; "Lifetime" = "Lifetime"; +"%d%% off" = "%d%% off"; diff --git a/RevenueCatUI/Resources/en_AU.lproj/Localizable.strings b/RevenueCatUI/Resources/en_AU.lproj/Localizable.strings index 88631ea105..079ac27f56 100644 --- a/RevenueCatUI/Resources/en_AU.lproj/Localizable.strings +++ b/RevenueCatUI/Resources/en_AU.lproj/Localizable.strings @@ -14,3 +14,4 @@ "Monthly" = "Monthly"; "Weekly" = "Weekly"; "Lifetime" = "Lifetime"; +"%d%% off" = "%d%% off"; diff --git a/RevenueCatUI/Resources/en_CA.lproj/Localizable.strings b/RevenueCatUI/Resources/en_CA.lproj/Localizable.strings index 88631ea105..079ac27f56 100644 --- a/RevenueCatUI/Resources/en_CA.lproj/Localizable.strings +++ b/RevenueCatUI/Resources/en_CA.lproj/Localizable.strings @@ -14,3 +14,4 @@ "Monthly" = "Monthly"; "Weekly" = "Weekly"; "Lifetime" = "Lifetime"; +"%d%% off" = "%d%% off"; diff --git a/RevenueCatUI/Resources/en_GB.lproj/Localizable.strings b/RevenueCatUI/Resources/en_GB.lproj/Localizable.strings index 88631ea105..079ac27f56 100644 --- a/RevenueCatUI/Resources/en_GB.lproj/Localizable.strings +++ b/RevenueCatUI/Resources/en_GB.lproj/Localizable.strings @@ -14,3 +14,4 @@ "Monthly" = "Monthly"; "Weekly" = "Weekly"; "Lifetime" = "Lifetime"; +"%d%% off" = "%d%% off"; diff --git a/RevenueCatUI/Resources/en_US.lproj/Localizable.strings b/RevenueCatUI/Resources/en_US.lproj/Localizable.strings index 88631ea105..079ac27f56 100644 --- a/RevenueCatUI/Resources/en_US.lproj/Localizable.strings +++ b/RevenueCatUI/Resources/en_US.lproj/Localizable.strings @@ -14,3 +14,4 @@ "Monthly" = "Monthly"; "Weekly" = "Weekly"; "Lifetime" = "Lifetime"; +"%d%% off" = "%d%% off"; diff --git a/RevenueCatUI/Resources/es_419.lproj/Localizable.strings b/RevenueCatUI/Resources/es_419.lproj/Localizable.strings index df4a30fbc8..58e9e42a92 100644 --- a/RevenueCatUI/Resources/es_419.lproj/Localizable.strings +++ b/RevenueCatUI/Resources/es_419.lproj/Localizable.strings @@ -14,3 +14,4 @@ "Monthly" = "Mensual"; "Weekly" = "Semanalmente"; "Lifetime" = "Toda la vida"; +"%d%% off" = "%d%% de descuento"; diff --git a/RevenueCatUI/Resources/es_ES.lproj/Localizable.strings b/RevenueCatUI/Resources/es_ES.lproj/Localizable.strings index df4a30fbc8..58e9e42a92 100644 --- a/RevenueCatUI/Resources/es_ES.lproj/Localizable.strings +++ b/RevenueCatUI/Resources/es_ES.lproj/Localizable.strings @@ -14,3 +14,4 @@ "Monthly" = "Mensual"; "Weekly" = "Semanalmente"; "Lifetime" = "Toda la vida"; +"%d%% off" = "%d%% de descuento"; diff --git a/RevenueCatUI/Resources/fi.lproj/Localizable.strings b/RevenueCatUI/Resources/fi.lproj/Localizable.strings index db9284c69c..1312aab4e2 100644 --- a/RevenueCatUI/Resources/fi.lproj/Localizable.strings +++ b/RevenueCatUI/Resources/fi.lproj/Localizable.strings @@ -14,3 +14,4 @@ "Monthly" = "Kuukausittain"; "Weekly" = "Viikoittain"; "Lifetime" = "Elinikä"; +"%d%% off" = "%d%% alennus"; diff --git a/RevenueCatUI/Resources/fr_CA.lproj/Localizable.strings b/RevenueCatUI/Resources/fr_CA.lproj/Localizable.strings index a762b19b59..d896158685 100644 --- a/RevenueCatUI/Resources/fr_CA.lproj/Localizable.strings +++ b/RevenueCatUI/Resources/fr_CA.lproj/Localizable.strings @@ -14,3 +14,4 @@ "Monthly" = "Mensuel"; "Weekly" = "Hebdomadaire"; "Lifetime" = "Durée de vie"; +"%d%% off" = "%d%% de réduction"; diff --git a/RevenueCatUI/Resources/he.lproj/Localizable.strings b/RevenueCatUI/Resources/he.lproj/Localizable.strings index bb6e6d014a..a81c1be5d9 100644 --- a/RevenueCatUI/Resources/he.lproj/Localizable.strings +++ b/RevenueCatUI/Resources/he.lproj/Localizable.strings @@ -14,3 +14,4 @@ "Monthly" = "יַרחוֹן"; "Weekly" = "שְׁבוּעִי"; "Lifetime" = "לכל החיים"; +"%d%% off" = "%d%% הנחה"; diff --git a/RevenueCatUI/Resources/hi.lproj/Localizable.strings b/RevenueCatUI/Resources/hi.lproj/Localizable.strings index 9264a75e11..91496f387a 100644 --- a/RevenueCatUI/Resources/hi.lproj/Localizable.strings +++ b/RevenueCatUI/Resources/hi.lproj/Localizable.strings @@ -14,3 +14,4 @@ "Monthly" = "महीने के"; "Weekly" = "साप्ताहिक"; "Lifetime" = "जीवनभर"; +"%d%% off" = "%d%% छूट"; diff --git a/RevenueCatUI/Resources/hr.lproj/Localizable.strings b/RevenueCatUI/Resources/hr.lproj/Localizable.strings index d6df4f9372..486eddcd94 100644 --- a/RevenueCatUI/Resources/hr.lproj/Localizable.strings +++ b/RevenueCatUI/Resources/hr.lproj/Localizable.strings @@ -14,3 +14,4 @@ "Monthly" = "Mjesečno"; "Weekly" = "Tjedni"; "Lifetime" = "Doživotno"; +"%d%% off" = "%d%% popusta"; diff --git a/RevenueCatUI/Resources/hu.lproj/Localizable.strings b/RevenueCatUI/Resources/hu.lproj/Localizable.strings index 716582c837..4cb4c31b86 100644 --- a/RevenueCatUI/Resources/hu.lproj/Localizable.strings +++ b/RevenueCatUI/Resources/hu.lproj/Localizable.strings @@ -14,3 +14,4 @@ "Monthly" = "Havi"; "Weekly" = "Heti"; "Lifetime" = "Élettartam"; +"%d%% off" = "%d%% kedvezménnyel"; diff --git a/RevenueCatUI/Resources/id.lproj/Localizable.strings b/RevenueCatUI/Resources/id.lproj/Localizable.strings index 035c3d0751..c66c51b387 100644 --- a/RevenueCatUI/Resources/id.lproj/Localizable.strings +++ b/RevenueCatUI/Resources/id.lproj/Localizable.strings @@ -14,3 +14,4 @@ "Monthly" = "Bulanan"; "Weekly" = "Mingguan"; "Lifetime" = "Seumur hidup"; +"%d%% off" = "Diskon %d%%"; diff --git a/RevenueCatUI/Resources/it.lproj/Localizable.strings b/RevenueCatUI/Resources/it.lproj/Localizable.strings index 8a7a729651..3772124acc 100644 --- a/RevenueCatUI/Resources/it.lproj/Localizable.strings +++ b/RevenueCatUI/Resources/it.lproj/Localizable.strings @@ -14,3 +14,4 @@ "Monthly" = "Mensile"; "Weekly" = "settimanalmente"; "Lifetime" = "Tutta la vita"; +"%d%% off" = "Sconto del %d%%"; diff --git a/RevenueCatUI/Resources/ja.lproj/Localizable.strings b/RevenueCatUI/Resources/ja.lproj/Localizable.strings index 301f4dc3d2..7a73c255e5 100644 --- a/RevenueCatUI/Resources/ja.lproj/Localizable.strings +++ b/RevenueCatUI/Resources/ja.lproj/Localizable.strings @@ -14,3 +14,4 @@ "Monthly" = "毎月"; "Weekly" = "毎週"; "Lifetime" = "一生"; +"%d%% off" = "%d%%オフ"; diff --git a/RevenueCatUI/Resources/kk.lproj/Localizable.strings b/RevenueCatUI/Resources/kk.lproj/Localizable.strings index 040db62702..fe6c9455a0 100644 --- a/RevenueCatUI/Resources/kk.lproj/Localizable.strings +++ b/RevenueCatUI/Resources/kk.lproj/Localizable.strings @@ -14,3 +14,4 @@ "Monthly" = "Ай сайын"; "Weekly" = "Апта сайын"; "Lifetime" = "Өмір кезеңі"; +"%d%% off" = "%d%% жеңілдік"; diff --git a/RevenueCatUI/Resources/ko.lproj/Localizable.strings b/RevenueCatUI/Resources/ko.lproj/Localizable.strings index 1e16ac29b7..0315502724 100644 --- a/RevenueCatUI/Resources/ko.lproj/Localizable.strings +++ b/RevenueCatUI/Resources/ko.lproj/Localizable.strings @@ -14,3 +14,4 @@ "Monthly" = "월간 간행물"; "Weekly" = "주간"; "Lifetime" = "일생"; +"%d%% off" = "%d%% 할인"; diff --git a/RevenueCatUI/Resources/ms.lproj/Localizable.strings b/RevenueCatUI/Resources/ms.lproj/Localizable.strings index 0c3b39941d..00ca5cda5d 100644 --- a/RevenueCatUI/Resources/ms.lproj/Localizable.strings +++ b/RevenueCatUI/Resources/ms.lproj/Localizable.strings @@ -14,3 +14,4 @@ "Monthly" = "Bulanan"; "Weekly" = "Setiap minggu"; "Lifetime" = "Seumur hidup"; +"%d%% off" = "%d%% diskaun"; diff --git a/RevenueCatUI/Resources/nl.lproj/Localizable.strings b/RevenueCatUI/Resources/nl.lproj/Localizable.strings index b252bd77b6..3af0dd99a3 100644 --- a/RevenueCatUI/Resources/nl.lproj/Localizable.strings +++ b/RevenueCatUI/Resources/nl.lproj/Localizable.strings @@ -14,3 +14,4 @@ "Monthly" = "Maandelijks"; "Weekly" = "Wekelijks"; "Lifetime" = "Levenslang"; +"%d%% off" = "%d%% korting"; diff --git a/RevenueCatUI/Resources/no.lproj/Localizable.strings b/RevenueCatUI/Resources/no.lproj/Localizable.strings index d17e617596..7276382313 100644 --- a/RevenueCatUI/Resources/no.lproj/Localizable.strings +++ b/RevenueCatUI/Resources/no.lproj/Localizable.strings @@ -14,3 +14,4 @@ "Monthly" = "Månedlig"; "Weekly" = "Ukentlig"; "Lifetime" = "Livstid"; +"%d%% off" = "%d %% rabatt"; diff --git a/RevenueCatUI/Resources/pl.lproj/Localizable.strings b/RevenueCatUI/Resources/pl.lproj/Localizable.strings index 47b852678a..137e56a94a 100644 --- a/RevenueCatUI/Resources/pl.lproj/Localizable.strings +++ b/RevenueCatUI/Resources/pl.lproj/Localizable.strings @@ -14,3 +14,4 @@ "Monthly" = "Miesięczny"; "Weekly" = "Co tydzień"; "Lifetime" = "Dożywotni"; +"%d%% off" = "%d%% zniżki"; diff --git a/RevenueCatUI/Resources/pt_BR.lproj/Localizable.strings b/RevenueCatUI/Resources/pt_BR.lproj/Localizable.strings index 7d8b730074..a9267c0f1a 100644 --- a/RevenueCatUI/Resources/pt_BR.lproj/Localizable.strings +++ b/RevenueCatUI/Resources/pt_BR.lproj/Localizable.strings @@ -14,3 +14,4 @@ "Monthly" = "Por mês"; "Weekly" = "Semanalmente"; "Lifetime" = "Vida"; +"%d%% off" = "%d%% de desconto"; diff --git a/RevenueCatUI/Resources/pt_PT.lproj/Localizable.strings b/RevenueCatUI/Resources/pt_PT.lproj/Localizable.strings index 7d8b730074..a9267c0f1a 100644 --- a/RevenueCatUI/Resources/pt_PT.lproj/Localizable.strings +++ b/RevenueCatUI/Resources/pt_PT.lproj/Localizable.strings @@ -14,3 +14,4 @@ "Monthly" = "Por mês"; "Weekly" = "Semanalmente"; "Lifetime" = "Vida"; +"%d%% off" = "%d%% de desconto"; diff --git a/RevenueCatUI/Resources/ro.lproj/Localizable.strings b/RevenueCatUI/Resources/ro.lproj/Localizable.strings index 6e1e471f06..f9cf0bea91 100644 --- a/RevenueCatUI/Resources/ro.lproj/Localizable.strings +++ b/RevenueCatUI/Resources/ro.lproj/Localizable.strings @@ -14,3 +14,4 @@ "Monthly" = "Lunar"; "Weekly" = "Săptămânal"; "Lifetime" = "Durata de viață"; +"%d%% off" = "%d%% reducere"; diff --git a/RevenueCatUI/Resources/ru.lproj/Localizable.strings b/RevenueCatUI/Resources/ru.lproj/Localizable.strings index 773acdd55d..ffc0021d78 100644 --- a/RevenueCatUI/Resources/ru.lproj/Localizable.strings +++ b/RevenueCatUI/Resources/ru.lproj/Localizable.strings @@ -14,3 +14,4 @@ "Monthly" = "Ежемесячно"; "Weekly" = "Еженедельно"; "Lifetime" = "Продолжительность жизни"; +"%d%% off" = "Скидка %d%%"; diff --git a/RevenueCatUI/Resources/sk.lproj/Localizable.strings b/RevenueCatUI/Resources/sk.lproj/Localizable.strings index e5a418279f..2ce7c7f4c1 100644 --- a/RevenueCatUI/Resources/sk.lproj/Localizable.strings +++ b/RevenueCatUI/Resources/sk.lproj/Localizable.strings @@ -14,3 +14,4 @@ "Monthly" = "Mesačne"; "Weekly" = "Týždenne"; "Lifetime" = "Život"; +"%d%% off" = "%d%% zľava"; diff --git a/RevenueCatUI/Resources/sv.lproj/Localizable.strings b/RevenueCatUI/Resources/sv.lproj/Localizable.strings index fa1950b1f4..8886eeaa2b 100644 --- a/RevenueCatUI/Resources/sv.lproj/Localizable.strings +++ b/RevenueCatUI/Resources/sv.lproj/Localizable.strings @@ -14,3 +14,4 @@ "Monthly" = "En gång i månaden"; "Weekly" = "Varje vecka"; "Lifetime" = "Livstid"; +"%d%% off" = "%d%% rabatt"; diff --git a/RevenueCatUI/Resources/th.lproj/Localizable.strings b/RevenueCatUI/Resources/th.lproj/Localizable.strings index 947e0eeb72..e6a98bf189 100644 --- a/RevenueCatUI/Resources/th.lproj/Localizable.strings +++ b/RevenueCatUI/Resources/th.lproj/Localizable.strings @@ -14,3 +14,4 @@ "Monthly" = "รายเดือน"; "Weekly" = "รายสัปดาห์"; "Lifetime" = "อายุการใช้งาน"; +"%d%% off" = "ลด %d%%"; diff --git a/RevenueCatUI/Resources/tr.lproj/Localizable.strings b/RevenueCatUI/Resources/tr.lproj/Localizable.strings index 34a864412b..4841b285df 100644 --- a/RevenueCatUI/Resources/tr.lproj/Localizable.strings +++ b/RevenueCatUI/Resources/tr.lproj/Localizable.strings @@ -14,3 +14,4 @@ "Monthly" = "Aylık"; "Weekly" = "Haftalık"; "Lifetime" = "Ömür"; +"%d%% off" = "%%d%% indirim"; diff --git a/RevenueCatUI/Resources/uk.lproj/Localizable.strings b/RevenueCatUI/Resources/uk.lproj/Localizable.strings index 909e822bc5..ec2120754b 100644 --- a/RevenueCatUI/Resources/uk.lproj/Localizable.strings +++ b/RevenueCatUI/Resources/uk.lproj/Localizable.strings @@ -14,3 +14,4 @@ "Monthly" = "Щомісяця"; "Weekly" = "Щотижня"; "Lifetime" = "Час життя"; +"%d%% off" = "Знижка %d%%"; diff --git a/RevenueCatUI/Resources/vi.lproj/Localizable.strings b/RevenueCatUI/Resources/vi.lproj/Localizable.strings index b707fbfdf7..aaaf8ca298 100644 --- a/RevenueCatUI/Resources/vi.lproj/Localizable.strings +++ b/RevenueCatUI/Resources/vi.lproj/Localizable.strings @@ -14,3 +14,4 @@ "Monthly" = "hàng tháng"; "Weekly" = "hàng tuần"; "Lifetime" = "Cả đời"; +"%d%% off" = "giảm giá %d%%"; diff --git a/RevenueCatUI/Resources/zh-Hans.lproj/Localizable.strings b/RevenueCatUI/Resources/zh-Hans.lproj/Localizable.strings index 58448d67ce..1e1e97f5cc 100644 --- a/RevenueCatUI/Resources/zh-Hans.lproj/Localizable.strings +++ b/RevenueCatUI/Resources/zh-Hans.lproj/Localizable.strings @@ -14,3 +14,4 @@ "Monthly" = "每月"; "Weekly" = "每周"; "Lifetime" = "寿命"; +"%d%% off" = "%d%%折"; diff --git a/RevenueCatUI/Resources/zh_HK.lproj/Localizable.strings b/RevenueCatUI/Resources/zh_HK.lproj/Localizable.strings index 297a98706f..1433d9e560 100644 --- a/RevenueCatUI/Resources/zh_HK.lproj/Localizable.strings +++ b/RevenueCatUI/Resources/zh_HK.lproj/Localizable.strings @@ -14,3 +14,4 @@ "Monthly" = "每月"; "Weekly" = "每週"; "Lifetime" = "壽命"; +"%d%% off" = "%d%%折"; diff --git a/RevenueCatUI/Resources/zh_TW.lproj/Localizable.strings b/RevenueCatUI/Resources/zh_TW.lproj/Localizable.strings index 297a98706f..1433d9e560 100644 --- a/RevenueCatUI/Resources/zh_TW.lproj/Localizable.strings +++ b/RevenueCatUI/Resources/zh_TW.lproj/Localizable.strings @@ -14,3 +14,4 @@ "Monthly" = "每月"; "Weekly" = "每週"; "Lifetime" = "壽命"; +"%d%% off" = "%d%%折"; diff --git a/RevenueCatUI/Views/PurchaseButton.swift b/RevenueCatUI/Views/PurchaseButton.swift index 8f390bc8da..6de771a9d3 100644 --- a/RevenueCatUI/Views/PurchaseButton.swift +++ b/RevenueCatUI/Views/PurchaseButton.swift @@ -165,8 +165,11 @@ struct PurchaseButton_Previews: PreviewProvider { private static let package: TemplateViewConfiguration.Package = .init( content: TestData.packageWithIntroOffer, - localization: TestData.localization1.processVariables(with: TestData.packageWithIntroOffer, - locale: .current), + localization: TestData.localization1.processVariables( + with: TestData.packageWithIntroOffer, + context: .init(discountRelativeToMostExpensivePerMonth: nil), + locale: .current + ), currentlySubscribed: Bool.random(), discountRelativeToMostExpensivePerMonth: nil ) diff --git a/Tests/RevenueCatUITests/Data/PackageVariablesTests.swift b/Tests/RevenueCatUITests/Data/PackageVariablesTests.swift index 7b3db9ad44..bb73fee0ae 100644 --- a/Tests/RevenueCatUITests/Data/PackageVariablesTests.swift +++ b/Tests/RevenueCatUITests/Data/PackageVariablesTests.swift @@ -157,6 +157,16 @@ class PackageVariablesTests: TestCase { expect(TestData.lifetimePackage.localizedIntroductoryOfferPrice).to(beNil()) } + func testEnglishRelativeDiscount() { + expect(TestData.weeklyPackage.localizedRelativeDiscount(nil, Self.english)).to(beNil()) + expect(TestData.weeklyPackage.localizedRelativeDiscount(0.372, Self.english)) == "37% off" + } + + func testSpanishRelativeDiscount() { + expect(TestData.weeklyPackage.localizedRelativeDiscount(nil, Self.spanish)).to(beNil()) + expect(TestData.weeklyPackage.localizedRelativeDiscount(0.372, Self.spanish)) == "37% de descuento" + } + } @available(iOS 15.0, macOS 12.0, tvOS 15.0, watchOS 8.0, *) diff --git a/Tests/RevenueCatUITests/Data/TemplateViewConfigurationTests.swift b/Tests/RevenueCatUITests/Data/TemplateViewConfigurationTests.swift index 7d19672195..edf4d68a8f 100644 --- a/Tests/RevenueCatUITests/Data/TemplateViewConfigurationTests.swift +++ b/Tests/RevenueCatUITests/Data/TemplateViewConfigurationTests.swift @@ -104,6 +104,8 @@ class TemplateViewConfigurationCreationTests: BaseTemplateViewConfigurationTests switch result { case let .single(package): expect(package.content) === TestData.lifetimePackage + expect(package.currentlySubscribed) == false + expect(package.discountRelativeToMostExpensivePerMonth).to(beNil()) Self.verifyLocalizationWasProcessed(package.localization, for: TestData.lifetimePackage) case .multiple: fail("Invalid result: \(result)") diff --git a/Tests/RevenueCatUITests/Data/VariablesTests.swift b/Tests/RevenueCatUITests/Data/VariablesTests.swift index bbacfe8455..d484bf3059 100644 --- a/Tests/RevenueCatUITests/Data/VariablesTests.swift +++ b/Tests/RevenueCatUITests/Data/VariablesTests.swift @@ -18,11 +18,13 @@ import XCTest class VariablesTests: TestCase { private var provider: MockVariableProvider! + private var context: VariableHandler.Context! override func setUp() { super.setUp() self.provider = .init() + self.context = .init(discountRelativeToMostExpensivePerMonth: nil) } func testEmptyString() { @@ -87,6 +89,11 @@ class VariablesTests: TestCase { expect(self.process("{{ sub_offer_price }}")) == self.provider.localizedIntroductoryOfferPrice } + func testRelativeDiscount() { + self.provider.relativeDiscount = "30% off" + expect(self.process("{{ sub_relative_discount }}")) == "30% off" + } + func testMultipleVariables() { self.provider.productName = "Pro" self.provider.localizedPricePerMonth = "$1.99" @@ -113,11 +120,14 @@ class VariablesTests: TestCase { content: "Trial lasts {{ sub_offer_duration }}", iconID: nil), .init(title: "Only {{ price }}", - content: "{{ sub_period }} subscription", + content: "{{ sub_period }} subscription, {{ sub_relative_discount }}", iconID: nil) ] ) - let processed = configuration.processVariables(with: TestData.packageWithIntroOffer) + let processed = configuration.processVariables( + with: TestData.packageWithIntroOffer, + context: .init(discountRelativeToMostExpensivePerMonth: 0.3) + ) expect(processed.title) == "Buy PRO monthly for xctest" expect(processed.subtitle) == "Price: $3.99" @@ -131,7 +141,7 @@ class VariablesTests: TestCase { content: "Trial lasts 1 week", iconID: nil), .init(title: "Only $3.99", - content: "Monthly subscription", + content: "Monthly subscription, 30% off", iconID: nil) ] } @@ -139,32 +149,32 @@ class VariablesTests: TestCase { // Note: this isn't perfect, but a warning is logged // and it's better than crashing. func testPricePerMonthForLifetimeProductsReturnsPrice() { - let result = VariableHandler.processVariables( - in: "{{ sub_price_per_month }}", + let result = self.process( + "{{ sub_price_per_month }}", with: TestData.lifetimePackage ) expect(result) == "$119.49" } func testTotalPriceAndPerMonthForLifetimeProductsReturnsPrice() { - let result = VariableHandler.processVariables( - in: "{{ total_price_and_per_month }}", + let result = self.process( + "{{ total_price_and_per_month }}", with: TestData.lifetimePackage ) expect(result) == "$119.49" } func testTotalPriceAndPerMonthForForMonthlyPackage() { - let result = VariableHandler.processVariables( - in: "{{ total_price_and_per_month }}", + let result = self.process( + "{{ total_price_and_per_month }}", with: TestData.monthlyPackage ) expect(result) == "$6.99/mo" } func testTotalPriceAndPerMonthForCustomMonthlyProductsReturnsPrice() { - let result = VariableHandler.processVariables( - in: "{{ total_price_and_per_month }}", + let result = self.process( + "{{ total_price_and_per_month }}", with: Package( identifier: "custom", packageType: .custom, @@ -176,8 +186,8 @@ class VariablesTests: TestCase { } func testTotalPriceAndPerMonthForCustomAnnualProductsReturnsPriceAndPerMonth() { - let result = VariableHandler.processVariables( - in: "{{ total_price_and_per_month }}", + let result = self.process( + "{{ total_price_and_per_month }}", with: Package( identifier: "custom", packageType: .custom, @@ -188,13 +198,27 @@ class VariablesTests: TestCase { expect(result) == "$53.99/yr ($4.49/mo)" } - // MARK: - validation + func testRelativeDiscountWithNoDiscount() { + self.context.discountRelativeToMostExpensivePerMonth = nil + let result = self.process("{{ sub_relative_discount }}", with: TestData.monthlyPackage) + + expect(result) == "" + } + + func testRelativeDiscountWithDiscount() { + self.context.discountRelativeToMostExpensivePerMonth = 0.3 + let result = self.process("{{ sub_relative_discount }}", with: TestData.monthlyPackage) + + expect(result) == "30% off" + } + + // MARK: - Validation func testNoUnrecognizedVariables() { let allVariables = "{{ app_name }} {{ price }} {{ price_per_period }} " + "{{ total_price_and_per_month }} {{ product_name }} {{ sub_period }} " + "{{ sub_price_per_month }} {{ sub_duration }} {{ sub_offer_duration }} " + - "{{ sub_offer_price }}" + "{{ sub_offer_price }} {{ sub_relative_discount }}" expect("".unrecognizedVariables()).to(beEmpty()) expect(allVariables.unrecognizedVariables()).to(beEmpty()) @@ -214,8 +238,17 @@ class VariablesTests: TestCase { @available(iOS 15.0, macOS 12.0, tvOS 15.0, watchOS 8.0, *) private extension VariablesTests { - func process(_ string: String, locale: Locale = .current) -> String { - return VariableHandler.processVariables(in: string, with: self.provider, locale: locale) + func process( + _ string: String, + with provider: VariableDataProvider? = nil, + locale: Locale = .current + ) -> String { + return VariableHandler.processVariables( + in: string, + with: provider ?? self.provider, + context: self.context, + locale: locale + ) } } @@ -232,6 +265,7 @@ private struct MockVariableProvider: VariableDataProvider { var subscriptionDuration: String? var introductoryOfferDuration: String? var introductoryOfferPrice: String = "" + var relativeDiscount: String? func periodName(_ locale: Locale) -> String { return self.periodName @@ -257,4 +291,8 @@ private struct MockVariableProvider: VariableDataProvider { return self.introductoryOfferPrice } + func localizedRelativeDiscount(_ discount: Double?, _ locale: Locale) -> String? { + return self.relativeDiscount + } + } diff --git a/Tests/RevenueCatUITests/LocalizationTests.swift b/Tests/RevenueCatUITests/LocalizationTests.swift index 87e7c60b18..9572c2907d 100644 --- a/Tests/RevenueCatUITests/LocalizationTests.swift +++ b/Tests/RevenueCatUITests/LocalizationTests.swift @@ -265,14 +265,12 @@ class DiscountSpanishLocalizationTests: BaseLocalizationTests { override var locale: Locale { return .init(identifier: "es_ES") } func testLocalization() throws { - try XCTSkipIf(true, "Localization not available yet") - - verify(0, "Ahorra 0%") - verify(0.1, "Ahorra 10%") - verify(0.331, "Ahorra 33%") - verify(0.5, "Ahorra 50%") - verify(1, "Ahorra 100%") - verify(1.1, "Ahorra 110%") + verify(0, "0% de descuento") + verify(0.1, "10% de descuento") + verify(0.331, "33% de descuento") + verify(0.5, "50% de descuento") + verify(1, "100% de descuento") + verify(1.1, "110% de descuento") } } @@ -280,7 +278,7 @@ class DiscountSpanishLocalizationTests: BaseLocalizationTests { @available(iOS 15.0, macOS 12.0, tvOS 15.0, watchOS 8.0, *) class DiscountOtherLanguageLocalizationTests: BaseLocalizationTests { - override var locale: Locale { return .init(identifier: "fr") } + override var locale: Locale { return .init(identifier: "ci") } func testLocalizationDefaultsToEnglish() { verify(1, "100% off")