@billdog.io/react-native 1.0.0-beta.2
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/BillDogReactNative.podspec +30 -0
- package/LICENSE +21 -0
- package/README.md +330 -0
- package/android/build.gradle +99 -0
- package/android/src/main/AndroidManifest.xml +1 -0
- package/android/src/main/kotlin/io/billdog/reactnative/BillDogABTestModule.kt +317 -0
- package/android/src/main/kotlin/io/billdog/reactnative/BillDogAnalyticsModule.kt +389 -0
- package/android/src/main/kotlin/io/billdog/reactnative/BillDogModule.kt +817 -0
- package/android/src/main/kotlin/io/billdog/reactnative/BillDogPaywallViewManager.kt +383 -0
- package/android/src/main/kotlin/io/billdog/reactnative/BillDogSurveyModule.kt +427 -0
- package/android/src/main/kotlin/io/billdog/reactnative/BillDogVirtualCurrencyModule.kt +407 -0
- package/android/src/main/kotlin/io/billdog/virtualcurrency/BillDogVirtualCurrencyPackage.kt +44 -0
- package/dist/AdvancedPaywallCache.d.ts +70 -0
- package/dist/AdvancedPaywallCache.js +344 -0
- package/dist/BillDog.d.ts +255 -0
- package/dist/BillDog.js +918 -0
- package/dist/BillDogConfiguration.d.ts +145 -0
- package/dist/BillDogConfiguration.js +229 -0
- package/dist/ComponentRenderer.d.ts +17 -0
- package/dist/ComponentRenderer.js +886 -0
- package/dist/LayoutEngine.d.ts +101 -0
- package/dist/LayoutEngine.js +394 -0
- package/dist/LocalizationResolver.d.ts +99 -0
- package/dist/LocalizationResolver.js +266 -0
- package/dist/NativePaywallView.d.ts +36 -0
- package/dist/NativePaywallView.js +59 -0
- package/dist/PaywallCache.d.ts +74 -0
- package/dist/PaywallCache.js +293 -0
- package/dist/PaywallStateManager.d.ts +62 -0
- package/dist/PaywallStateManager.js +147 -0
- package/dist/PaywallView.d.ts +42 -0
- package/dist/PaywallView.js +275 -0
- package/dist/ProductVariableProvider.d.ts +60 -0
- package/dist/ProductVariableProvider.js +239 -0
- package/dist/VariableResolver.d.ts +120 -0
- package/dist/VariableResolver.js +379 -0
- package/dist/WebViewPaywall.d.ts +75 -0
- package/dist/WebViewPaywall.js +360 -0
- package/dist/animations/presets.d.ts +49 -0
- package/dist/animations/presets.js +367 -0
- package/dist/components/AccoladeCpsComponent.d.ts +13 -0
- package/dist/components/AccoladeCpsComponent.js +248 -0
- package/dist/components/AccordionComponent.d.ts +10 -0
- package/dist/components/AccordionComponent.js +187 -0
- package/dist/components/AnimatedWrapper.d.ts +35 -0
- package/dist/components/AnimatedWrapper.js +277 -0
- package/dist/components/CarouselComponent.d.ts +23 -0
- package/dist/components/CarouselComponent.js +484 -0
- package/dist/components/ChartCurveComponent.d.ts +13 -0
- package/dist/components/ChartCurveComponent.js +333 -0
- package/dist/components/CheckboxComponent.d.ts +9 -0
- package/dist/components/CheckboxComponent.js +172 -0
- package/dist/components/ConfettiComponent.d.ts +9 -0
- package/dist/components/ConfettiComponent.js +172 -0
- package/dist/components/CountdownComponent.d.ts +16 -0
- package/dist/components/CountdownComponent.js +414 -0
- package/dist/components/DelayedXComponent.d.ts +12 -0
- package/dist/components/DelayedXComponent.js +240 -0
- package/dist/components/DrawerModalComponent.d.ts +20 -0
- package/dist/components/DrawerModalComponent.js +286 -0
- package/dist/components/GradientView.d.ts +38 -0
- package/dist/components/GradientView.js +126 -0
- package/dist/components/HapticFeedbackComponent.d.ts +16 -0
- package/dist/components/HapticFeedbackComponent.js +64 -0
- package/dist/components/HorizontalProductsComponent.d.ts +17 -0
- package/dist/components/HorizontalProductsComponent.js +284 -0
- package/dist/components/MultiChoiceGridComponent.d.ts +13 -0
- package/dist/components/MultiChoiceGridComponent.js +234 -0
- package/dist/components/NPSScaleComponent.d.ts +16 -0
- package/dist/components/NPSScaleComponent.js +260 -0
- package/dist/components/NavigationComponent.d.ts +26 -0
- package/dist/components/NavigationComponent.js +302 -0
- package/dist/components/OAuthButtonComponent.d.ts +20 -0
- package/dist/components/OAuthButtonComponent.js +237 -0
- package/dist/components/ProgressLoaderComponent.d.ts +13 -0
- package/dist/components/ProgressLoaderComponent.js +308 -0
- package/dist/components/ProgressStepperComponent.d.ts +18 -0
- package/dist/components/ProgressStepperComponent.js +228 -0
- package/dist/components/SegmentedPickerComponent.d.ts +10 -0
- package/dist/components/SegmentedPickerComponent.js +221 -0
- package/dist/components/ShareButtonsComponent.d.ts +21 -0
- package/dist/components/ShareButtonsComponent.js +265 -0
- package/dist/components/ShareCodeComponent.d.ts +16 -0
- package/dist/components/ShareCodeComponent.js +274 -0
- package/dist/components/SingleChoiceGridComponent.d.ts +13 -0
- package/dist/components/SingleChoiceGridComponent.js +210 -0
- package/dist/components/SliderComponent.d.ts +9 -0
- package/dist/components/SliderComponent.js +263 -0
- package/dist/components/StarRatingInputComponent.d.ts +20 -0
- package/dist/components/StarRatingInputComponent.js +198 -0
- package/dist/components/StickerCompositeComponent.d.ts +13 -0
- package/dist/components/StickerCompositeComponent.js +136 -0
- package/dist/components/TabsComponent.d.ts +19 -0
- package/dist/components/TabsComponent.js +431 -0
- package/dist/components/TextInputComponent.d.ts +18 -0
- package/dist/components/TextInputComponent.js +227 -0
- package/dist/components/TimelineComponent.d.ts +19 -0
- package/dist/components/TimelineComponent.js +104 -0
- package/dist/components/ToggleComponent.d.ts +14 -0
- package/dist/components/ToggleComponent.js +132 -0
- package/dist/components/VerticalProductsComponent.d.ts +17 -0
- package/dist/components/VerticalProductsComponent.js +311 -0
- package/dist/dynamicValues.d.ts +36 -0
- package/dist/dynamicValues.js +142 -0
- package/dist/filters/dateFilters.d.ts +50 -0
- package/dist/filters/dateFilters.js +390 -0
- package/dist/filters/index.d.ts +18 -0
- package/dist/filters/index.js +253 -0
- package/dist/hooks/useOfferings.d.ts +76 -0
- package/dist/hooks/useOfferings.js +99 -0
- package/dist/hooks/usePaywall.d.ts +27 -0
- package/dist/hooks/usePaywall.js +114 -0
- package/dist/hooks/usePlacement.d.ts +34 -0
- package/dist/hooks/usePlacement.js +89 -0
- package/dist/hooks/useSubscription.d.ts +40 -0
- package/dist/hooks/useSubscription.js +97 -0
- package/dist/index.d.ts +98 -0
- package/dist/index.js +198 -0
- package/dist/models/BillDogError.d.ts +40 -0
- package/dist/models/BillDogError.js +11 -0
- package/dist/models/CacheFetchPolicy.d.ts +8 -0
- package/dist/models/CacheFetchPolicy.js +4 -0
- package/dist/models/CustomProduct.d.ts +32 -0
- package/dist/models/CustomProduct.js +15 -0
- package/dist/models/CustomerInfo.d.ts +17 -0
- package/dist/models/CustomerInfo.js +11 -0
- package/dist/models/Delegate.d.ts +20 -0
- package/dist/models/Delegate.js +11 -0
- package/dist/models/EntitlementInfo.d.ts +19 -0
- package/dist/models/EntitlementInfo.js +2 -0
- package/dist/models/Placement.d.ts +54 -0
- package/dist/models/Placement.js +15 -0
- package/dist/models/PurchaseParams.d.ts +39 -0
- package/dist/models/PurchaseParams.js +60 -0
- package/dist/models/PurchaseResult.d.ts +30 -0
- package/dist/models/PurchaseResult.js +15 -0
- package/dist/models/RestoreResult.d.ts +30 -0
- package/dist/models/RestoreResult.js +22 -0
- package/dist/models/RestoredItem.d.ts +26 -0
- package/dist/models/RestoredItem.js +13 -0
- package/dist/modules/ABTest.d.ts +65 -0
- package/dist/modules/ABTest.js +113 -0
- package/dist/modules/Analytics.d.ts +91 -0
- package/dist/modules/Analytics.js +182 -0
- package/dist/modules/Survey.d.ts +65 -0
- package/dist/modules/Survey.js +82 -0
- package/dist/modules/VirtualCurrency.d.ts +63 -0
- package/dist/modules/VirtualCurrency.js +147 -0
- package/dist/murmur.d.ts +23 -0
- package/dist/murmur.js +104 -0
- package/dist/notifications/LiveActivitiesManager.d.ts +47 -0
- package/dist/notifications/LiveActivitiesManager.js +106 -0
- package/dist/notifications/NotificationScheduler.d.ts +55 -0
- package/dist/notifications/NotificationScheduler.js +199 -0
- package/dist/notifications/PurchaseNotificationHandler.d.ts +24 -0
- package/dist/notifications/PurchaseNotificationHandler.js +55 -0
- package/dist/notifications/useDeviceToken.d.ts +36 -0
- package/dist/notifications/useDeviceToken.js +97 -0
- package/dist/notifications/useLiveActivity.d.ts +39 -0
- package/dist/notifications/useLiveActivity.js +104 -0
- package/dist/review/ReviewAnalytics.d.ts +34 -0
- package/dist/review/ReviewAnalytics.js +73 -0
- package/dist/review/ReviewManager.d.ts +83 -0
- package/dist/review/ReviewManager.js +339 -0
- package/dist/review/ReviewTimingManager.d.ts +82 -0
- package/dist/review/ReviewTimingManager.js +224 -0
- package/dist/survey-core/types.d.ts +215 -0
- package/dist/survey-core/types.js +8 -0
- package/dist/types.d.ts +438 -0
- package/dist/types.js +158 -0
- package/dist/utils/ApiClient.d.ts +65 -0
- package/dist/utils/ApiClient.js +132 -0
- package/dist/utils/CSSConverter.d.ts +49 -0
- package/dist/utils/CSSConverter.js +470 -0
- package/dist/utils/animations.d.ts +41 -0
- package/dist/utils/animations.js +194 -0
- package/dist/utils/calendarIntegration.d.ts +36 -0
- package/dist/utils/calendarIntegration.js +89 -0
- package/dist/utils/configurationResolver.d.ts +23 -0
- package/dist/utils/configurationResolver.js +78 -0
- package/dist/utils/deviceDetection.d.ts +38 -0
- package/dist/utils/deviceDetection.js +59 -0
- package/dist/utils/hapticFeedback.d.ts +17 -0
- package/dist/utils/hapticFeedback.js +53 -0
- package/dist/utils/iconMapper.d.ts +26 -0
- package/dist/utils/iconMapper.js +113 -0
- package/dist/utils/notificationPermissions.d.ts +13 -0
- package/dist/utils/notificationPermissions.js +51 -0
- package/dist/utils/priceFormatting.d.ts +19 -0
- package/dist/utils/priceFormatting.js +99 -0
- package/dist/utils/shareSheet.d.ts +17 -0
- package/dist/utils/shareSheet.js +42 -0
- package/dist/utils/subscriptionUpgrade.d.ts +84 -0
- package/dist/utils/subscriptionUpgrade.js +100 -0
- package/dist/valueUtils.d.ts +48 -0
- package/dist/valueUtils.js +129 -0
- package/dist/xFlowTags.d.ts +18 -0
- package/dist/xFlowTags.js +385 -0
- package/ios/BillDogABTest.m +50 -0
- package/ios/BillDogABTest.swift +188 -0
- package/ios/BillDogAnalytics.m +105 -0
- package/ios/BillDogAnalytics.swift +324 -0
- package/ios/BillDogBridge.m +222 -0
- package/ios/BillDogBridge.swift +715 -0
- package/ios/BillDogLiveActivities.m +41 -0
- package/ios/BillDogLiveActivities.swift +104 -0
- package/ios/BillDogPaywallViewManager.m +39 -0
- package/ios/BillDogPaywallViewManager.swift +361 -0
- package/ios/BillDogSurvey.m +34 -0
- package/ios/BillDogSurvey.swift +257 -0
- package/ios/BillDogVirtualCurrencyBridge.m +63 -0
- package/ios/BillDogVirtualCurrencyBridge.swift +511 -0
- package/junit.xml +25 -0
- package/package.json +61 -0
- package/react-native.config.js +13 -0
- package/src/AdvancedPaywallCache.ts +433 -0
- package/src/BillDog.ts +983 -0
- package/src/BillDogConfiguration.ts +318 -0
- package/src/ComponentRenderer.tsx +1325 -0
- package/src/LayoutEngine.ts +459 -0
- package/src/LocalizationResolver.ts +311 -0
- package/src/NativePaywallView.tsx +132 -0
- package/src/PaywallCache.ts +358 -0
- package/src/PaywallStateManager.ts +154 -0
- package/src/PaywallView.tsx +328 -0
- package/src/ProductVariableProvider.ts +282 -0
- package/src/VariableResolver.ts +516 -0
- package/src/WebViewPaywall.tsx +454 -0
- package/src/animations/presets.ts +421 -0
- package/src/components/AccoladeCpsComponent.tsx +259 -0
- package/src/components/AccordionComponent.tsx +213 -0
- package/src/components/AnimatedWrapper.tsx +316 -0
- package/src/components/CarouselComponent.tsx +615 -0
- package/src/components/ChartCurveComponent.tsx +452 -0
- package/src/components/CheckboxComponent.tsx +185 -0
- package/src/components/ConfettiComponent.tsx +178 -0
- package/src/components/CountdownComponent.tsx +454 -0
- package/src/components/DelayedXComponent.tsx +266 -0
- package/src/components/DrawerModalComponent.tsx +324 -0
- package/src/components/GradientView.tsx +129 -0
- package/src/components/HapticFeedbackComponent.tsx +39 -0
- package/src/components/HorizontalProductsComponent.tsx +329 -0
- package/src/components/MultiChoiceGridComponent.tsx +265 -0
- package/src/components/NPSScaleComponent.tsx +294 -0
- package/src/components/NavigationComponent.tsx +329 -0
- package/src/components/OAuthButtonComponent.tsx +266 -0
- package/src/components/ProgressLoaderComponent.tsx +366 -0
- package/src/components/ProgressStepperComponent.tsx +250 -0
- package/src/components/SegmentedPickerComponent.tsx +245 -0
- package/src/components/ShareButtonsComponent.tsx +299 -0
- package/src/components/ShareCodeComponent.tsx +303 -0
- package/src/components/SingleChoiceGridComponent.tsx +229 -0
- package/src/components/SliderComponent.tsx +280 -0
- package/src/components/StarRatingInputComponent.tsx +223 -0
- package/src/components/StickerCompositeComponent.tsx +168 -0
- package/src/components/TabsComponent.tsx +496 -0
- package/src/components/TextInputComponent.tsx +258 -0
- package/src/components/TimelineComponent.tsx +129 -0
- package/src/components/ToggleComponent.tsx +142 -0
- package/src/components/VerticalProductsComponent.tsx +357 -0
- package/src/dynamicValues.ts +180 -0
- package/src/filters/dateFilters.ts +411 -0
- package/src/filters/index.ts +281 -0
- package/src/hooks/useOfferings.ts +192 -0
- package/src/hooks/usePaywall.ts +154 -0
- package/src/hooks/usePlacement.ts +143 -0
- package/src/hooks/useSubscription.ts +154 -0
- package/src/index.ts +245 -0
- package/src/models/BillDogError.ts +24 -0
- package/src/models/CacheFetchPolicy.ts +13 -0
- package/src/models/CustomProduct.ts +33 -0
- package/src/models/CustomerInfo.ts +26 -0
- package/src/models/Delegate.ts +22 -0
- package/src/models/EntitlementInfo.ts +27 -0
- package/src/models/Placement.ts +50 -0
- package/src/models/PurchaseParams.ts +81 -0
- package/src/models/PurchaseResult.ts +32 -0
- package/src/models/RestoreResult.ts +27 -0
- package/src/models/RestoredItem.ts +25 -0
- package/src/modules/ABTest.ts +131 -0
- package/src/modules/Analytics.ts +227 -0
- package/src/modules/Survey.ts +126 -0
- package/src/modules/VirtualCurrency.ts +223 -0
- package/src/murmur.ts +102 -0
- package/src/notifications/LiveActivitiesManager.ts +140 -0
- package/src/notifications/NotificationScheduler.ts +292 -0
- package/src/notifications/PurchaseNotificationHandler.ts +67 -0
- package/src/notifications/useDeviceToken.ts +116 -0
- package/src/notifications/useLiveActivity.ts +125 -0
- package/src/review/ReviewAnalytics.ts +89 -0
- package/src/review/ReviewManager.ts +355 -0
- package/src/review/ReviewTimingManager.ts +265 -0
- package/src/survey-core/types.ts +244 -0
- package/src/types/ambient.d.ts +9 -0
- package/src/types.ts +680 -0
- package/src/utils/ApiClient.ts +163 -0
- package/src/utils/CSSConverter.ts +552 -0
- package/src/utils/animations.ts +232 -0
- package/src/utils/calendarIntegration.ts +113 -0
- package/src/utils/configurationResolver.ts +106 -0
- package/src/utils/deviceDetection.ts +83 -0
- package/src/utils/hapticFeedback.ts +55 -0
- package/src/utils/iconMapper.ts +102 -0
- package/src/utils/notificationPermissions.ts +48 -0
- package/src/utils/priceFormatting.ts +119 -0
- package/src/utils/shareSheet.ts +55 -0
- package/src/utils/subscriptionUpgrade.ts +183 -0
- package/src/valueUtils.ts +108 -0
- package/src/xFlowTags.ts +463 -0
|
@@ -0,0 +1,311 @@
|
|
|
1
|
+
import { Platform, NativeModules } from 'react-native';
|
|
2
|
+
import { PaywallConfiguration, PaywallComponent } from './types';
|
|
3
|
+
import { BillDogConfiguration } from './BillDogConfiguration';
|
|
4
|
+
|
|
5
|
+
/**
|
|
6
|
+
* Properties that can be localized
|
|
7
|
+
*/
|
|
8
|
+
const LOCALIZABLE_PROPERTIES = new Set([
|
|
9
|
+
'text', 'title', 'subtitle', 'label', 'placeholder',
|
|
10
|
+
'buttonText', 'ctaText', 'description', 'content',
|
|
11
|
+
'headerText', 'footerText', 'disclaimer'
|
|
12
|
+
]);
|
|
13
|
+
|
|
14
|
+
/**
|
|
15
|
+
* Right-to-left languages. CLDR-aligned 11-code set — kept identical to
|
|
16
|
+
* iOS `PaywallRenderer.swift rtlLocales` and Android
|
|
17
|
+
* `PaywallScreen.kt rtlLocales` so layout direction is consistent across
|
|
18
|
+
* every platform.
|
|
19
|
+
*/
|
|
20
|
+
const RTL_LOCALES = new Set([
|
|
21
|
+
'ar', 'he', 'fa', 'ur', 'ps', 'sd', 'yi', 'ku', 'ckb', 'dv', 'ug',
|
|
22
|
+
]);
|
|
23
|
+
|
|
24
|
+
/**
|
|
25
|
+
* Client-side localization resolver for React Native SDK
|
|
26
|
+
* Resolves localized text based on device locale with fallback to baseLocale
|
|
27
|
+
*/
|
|
28
|
+
export class LocalizationResolver {
|
|
29
|
+
private localizations?: Record<string, Record<string, string>>;
|
|
30
|
+
private baseLocale: string;
|
|
31
|
+
private selectedLocale: string;
|
|
32
|
+
|
|
33
|
+
constructor(
|
|
34
|
+
localizations?: Record<string, Record<string, string>>,
|
|
35
|
+
baseLocale: string = 'en',
|
|
36
|
+
explicitLocale?: string
|
|
37
|
+
) {
|
|
38
|
+
this.localizations = localizations;
|
|
39
|
+
this.baseLocale = baseLocale;
|
|
40
|
+
// Priority: explicit locale > global config override > device locale
|
|
41
|
+
this.selectedLocale = explicitLocale ?? this.getEffectiveLocale();
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
/**
|
|
45
|
+
* Get the effective locale. Precedence mirrors Android
|
|
46
|
+
* `BillDogConfig.getEffectiveLocale` (BillDogConfig.kt:150-152):
|
|
47
|
+
* runtime locale override > device locale > "en".
|
|
48
|
+
*
|
|
49
|
+
* Public + static so callers (e.g. WebViewPaywall's locale bridge) can
|
|
50
|
+
* resolve the active locale without constructing a resolver.
|
|
51
|
+
*/
|
|
52
|
+
static getEffectiveLocale(): string {
|
|
53
|
+
const configOverride = BillDogConfiguration.getLocaleOverride();
|
|
54
|
+
if (configOverride && configOverride.trim()) {
|
|
55
|
+
return configOverride;
|
|
56
|
+
}
|
|
57
|
+
return LocalizationResolver.detectDeviceLocale();
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
/** Instance accessor — defers to the canonical static resolver. */
|
|
61
|
+
private getEffectiveLocale(): string {
|
|
62
|
+
return LocalizationResolver.getEffectiveLocale();
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
/**
|
|
66
|
+
* Returns true when `locale`'s language belongs to a right-to-left
|
|
67
|
+
* script. Matches against the language portion only (e.g. `ar-EG` → `ar`),
|
|
68
|
+
* mirroring Android `PaywallScreen.kt:431-458`.
|
|
69
|
+
*/
|
|
70
|
+
static isRtlLocale(locale: string | null | undefined): boolean {
|
|
71
|
+
if (!locale) return false;
|
|
72
|
+
const language = locale.split(/[-_]/)[0]?.toLowerCase() ?? '';
|
|
73
|
+
return RTL_LOCALES.has(language);
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
/** Detect the device locale from the platform native modules. */
|
|
77
|
+
static detectDeviceLocale(): string {
|
|
78
|
+
try {
|
|
79
|
+
if (Platform.OS === 'ios') {
|
|
80
|
+
return (
|
|
81
|
+
NativeModules.SettingsManager?.settings?.AppleLocale?.split('_')[0] ||
|
|
82
|
+
NativeModules.SettingsManager?.settings?.AppleLanguages?.[0]?.split('-')[0] ||
|
|
83
|
+
'en'
|
|
84
|
+
);
|
|
85
|
+
} else if (Platform.OS === 'android') {
|
|
86
|
+
return NativeModules.I18nManager?.localeIdentifier?.split('_')[0] || 'en';
|
|
87
|
+
}
|
|
88
|
+
} catch {
|
|
89
|
+
// Fall through to default.
|
|
90
|
+
}
|
|
91
|
+
return 'en';
|
|
92
|
+
}
|
|
93
|
+
|
|
94
|
+
/**
|
|
95
|
+
* Set the active locale for resolution
|
|
96
|
+
*/
|
|
97
|
+
setLocale(locale: string): void {
|
|
98
|
+
this.selectedLocale = locale;
|
|
99
|
+
}
|
|
100
|
+
|
|
101
|
+
/**
|
|
102
|
+
* Get the currently selected locale
|
|
103
|
+
*/
|
|
104
|
+
getSelectedLocale(): string {
|
|
105
|
+
return this.selectedLocale;
|
|
106
|
+
}
|
|
107
|
+
|
|
108
|
+
/**
|
|
109
|
+
* Get the base locale (source language)
|
|
110
|
+
*/
|
|
111
|
+
getBaseLocale(): string {
|
|
112
|
+
return this.baseLocale;
|
|
113
|
+
}
|
|
114
|
+
|
|
115
|
+
/**
|
|
116
|
+
* Find the matching locale key using the canonical 4-step matcher.
|
|
117
|
+
* Mirrors Android `LocalizationResolver.findLocaleKey` (LocalizationResolver.kt:50-86)
|
|
118
|
+
* and iOS `LocalizationResolver.findLocaleKey` exactly:
|
|
119
|
+
* 1. exact match
|
|
120
|
+
* 2. case-insensitive match
|
|
121
|
+
* 3. separator-normalized match (en-US ↔ en_US)
|
|
122
|
+
* 4. language-only fallback (en-US → en, or en → first en-* regional)
|
|
123
|
+
*/
|
|
124
|
+
private findLocaleKey(locale: string): string | undefined {
|
|
125
|
+
if (!this.localizations || !locale) return undefined;
|
|
126
|
+
const keys = Object.keys(this.localizations);
|
|
127
|
+
|
|
128
|
+
// Step 1: exact match
|
|
129
|
+
if (this.localizations[locale]) return locale;
|
|
130
|
+
|
|
131
|
+
// Step 2: case-insensitive match
|
|
132
|
+
const lower = locale.toLowerCase();
|
|
133
|
+
const ciMatch = keys.find(k => k.toLowerCase() === lower);
|
|
134
|
+
if (ciMatch) return ciMatch;
|
|
135
|
+
|
|
136
|
+
// Step 3: separator-normalized match
|
|
137
|
+
const alt = locale.includes('-')
|
|
138
|
+
? locale.replace(/-/g, '_')
|
|
139
|
+
: locale.replace(/_/g, '-');
|
|
140
|
+
if (alt !== locale) {
|
|
141
|
+
if (this.localizations[alt]) return alt;
|
|
142
|
+
const altLower = alt.toLowerCase();
|
|
143
|
+
const altMatch = keys.find(k => k.toLowerCase() === altLower);
|
|
144
|
+
if (altMatch) return altMatch;
|
|
145
|
+
}
|
|
146
|
+
|
|
147
|
+
// Step 4: language-only fallback
|
|
148
|
+
const languageOnly = locale.split(/[-_]/)[0] ?? '';
|
|
149
|
+
if (languageOnly) {
|
|
150
|
+
const langLower = languageOnly.toLowerCase();
|
|
151
|
+
// Prefer an exact-language key (e.g. "en") over a regional variant
|
|
152
|
+
const exactLang = keys.find(k => k.toLowerCase() === langLower);
|
|
153
|
+
if (exactLang) return exactLang;
|
|
154
|
+
// Otherwise any regional variant matching the language
|
|
155
|
+
const regional = keys.find(
|
|
156
|
+
k => (k.split(/[-_]/)[0] ?? '').toLowerCase() === langLower
|
|
157
|
+
);
|
|
158
|
+
if (regional) return regional;
|
|
159
|
+
}
|
|
160
|
+
|
|
161
|
+
return undefined;
|
|
162
|
+
}
|
|
163
|
+
|
|
164
|
+
/**
|
|
165
|
+
* Check if current locale is the base locale
|
|
166
|
+
*/
|
|
167
|
+
isBaseLocale(): boolean {
|
|
168
|
+
return this.selectedLocale.toLowerCase() === this.baseLocale.toLowerCase();
|
|
169
|
+
}
|
|
170
|
+
|
|
171
|
+
/**
|
|
172
|
+
* Get available locales from the localization data
|
|
173
|
+
*/
|
|
174
|
+
getAvailableLocales(): Set<string> {
|
|
175
|
+
const locales = new Set<string>([this.baseLocale]);
|
|
176
|
+
if (this.localizations) {
|
|
177
|
+
Object.keys(this.localizations).forEach(key => locales.add(key));
|
|
178
|
+
}
|
|
179
|
+
return locales;
|
|
180
|
+
}
|
|
181
|
+
|
|
182
|
+
/**
|
|
183
|
+
* Resolve a localization key to its translated value
|
|
184
|
+
* Falls back to defaultValue if translation not found
|
|
185
|
+
*/
|
|
186
|
+
resolve(key: string, defaultValue: string): string {
|
|
187
|
+
// If selected locale is base locale, return default (original) value
|
|
188
|
+
if (this.isBaseLocale()) {
|
|
189
|
+
return defaultValue;
|
|
190
|
+
}
|
|
191
|
+
|
|
192
|
+
// Try to find translation for selected locale (case-insensitive)
|
|
193
|
+
const matchedKey = this.findLocaleKey(this.selectedLocale);
|
|
194
|
+
const translations = matchedKey ? this.localizations![matchedKey] : undefined;
|
|
195
|
+
const translated = translations?.[key];
|
|
196
|
+
|
|
197
|
+
if (translated !== undefined) {
|
|
198
|
+
return translated;
|
|
199
|
+
}
|
|
200
|
+
|
|
201
|
+
// Log missing translation for debugging
|
|
202
|
+
console.debug(
|
|
203
|
+
`[LocalizationResolver] Translation not found for key '${key}' in locale '${this.selectedLocale}', using default`
|
|
204
|
+
);
|
|
205
|
+
return defaultValue;
|
|
206
|
+
}
|
|
207
|
+
|
|
208
|
+
/**
|
|
209
|
+
* Resolve a variable value with localization support
|
|
210
|
+
* Variable keys are prefixed with locale for lookup
|
|
211
|
+
*/
|
|
212
|
+
resolveVariable(variableKey: string, defaultValue: string): string {
|
|
213
|
+
if (this.isBaseLocale()) {
|
|
214
|
+
return defaultValue;
|
|
215
|
+
}
|
|
216
|
+
|
|
217
|
+
// Try locale-prefixed key first (e.g., "es.headline" for Spanish)
|
|
218
|
+
const localizedKey = `${this.selectedLocale}.${variableKey}`;
|
|
219
|
+
const matchedKey = this.findLocaleKey(this.selectedLocale);
|
|
220
|
+
const translations = matchedKey ? this.localizations![matchedKey] : undefined;
|
|
221
|
+
|
|
222
|
+
return (
|
|
223
|
+
translations?.[localizedKey] ??
|
|
224
|
+
translations?.[variableKey] ??
|
|
225
|
+
defaultValue
|
|
226
|
+
);
|
|
227
|
+
}
|
|
228
|
+
|
|
229
|
+
/**
|
|
230
|
+
* Resolve all localizable properties in a component.
|
|
231
|
+
*
|
|
232
|
+
* Lookup keys are componentId-prefixed (`<componentId>.<propertyName>`),
|
|
233
|
+
* matching the shape the dashboard `LocalizationPanel` writes and the
|
|
234
|
+
* Android `VariableResolver.resolveComponent` runtime path
|
|
235
|
+
* (VariableResolver.kt:124-156) + iOS `LocalizationResolver.swift:194-198`.
|
|
236
|
+
* Previously this looked up the source string itself as the key, which
|
|
237
|
+
* never matched the dashboard-authored translation maps.
|
|
238
|
+
*/
|
|
239
|
+
resolveComponent(component: PaywallComponent): PaywallComponent {
|
|
240
|
+
const resolvedProperties: Record<string, any> = {};
|
|
241
|
+
const componentId = component.id ?? '';
|
|
242
|
+
|
|
243
|
+
for (const [key, value] of Object.entries(component.properties || {})) {
|
|
244
|
+
if (typeof value === 'string' && LOCALIZABLE_PROPERTIES.has(key)) {
|
|
245
|
+
const localizationKey = componentId ? `${componentId}.${key}` : key;
|
|
246
|
+
resolvedProperties[key] = this.resolve(localizationKey, value);
|
|
247
|
+
} else {
|
|
248
|
+
resolvedProperties[key] = value;
|
|
249
|
+
}
|
|
250
|
+
}
|
|
251
|
+
|
|
252
|
+
// Recursively resolve children
|
|
253
|
+
const resolvedChildren = component.children?.map(child =>
|
|
254
|
+
this.resolveComponent(child)
|
|
255
|
+
);
|
|
256
|
+
|
|
257
|
+
return {
|
|
258
|
+
...component,
|
|
259
|
+
properties: resolvedProperties,
|
|
260
|
+
children: resolvedChildren,
|
|
261
|
+
};
|
|
262
|
+
}
|
|
263
|
+
|
|
264
|
+
/**
|
|
265
|
+
* Resolve all components in a configuration
|
|
266
|
+
*/
|
|
267
|
+
resolveConfiguration(config: PaywallConfiguration): PaywallConfiguration {
|
|
268
|
+
return {
|
|
269
|
+
...config,
|
|
270
|
+
components: config.components.map(c => this.resolveComponent(c)),
|
|
271
|
+
};
|
|
272
|
+
}
|
|
273
|
+
|
|
274
|
+
// Factory Methods
|
|
275
|
+
|
|
276
|
+
/**
|
|
277
|
+
* Create resolver from PaywallConfiguration
|
|
278
|
+
* Respects global locale override from BillDogConfiguration
|
|
279
|
+
*/
|
|
280
|
+
static fromConfiguration(config: PaywallConfiguration): LocalizationResolver {
|
|
281
|
+
return new LocalizationResolver(
|
|
282
|
+
config.localizations,
|
|
283
|
+
config.baseLocale ?? 'en'
|
|
284
|
+
);
|
|
285
|
+
}
|
|
286
|
+
|
|
287
|
+
/**
|
|
288
|
+
* Create resolver with device locale auto-detected
|
|
289
|
+
* Respects global locale override from BillDogConfiguration
|
|
290
|
+
*/
|
|
291
|
+
static withDeviceLocale(config: PaywallConfiguration): LocalizationResolver {
|
|
292
|
+
return new LocalizationResolver(
|
|
293
|
+
config.localizations,
|
|
294
|
+
config.baseLocale ?? 'en'
|
|
295
|
+
);
|
|
296
|
+
}
|
|
297
|
+
|
|
298
|
+
/**
|
|
299
|
+
* Create resolver with specific locale (overrides global config)
|
|
300
|
+
*/
|
|
301
|
+
static withLocale(
|
|
302
|
+
config: PaywallConfiguration,
|
|
303
|
+
locale: string
|
|
304
|
+
): LocalizationResolver {
|
|
305
|
+
return new LocalizationResolver(
|
|
306
|
+
config.localizations,
|
|
307
|
+
config.baseLocale ?? 'en',
|
|
308
|
+
locale
|
|
309
|
+
);
|
|
310
|
+
}
|
|
311
|
+
}
|
|
@@ -0,0 +1,132 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* NativePaywallView — renders a BillDog paywall by embedding the NATIVE paywall view
|
|
3
|
+
* (SwiftUI/UIKit on iOS, Jetpack Compose on Android) via a legacy (Paper) native
|
|
4
|
+
* component (`BDPaywallView`). This is the thin-bridge "Option 2" inline path: the
|
|
5
|
+
* native BillDog SDK renders the paywall and handles purchases; this component only
|
|
6
|
+
* hosts it and forwards its events.
|
|
7
|
+
*
|
|
8
|
+
* The pure-JS `PaywallView`/`ComponentRenderer` and `WebViewPaywall` remain available
|
|
9
|
+
* as alternative rendering strategies (e.g. for environments without the native module).
|
|
10
|
+
*/
|
|
11
|
+
import React from 'react';
|
|
12
|
+
import {
|
|
13
|
+
requireNativeComponent,
|
|
14
|
+
UIManager,
|
|
15
|
+
StyleSheet,
|
|
16
|
+
View,
|
|
17
|
+
Text,
|
|
18
|
+
Platform,
|
|
19
|
+
type NativeSyntheticEvent,
|
|
20
|
+
type StyleProp,
|
|
21
|
+
type ViewStyle,
|
|
22
|
+
} from 'react-native';
|
|
23
|
+
|
|
24
|
+
const COMPONENT_NAME = 'BDPaywallView';
|
|
25
|
+
|
|
26
|
+
/** True only when the native view manager is actually registered on this platform. */
|
|
27
|
+
const isNativeComponentAvailable =
|
|
28
|
+
UIManager.getViewManagerConfig?.(COMPONENT_NAME) != null;
|
|
29
|
+
|
|
30
|
+
type PurchaseEvent = { productId: string; data?: Record<string, unknown> };
|
|
31
|
+
type ErrorEvent = { message: string };
|
|
32
|
+
type OpenUrlEvent = { url: string };
|
|
33
|
+
type TrackEvent = { event: string; data?: Record<string, unknown> };
|
|
34
|
+
|
|
35
|
+
interface NativeProps {
|
|
36
|
+
paywallIdentifier: string;
|
|
37
|
+
style?: StyleProp<ViewStyle>;
|
|
38
|
+
onLoaded?: (e: NativeSyntheticEvent<Record<string, never>>) => void;
|
|
39
|
+
onError?: (e: NativeSyntheticEvent<ErrorEvent>) => void;
|
|
40
|
+
onPurchase?: (e: NativeSyntheticEvent<PurchaseEvent>) => void;
|
|
41
|
+
onDismiss?: (e: NativeSyntheticEvent<Record<string, never>>) => void;
|
|
42
|
+
onRestore?: (e: NativeSyntheticEvent<Record<string, never>>) => void;
|
|
43
|
+
onOpenUrl?: (e: NativeSyntheticEvent<OpenUrlEvent>) => void;
|
|
44
|
+
onTrackEvent?: (e: NativeSyntheticEvent<TrackEvent>) => void;
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
// Only bind the native component when it exists — requireNativeComponent otherwise
|
|
48
|
+
// logs a hard error on some RN versions.
|
|
49
|
+
const BDPaywallViewNative = isNativeComponentAvailable
|
|
50
|
+
? requireNativeComponent<NativeProps>(COMPONENT_NAME)
|
|
51
|
+
: null;
|
|
52
|
+
|
|
53
|
+
export interface NativePaywallViewProps {
|
|
54
|
+
/** Placement / paywall identifier to load. */
|
|
55
|
+
identifier: string;
|
|
56
|
+
style?: ViewStyle;
|
|
57
|
+
/** Fired after a completed purchase (productId + native result fields). */
|
|
58
|
+
onPurchase?: (productId: string, data?: Record<string, unknown>) => void;
|
|
59
|
+
onDismiss?: () => void;
|
|
60
|
+
onRestore?: () => void;
|
|
61
|
+
onOpenUrl?: (url: string) => void;
|
|
62
|
+
onTrackEvent?: (event: string, data?: Record<string, unknown>) => void;
|
|
63
|
+
onLoaded?: () => void;
|
|
64
|
+
onError?: (message: string) => void;
|
|
65
|
+
/** Rendered when the native paywall component is unavailable on this platform. */
|
|
66
|
+
fallback?: React.ReactNode;
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
/**
|
|
70
|
+
* Inline native paywall. Requires the `BDPaywallView` native component (iOS
|
|
71
|
+
* `BillDogPaywallViewManager`, Android `BillDogPaywallViewManager`). When it is not
|
|
72
|
+
* registered, renders `fallback` (or nothing) instead of crashing.
|
|
73
|
+
*/
|
|
74
|
+
export function NativePaywallView(props: NativePaywallViewProps): React.ReactElement | null {
|
|
75
|
+
const {
|
|
76
|
+
identifier,
|
|
77
|
+
style,
|
|
78
|
+
onPurchase,
|
|
79
|
+
onDismiss,
|
|
80
|
+
onRestore,
|
|
81
|
+
onOpenUrl,
|
|
82
|
+
onTrackEvent,
|
|
83
|
+
onLoaded,
|
|
84
|
+
onError,
|
|
85
|
+
fallback,
|
|
86
|
+
} = props;
|
|
87
|
+
|
|
88
|
+
if (BDPaywallViewNative == null) {
|
|
89
|
+
if (fallback != null) return <>{fallback}</>;
|
|
90
|
+
if (__DEV__) {
|
|
91
|
+
return (
|
|
92
|
+
<View style={[styles.fill, styles.center, style]}>
|
|
93
|
+
<Text style={styles.warn}>
|
|
94
|
+
BillDog: native paywall component "{COMPONENT_NAME}" is not registered
|
|
95
|
+
on {Platform.OS}. Ensure the native SDK is linked, or use PaywallView /
|
|
96
|
+
WebViewPaywall.
|
|
97
|
+
</Text>
|
|
98
|
+
</View>
|
|
99
|
+
);
|
|
100
|
+
}
|
|
101
|
+
return null;
|
|
102
|
+
}
|
|
103
|
+
|
|
104
|
+
return (
|
|
105
|
+
<BDPaywallViewNative
|
|
106
|
+
paywallIdentifier={identifier}
|
|
107
|
+
style={[styles.fill, style]}
|
|
108
|
+
onLoaded={() => onLoaded?.()}
|
|
109
|
+
onError={(e) => onError?.(e.nativeEvent?.message ?? 'Unknown paywall error')}
|
|
110
|
+
onPurchase={(e) =>
|
|
111
|
+
onPurchase?.(e.nativeEvent?.productId ?? '', e.nativeEvent?.data)
|
|
112
|
+
}
|
|
113
|
+
onDismiss={() => onDismiss?.()}
|
|
114
|
+
onRestore={() => onRestore?.()}
|
|
115
|
+
onOpenUrl={(e) => onOpenUrl?.(e.nativeEvent?.url ?? '')}
|
|
116
|
+
onTrackEvent={(e) =>
|
|
117
|
+
onTrackEvent?.(e.nativeEvent?.event ?? '', e.nativeEvent?.data)
|
|
118
|
+
}
|
|
119
|
+
/>
|
|
120
|
+
);
|
|
121
|
+
}
|
|
122
|
+
|
|
123
|
+
/** Whether the native paywall component is available on the current platform. */
|
|
124
|
+
export const isNativePaywallAvailable = isNativeComponentAvailable;
|
|
125
|
+
|
|
126
|
+
const styles = StyleSheet.create({
|
|
127
|
+
fill: { flex: 1 },
|
|
128
|
+
center: { alignItems: 'center', justifyContent: 'center', padding: 16 },
|
|
129
|
+
warn: { color: '#B00020', textAlign: 'center', fontSize: 13 },
|
|
130
|
+
});
|
|
131
|
+
|
|
132
|
+
export default NativePaywallView;
|