diff --git a/SubscriberCount/Localizable.xcstrings b/SubscriberCount/Localizable.xcstrings index bfd1501..03d6559 100644 --- a/SubscriberCount/Localizable.xcstrings +++ b/SubscriberCount/Localizable.xcstrings @@ -9,43 +9,43 @@ "ar" : { "stringUnit" : { "state" : "translated", - "value" : "- أضف قناة في التطبيق، ثم انقر على هذه الواجهة أثناء التحرير" + "value" : "- أضف قناة في التطبيق، ثم اضغط على هذه الأداة أثناء تعديلها" } }, "de" : { "stringUnit" : { "state" : "translated", - "value" : "- Fügen Sie einen Kanal in der App hinzu und tippen Sie dann auf dieses Widget, während Sie es bearbeiten" + "value" : "- Fügen Sie zuerst in der App einen Kanal hinzu und tippen Sie dann beim Bearbeiten auf dieses Widget" } }, "es" : { "stringUnit" : { "state" : "translated", - "value" : "- Agrega un canal en la aplicación, luego toca este widget mientras lo editas" + "value" : "- Agrega un canal en la app y luego toca este widget mientras lo editas" } }, "fr" : { "stringUnit" : { "state" : "translated", - "value" : "- Ajoutez une chaîne dans l'application, puis touchez ce widget en cours de modification" + "value" : "- Ajoutez d’abord une chaîne dans l’app, puis touchez ce widget pendant sa modification" } }, "hi" : { "stringUnit" : { "state" : "translated", - "value" : "- ऐप में एक चैनल जोड़ें, फिर संपादन के दौरान इस विजेट को टैप करें" + "value" : "- पहले ऐप में एक चैनल जोड़ें, फिर एडिट करते समय इस विजेट पर टैप करें" } }, "pt-BR" : { "stringUnit" : { "state" : "translated", - "value" : "- Adicione um canal no aplicativo e depois toque neste widget enquanto edita" + "value" : "- Adicione um canal no app e depois toque neste widget enquanto o estiver editando" } }, "ta" : { "stringUnit" : { "state" : "translated", - "value" : "- செயலியில் ஒரு சேனலைச் சேர்க்கவும், பின்னர் இந்த விஜெட்டை திருத்தும்போது தட்டவும்" + "value" : "- முதலில் செயலியில் ஒரு சேனலைச் சேர்க்கவும், பின்னர் திருத்தும் போது இந்த விட்ஜெட்டைத் தட்டவும்" } } } @@ -239,25 +239,25 @@ "ar" : { "stringUnit" : { "state" : "translated", - "value" : "أضف قناة في التطبيق" + "value" : "أضف قناة داخل التطبيق" } }, "de" : { "stringUnit" : { "state" : "translated", - "value" : "Fügen Sie einen Kanal in der App hinzu" + "value" : "Fügen Sie in der App einen Kanal hinzu" } }, "es" : { "stringUnit" : { "state" : "translated", - "value" : "Agrega un canal en la aplicación" + "value" : "Agrega un canal en la app" } }, "fr" : { "stringUnit" : { "state" : "translated", - "value" : "Ajoutez une chaîne dans l'application" + "value" : "Ajoutez une chaîne dans l’app" } }, "hi" : { @@ -269,7 +269,7 @@ "pt-BR" : { "stringUnit" : { "state" : "translated", - "value" : "Adicione um canal no aplicativo" + "value" : "Adicione um canal no app" } }, "ta" : { @@ -281,6 +281,7 @@ } }, "Currently, you can add up to 10 channels. If you would like to add more, please contact me using the link in Settings and I will increase the limit." : { + "extractionState" : "stale", "localizations" : { "ar" : { "stringUnit" : { @@ -377,13 +378,13 @@ "ar" : { "stringUnit" : { "state" : "translated", - "value" : "احصل على SubWidget Pro لاستخدام هذا الودجت" + "value" : "احصل على SubWidget Pro لاستخدام هذه الأداة" } }, "de" : { "stringUnit" : { "state" : "translated", - "value" : "Hol dir SubWidget Pro, um dieses Widget zu nutzen" + "value" : "Hol dir SubWidget Pro, um dieses Widget zu verwenden" } }, "es" : { @@ -395,13 +396,13 @@ "fr" : { "stringUnit" : { "state" : "translated", - "value" : "Obtenez SubWidget Pro pour utiliser ce widget" + "value" : "Passez à SubWidget Pro pour utiliser ce widget" } }, "hi" : { "stringUnit" : { "state" : "translated", - "value" : "इस विजेट का उपयोग करने के लिए SubWidget Pro लें" + "value" : "इस विजेट का उपयोग करने के लिए SubWidget Pro प्राप्त करें" } }, "pt-BR" : { @@ -413,7 +414,7 @@ "ta" : { "stringUnit" : { "state" : "translated", - "value" : "இந்த விட்ஜெட்டை பயன்படுத்த SubWidget Pro ஐ பெறுங்கள்" + "value" : "இந்த விட்ஜெட்டை பயன்படுத்த SubWidget Pro ஐப் பெறுங்கள்" } } } @@ -423,13 +424,13 @@ "ar" : { "stringUnit" : { "state" : "translated", - "value" : "استمر في الضغط وانقر على 'تحرير الواجهة'" + "value" : "اضغط مطولاً ثم اضغط على 'تحرير الأداة'" } }, "de" : { "stringUnit" : { "state" : "translated", - "value" : "Halten und tippen Sie auf 'Widget bearbeiten'" + "value" : "Gedrückt halten und auf 'Widget bearbeiten' tippen" } }, "es" : { @@ -450,7 +451,7 @@ "other" : { "stringUnit" : { "state" : "translated", - "value" : "Maintenez enfoncé et appuyez sur 'Modifier le widget'" + "value" : "Maintenez le doigt puis touchez « Modifier le widget »" } } } @@ -459,19 +460,19 @@ "hi" : { "stringUnit" : { "state" : "translated", - "value" : "'एडिट विजेट' पर टैप करके रखें" + "value" : "दबाकर रखें, फिर 'एडिट विजेट' पर टैप करें" } }, "pt-BR" : { "stringUnit" : { "state" : "translated", - "value" : "Segure e toque em 'Editar Widget'" + "value" : "Toque e segure, depois toque em 'Editar Widget'" } }, "ta" : { "stringUnit" : { "state" : "translated", - "value" : "'விஜெட்டை திருத்து' என்று தட்டி அழுத்தவும்" + "value" : "அழுத்திப் பிடித்து, பின்னர் 'விட்ஜெட்டைத் திருத்து' என்பதைத் தட்டவும்" } } } @@ -567,6 +568,9 @@ } } } + }, + "How do I delete a channel?" : { + }, "How many channels can I add?" : { "localizations" : { @@ -637,7 +641,7 @@ "fr" : { "stringUnit" : { "state" : "translated", - "value" : "Choisissez la fréquence de mise à jour du nombre d'abonnés" + "value" : "À quelle fréquence le nombre d'abonnés est-il mis à jour ?" } }, "hi" : { @@ -751,6 +755,12 @@ } } } + }, + "My question is not listed here." : { + + }, + "Please contact me from the settings screen and I will answer any question you have. I typically respond within a few hours!." : { + }, "Please remove the widget from your homescreen and add it back." : { "localizations" : { @@ -797,8 +807,12 @@ } } } + }, + "Restores the widget's colors to the defaults - White in light mode and black in dark mode." : { + }, "Restores the widget's colors to the defaults." : { + "extractionState" : "stale", "localizations" : { "ar" : { "stringUnit" : { @@ -879,7 +893,7 @@ "pt-BR" : { "stringUnit" : { "state" : "translated", - "value" : "Selecione Seu Canal" + "value" : "Selecione seu canal" } }, "ta" : { @@ -981,6 +995,9 @@ } } } + }, + "Swipe right on the channel to delete it" : { + }, "Tap and hold anywhere on your homescreen and tap the plus button in the top left. Look for SubWidget and add it to your homescreen. Finally, tap and hold on the widget and select your channel." : { "localizations" : { @@ -1355,7 +1372,7 @@ "ar" : { "stringUnit" : { "state" : "translated", - "value" : "فتح القفل" + "value" : "افتح" } }, "de" : { @@ -1391,7 +1408,7 @@ "ta" : { "stringUnit" : { "state" : "translated", - "value" : "திறக்கவும்" + "value" : "திற" } } } @@ -1671,6 +1688,9 @@ } } } + }, + "With SubWidget Pro, you can add up to 10 channels. If you would like to add more, please contact me using the link in Settings and I will increase the limit." : { + }, "You can request a feature using the Wishlist tab below." : { "localizations" : { diff --git a/SubscriberCount/SubWidgetIntentTimelineProvider.swift b/SubscriberCount/SubWidgetIntentTimelineProvider.swift index 44b0b55..914bb9f 100644 --- a/SubscriberCount/SubWidgetIntentTimelineProvider.swift +++ b/SubscriberCount/SubWidgetIntentTimelineProvider.swift @@ -21,7 +21,11 @@ struct SimpleEntry: TimelineEntry { let channelImage: UIImage let widgetType: WidgetType - init(channel: YouTubeChannel?, channelImage: UIImage = UIImage(systemName: "person.circle")!, widgetType: WidgetType) { + init( + channel: YouTubeChannel?, + channelImage: UIImage = UIImage(systemName: "person.circle")!, + widgetType: WidgetType + ) { self.channel = channel self.channelImage = channelImage self.widgetType = widgetType @@ -54,7 +58,14 @@ struct SubWidgetIntentTimelineProvider: IntentTimelineProvider { if configuration.channel == nil { // Show first channel in add widget screen if exists let channels = channelStorageService.getChannels() - if !channels.isEmpty { + if channels.isEmpty { + let entry = SimpleEntry( + channel: nil, + widgetType: widgetType + ) + + completion(entry) + } else { let entry = SimpleEntry( channel: channels[0], channelImage: await getImageForUrl(channels[0].profileImage), @@ -215,7 +226,7 @@ struct SubscriberCountEntryView: View { } private var deepLink: URL? { - if isLocked { + if !hasProAccess { return WidgetDeepLink.paywall } diff --git a/SubscriberCount/Views/LockscreenWidget.swift b/SubscriberCount/Views/LockscreenWidget.swift index 6bef628..5676367 100644 --- a/SubscriberCount/Views/LockscreenWidget.swift +++ b/SubscriberCount/Views/LockscreenWidget.swift @@ -16,6 +16,10 @@ struct LockscreenWidget: View { entry?.channel } + private var usesLocalPreviewImage: Bool { + channel?.profileImage.hasPrefix("OnboardingAvatar-") == true + } + var count: String { switch entry?.widgetType { case .subscribers: @@ -33,13 +37,20 @@ struct LockscreenWidget: View { if let entry = entry, let channel = channel { HStack { - Image(uiImage: entry.channelImage) - .resizable() - .widgetAccentedRenderingMode(.desaturated) - .aspectRatio(contentMode: .fill) - .frame(width: 40, height: 40) - .clipShape(Circle()) - .shadow(radius: 2) + if Utils.isInWidget() || usesLocalPreviewImage { + Image(uiImage: entry.channelImage) + .resizable() + .widgetAccentedRenderingMode(.desaturated) + .aspectRatio(contentMode: .fill) + .frame(width: 40, height: 40) + .clipShape(Circle()) + .shadow(radius: 2) + } else { + AsyncImageView(url: URL(string: channel.profileImage)) + .frame(width: 40, height: 40) + .clipShape(Circle()) + .shadow(radius: 2) + } VStack(alignment: .leading) { Text(channel.channelName) diff --git a/SubscriberCount/Views/MediumWidget.swift b/SubscriberCount/Views/MediumWidget.swift index 4c44e20..76a2776 100644 --- a/SubscriberCount/Views/MediumWidget.swift +++ b/SubscriberCount/Views/MediumWidget.swift @@ -60,12 +60,16 @@ struct MediumWidget: View { return .youtubeRed } + private var usesLocalPreviewImage: Bool { + channel?.profileImage.hasPrefix("OnboardingAvatar-") == true + } + var body: some View { ZStack { if let entry = entry, let channel = channel { HStack { - if Utils.isInWidget() { + if Utils.isInWidget() || usesLocalPreviewImage { Image(uiImage: entry.channelImage) .resizable() .widgetAccentedRenderingMode(.desaturated) diff --git a/SubscriberCount/Views/SmallWidget.swift b/SubscriberCount/Views/SmallWidget.swift index d3ff446..db703d9 100644 --- a/SubscriberCount/Views/SmallWidget.swift +++ b/SubscriberCount/Views/SmallWidget.swift @@ -53,6 +53,10 @@ struct SmallWidget: View { return .youtubeRed } + private var usesLocalPreviewImage: Bool { + channel?.profileImage.hasPrefix("OnboardingAvatar-") == true + } + var body: some View { ZStack { if let entry = entry, @@ -63,7 +67,7 @@ struct SmallWidget: View { VStack(alignment: .leading) { HStack { - if Utils.isInWidget() { + if Utils.isInWidget() || usesLocalPreviewImage { Image(uiImage: entry.channelImage) .resizable() .widgetAccentedRenderingMode(.desaturated) diff --git a/SubscriberWidget/Assets.xcassets/OnboardingAvatar-ishowspeed.imageset/Contents.json b/SubscriberWidget/Assets.xcassets/OnboardingAvatar-ishowspeed.imageset/Contents.json new file mode 100644 index 0000000..fe51784 --- /dev/null +++ b/SubscriberWidget/Assets.xcassets/OnboardingAvatar-ishowspeed.imageset/Contents.json @@ -0,0 +1,21 @@ +{ + "images" : [ + { + "filename" : "ishowspeed.png", + "idiom" : "universal", + "scale" : "1x" + }, + { + "idiom" : "universal", + "scale" : "2x" + }, + { + "idiom" : "universal", + "scale" : "3x" + } + ], + "info" : { + "author" : "xcode", + "version" : 1 + } +} diff --git a/SubscriberWidget/Assets.xcassets/OnboardingAvatar-ishowspeed.imageset/ishowspeed.png b/SubscriberWidget/Assets.xcassets/OnboardingAvatar-ishowspeed.imageset/ishowspeed.png new file mode 100644 index 0000000..2323ad8 Binary files /dev/null and b/SubscriberWidget/Assets.xcassets/OnboardingAvatar-ishowspeed.imageset/ishowspeed.png differ diff --git a/SubscriberWidget/Assets.xcassets/OnboardingAvatar-linus-tech-tips.imageset/Contents.json b/SubscriberWidget/Assets.xcassets/OnboardingAvatar-linus-tech-tips.imageset/Contents.json new file mode 100644 index 0000000..38ef5c7 --- /dev/null +++ b/SubscriberWidget/Assets.xcassets/OnboardingAvatar-linus-tech-tips.imageset/Contents.json @@ -0,0 +1,21 @@ +{ + "images" : [ + { + "filename" : "linus-tech-tips.png", + "idiom" : "universal", + "scale" : "1x" + }, + { + "idiom" : "universal", + "scale" : "2x" + }, + { + "idiom" : "universal", + "scale" : "3x" + } + ], + "info" : { + "author" : "xcode", + "version" : 1 + } +} diff --git a/SubscriberWidget/Assets.xcassets/OnboardingAvatar-linus-tech-tips.imageset/linus-tech-tips.png b/SubscriberWidget/Assets.xcassets/OnboardingAvatar-linus-tech-tips.imageset/linus-tech-tips.png new file mode 100644 index 0000000..d18ac24 Binary files /dev/null and b/SubscriberWidget/Assets.xcassets/OnboardingAvatar-linus-tech-tips.imageset/linus-tech-tips.png differ diff --git a/SubscriberWidget/Assets.xcassets/OnboardingAvatar-mark-rober.imageset/Contents.json b/SubscriberWidget/Assets.xcassets/OnboardingAvatar-mark-rober.imageset/Contents.json new file mode 100644 index 0000000..9f3e885 --- /dev/null +++ b/SubscriberWidget/Assets.xcassets/OnboardingAvatar-mark-rober.imageset/Contents.json @@ -0,0 +1,21 @@ +{ + "images" : [ + { + "filename" : "mark-rober.png", + "idiom" : "universal", + "scale" : "1x" + }, + { + "idiom" : "universal", + "scale" : "2x" + }, + { + "idiom" : "universal", + "scale" : "3x" + } + ], + "info" : { + "author" : "xcode", + "version" : 1 + } +} diff --git a/SubscriberWidget/Assets.xcassets/OnboardingAvatar-mark-rober.imageset/mark-rober.png b/SubscriberWidget/Assets.xcassets/OnboardingAvatar-mark-rober.imageset/mark-rober.png new file mode 100644 index 0000000..b73b327 Binary files /dev/null and b/SubscriberWidget/Assets.xcassets/OnboardingAvatar-mark-rober.imageset/mark-rober.png differ diff --git a/SubscriberWidget/Assets.xcassets/OnboardingAvatar-mkbhd.imageset/Contents.json b/SubscriberWidget/Assets.xcassets/OnboardingAvatar-mkbhd.imageset/Contents.json new file mode 100644 index 0000000..1f6e1f7 --- /dev/null +++ b/SubscriberWidget/Assets.xcassets/OnboardingAvatar-mkbhd.imageset/Contents.json @@ -0,0 +1,21 @@ +{ + "images" : [ + { + "filename" : "mkbhd.png", + "idiom" : "universal", + "scale" : "1x" + }, + { + "idiom" : "universal", + "scale" : "2x" + }, + { + "idiom" : "universal", + "scale" : "3x" + } + ], + "info" : { + "author" : "xcode", + "version" : 1 + } +} diff --git a/SubscriberWidget/Assets.xcassets/OnboardingAvatar-mkbhd.imageset/mkbhd.png b/SubscriberWidget/Assets.xcassets/OnboardingAvatar-mkbhd.imageset/mkbhd.png new file mode 100644 index 0000000..130c23b Binary files /dev/null and b/SubscriberWidget/Assets.xcassets/OnboardingAvatar-mkbhd.imageset/mkbhd.png differ diff --git a/SubscriberWidget/Assets.xcassets/OnboardingAvatar-mrbeast.imageset/Contents.json b/SubscriberWidget/Assets.xcassets/OnboardingAvatar-mrbeast.imageset/Contents.json new file mode 100644 index 0000000..590d074 --- /dev/null +++ b/SubscriberWidget/Assets.xcassets/OnboardingAvatar-mrbeast.imageset/Contents.json @@ -0,0 +1,21 @@ +{ + "images" : [ + { + "filename" : "mrbeast.png", + "idiom" : "universal", + "scale" : "1x" + }, + { + "idiom" : "universal", + "scale" : "2x" + }, + { + "idiom" : "universal", + "scale" : "3x" + } + ], + "info" : { + "author" : "xcode", + "version" : 1 + } +} diff --git a/SubscriberWidget/Assets.xcassets/OnboardingAvatar-mrbeast.imageset/mrbeast.png b/SubscriberWidget/Assets.xcassets/OnboardingAvatar-mrbeast.imageset/mrbeast.png new file mode 100644 index 0000000..913c30e Binary files /dev/null and b/SubscriberWidget/Assets.xcassets/OnboardingAvatar-mrbeast.imageset/mrbeast.png differ diff --git a/SubscriberWidget/Assets.xcassets/OnboardingAvatar-pewdiepie.imageset/Contents.json b/SubscriberWidget/Assets.xcassets/OnboardingAvatar-pewdiepie.imageset/Contents.json new file mode 100644 index 0000000..562e813 --- /dev/null +++ b/SubscriberWidget/Assets.xcassets/OnboardingAvatar-pewdiepie.imageset/Contents.json @@ -0,0 +1,21 @@ +{ + "images" : [ + { + "filename" : "pewdiepie.png", + "idiom" : "universal", + "scale" : "1x" + }, + { + "idiom" : "universal", + "scale" : "2x" + }, + { + "idiom" : "universal", + "scale" : "3x" + } + ], + "info" : { + "author" : "xcode", + "version" : 1 + } +} diff --git a/SubscriberWidget/Assets.xcassets/OnboardingAvatar-pewdiepie.imageset/pewdiepie.png b/SubscriberWidget/Assets.xcassets/OnboardingAvatar-pewdiepie.imageset/pewdiepie.png new file mode 100644 index 0000000..6b89283 Binary files /dev/null and b/SubscriberWidget/Assets.xcassets/OnboardingAvatar-pewdiepie.imageset/pewdiepie.png differ diff --git a/SubscriberWidget/Assets.xcassets/OnboardingAvatar-veritasium.imageset/Contents.json b/SubscriberWidget/Assets.xcassets/OnboardingAvatar-veritasium.imageset/Contents.json new file mode 100644 index 0000000..95e8cac --- /dev/null +++ b/SubscriberWidget/Assets.xcassets/OnboardingAvatar-veritasium.imageset/Contents.json @@ -0,0 +1,21 @@ +{ + "images" : [ + { + "filename" : "veritasium.png", + "idiom" : "universal", + "scale" : "1x" + }, + { + "idiom" : "universal", + "scale" : "2x" + }, + { + "idiom" : "universal", + "scale" : "3x" + } + ], + "info" : { + "author" : "xcode", + "version" : 1 + } +} diff --git a/SubscriberWidget/Assets.xcassets/OnboardingAvatar-veritasium.imageset/veritasium.png b/SubscriberWidget/Assets.xcassets/OnboardingAvatar-veritasium.imageset/veritasium.png new file mode 100644 index 0000000..1b7231a Binary files /dev/null and b/SubscriberWidget/Assets.xcassets/OnboardingAvatar-veritasium.imageset/veritasium.png differ diff --git a/SubscriberWidget/Localizable.xcstrings b/SubscriberWidget/Localizable.xcstrings index 57d3c73..06c4f9e 100644 --- a/SubscriberWidget/Localizable.xcstrings +++ b/SubscriberWidget/Localizable.xcstrings @@ -52,43 +52,89 @@ "ar" : { "stringUnit" : { "state" : "translated", - "value" : "- أضف قناة في التطبيق، ثم انقر على هذه الواجهة أثناء التحرير" + "value" : "- أضف قناة في التطبيق، ثم اضغط على هذه الأداة أثناء تعديلها" } }, "de" : { "stringUnit" : { "state" : "translated", - "value" : "- Fügen Sie einen Kanal in der App hinzu und tippen Sie dann auf dieses Widget, während Sie es bearbeiten" + "value" : "- Fügen Sie zuerst in der App einen Kanal hinzu und tippen Sie dann beim Bearbeiten auf dieses Widget" } }, "es" : { "stringUnit" : { "state" : "translated", - "value" : "- Agrega un canal en la aplicación, luego toca este widget mientras lo editas" + "value" : "- Agrega un canal en la app y luego toca este widget mientras lo editas" } }, "fr" : { "stringUnit" : { "state" : "translated", - "value" : "- Ajoutez une chaîne dans l'application, puis touchez ce widget en cours de modification" + "value" : "- Ajoutez d’abord une chaîne dans l’app, puis touchez ce widget pendant sa modification" } }, "hi" : { "stringUnit" : { "state" : "translated", - "value" : "- ऐप में एक चैनल जोड़ें, फिर संपादन के दौरान इस विजेट को टैप करें" + "value" : "- पहले ऐप में एक चैनल जोड़ें, फिर एडिट करते समय इस विजेट पर टैप करें" } }, "pt-BR" : { "stringUnit" : { "state" : "translated", - "value" : "- Adicione um canal no aplicativo e depois toque neste widget enquanto edita" + "value" : "- Adicione um canal no app e depois toque neste widget enquanto o estiver editando" } }, "ta" : { "stringUnit" : { "state" : "translated", - "value" : "- செயலியில் ஒரு சேனலைச் சேர்க்கவும், பின்னர் இந்த விஜெட்டை திருத்தும்போது தட்டவும்" + "value" : "- முதலில் செயலியில் ஒரு சேனலைச் சேர்க்கவும், பின்னர் திருத்தும் போது இந்த விட்ஜெட்டைத் தட்டவும்" + } + } + } + }, + "@mkbhd" : { + "localizations" : { + "ar" : { + "stringUnit" : { + "state" : "translated", + "value" : "@mkbhd" + } + }, + "de" : { + "stringUnit" : { + "state" : "translated", + "value" : "@mkbhd" + } + }, + "es" : { + "stringUnit" : { + "state" : "translated", + "value" : "@mkbhd" + } + }, + "fr" : { + "stringUnit" : { + "state" : "translated", + "value" : "@mkbhd" + } + }, + "hi" : { + "stringUnit" : { + "state" : "translated", + "value" : "@mkbhd" + } + }, + "pt-BR" : { + "stringUnit" : { + "state" : "translated", + "value" : "@mkbhd" + } + }, + "ta" : { + "stringUnit" : { + "state" : "translated", + "value" : "@mkbhd" } } } @@ -374,25 +420,25 @@ "ar" : { "stringUnit" : { "state" : "translated", - "value" : "أضف قناة في التطبيق" + "value" : "أضف قناة داخل التطبيق" } }, "de" : { "stringUnit" : { "state" : "translated", - "value" : "Fügen Sie einen Kanal in der App hinzu" + "value" : "Fügen Sie in der App einen Kanal hinzu" } }, "es" : { "stringUnit" : { "state" : "translated", - "value" : "Agregar un canal en la aplicación" + "value" : "Agrega un canal en la app" } }, "fr" : { "stringUnit" : { "state" : "translated", - "value" : "Ajoutez une chaîne dans l'application" + "value" : "Ajoutez une chaîne dans l’app" } }, "hi" : { @@ -404,7 +450,7 @@ "pt-BR" : { "stringUnit" : { "state" : "translated", - "value" : "Adicione um canal no aplicativo" + "value" : "Adicione um canal no app" } }, "ta" : { @@ -507,6 +553,144 @@ } } }, + "Add your channel" : { + "localizations" : { + "ar" : { + "stringUnit" : { + "state" : "translated", + "value" : "أضف قناتك" + } + }, + "de" : { + "stringUnit" : { + "state" : "translated", + "value" : "Fügen Sie Ihren Kanal hinzu" + } + }, + "es" : { + "stringUnit" : { + "state" : "translated", + "value" : "Agrega tu canal" + } + }, + "fr" : { + "stringUnit" : { + "state" : "translated", + "value" : "Ajoutez votre chaîne" + } + }, + "hi" : { + "stringUnit" : { + "state" : "translated", + "value" : "अपना चैनल जोड़ें" + } + }, + "pt-BR" : { + "stringUnit" : { + "state" : "translated", + "value" : "Adicione seu canal" + } + }, + "ta" : { + "stringUnit" : { + "state" : "translated", + "value" : "உங்கள் சேனலைச் சேர்க்கவும்" + } + } + } + }, + "Add your first channel for free" : { + "localizations" : { + "ar" : { + "stringUnit" : { + "state" : "translated", + "value" : "أضف قناتك الأولى مجانًا" + } + }, + "de" : { + "stringUnit" : { + "state" : "translated", + "value" : "Fügen Sie Ihren ersten Kanal kostenlos hinzu" + } + }, + "es" : { + "stringUnit" : { + "state" : "translated", + "value" : "Agrega tu primer canal gratis" + } + }, + "fr" : { + "stringUnit" : { + "state" : "translated", + "value" : "Ajoutez votre première chaîne gratuitement" + } + }, + "hi" : { + "stringUnit" : { + "state" : "translated", + "value" : "अपना पहला चैनल मुफ्त में जोड़ें" + } + }, + "pt-BR" : { + "stringUnit" : { + "state" : "translated", + "value" : "Adicione seu primeiro canal grátis" + } + }, + "ta" : { + "stringUnit" : { + "state" : "translated", + "value" : "உங்கள் முதல் சேனலை இலவசமாகச் சேர்க்கவும்" + } + } + } + }, + "Add your first channel now, then place the widget when you’re ready." : { + "localizations" : { + "ar" : { + "stringUnit" : { + "state" : "translated", + "value" : "أضف قناتك الأولى الآن، ثم ضع الأداة عندما تكون جاهزًا." + } + }, + "de" : { + "stringUnit" : { + "state" : "translated", + "value" : "Fügen Sie jetzt Ihren ersten Kanal hinzu und platzieren Sie das Widget, wenn Sie bereit sind." + } + }, + "es" : { + "stringUnit" : { + "state" : "translated", + "value" : "Agrega ahora tu primer canal y coloca el widget cuando quieras." + } + }, + "fr" : { + "stringUnit" : { + "state" : "translated", + "value" : "Ajoutez votre première chaîne maintenant, puis placez le widget quand vous le souhaitez." + } + }, + "hi" : { + "stringUnit" : { + "state" : "translated", + "value" : "अपना पहला चैनल अभी जोड़ें, फिर जब चाहें विजेट लगा दें।" + } + }, + "pt-BR" : { + "stringUnit" : { + "state" : "translated", + "value" : "Adicione seu primeiro canal agora e coloque o widget quando quiser." + } + }, + "ta" : { + "stringUnit" : { + "state" : "translated", + "value" : "உங்கள் முதல் சேனலை இப்போது சேர்க்கவும், தயாரானபோது விட்ஜெட்டை இடவும்." + } + } + } + }, "Amethyst" : { "localizations" : { "ar" : { @@ -553,6 +737,52 @@ } } }, + "AT A GLANCE" : { + "localizations" : { + "ar" : { + "stringUnit" : { + "state" : "translated", + "value" : "بنظرة سريعة" + } + }, + "de" : { + "stringUnit" : { + "state" : "translated", + "value" : "AUF EINEN BLICK" + } + }, + "es" : { + "stringUnit" : { + "state" : "translated", + "value" : "DE UN VISTAZO" + } + }, + "fr" : { + "stringUnit" : { + "state" : "translated", + "value" : "EN UN COUP D’ŒIL" + } + }, + "hi" : { + "stringUnit" : { + "state" : "translated", + "value" : "एक नज़र में" + } + }, + "pt-BR" : { + "stringUnit" : { + "state" : "translated", + "value" : "DE RELANCE" + } + }, + "ta" : { + "stringUnit" : { + "state" : "translated", + "value" : "ஒரே பார்வையில்" + } + } + } + }, "Background" : { "localizations" : { "ar" : { @@ -1250,52 +1480,6 @@ } } }, - "Currently, you can add up to 10 channels. If you would like to add more, please contact me using the link in Settings and I will increase the limit." : { - "localizations" : { - "ar" : { - "stringUnit" : { - "state" : "translated", - "value" : "حاليًا، يمكنك إضافة ما يصل إلى 10 قنوات. إذا كنت ترغب في إضافة المزيد، يرجى الاتصال بي باستخدام الرابط في الإعدادات وسأزيد الحد." - } - }, - "de" : { - "stringUnit" : { - "state" : "translated", - "value" : "Zurzeit können Sie bis zu 10 Kanäle hinzufügen. Wenn Sie mehr hinzufügen möchten, kontaktieren Sie mich bitte über den Link in den Einstellungen und ich werde das Limit erhöhen." - } - }, - "es" : { - "stringUnit" : { - "state" : "translated", - "value" : "Actualmente, puedes agregar hasta 10 canales. Si deseas agregar más, por favor contáctame usando el enlace en Configuración y aumentaré el límite." - } - }, - "fr" : { - "stringUnit" : { - "state" : "translated", - "value" : "Actuellement, vous pouvez ajouter jusqu'à 10 chaînes. Si vous souhaitez en ajouter plus, veuillez me contacter en utilisant le lien dans les paramètres et j'augmenterai la limite." - } - }, - "hi" : { - "stringUnit" : { - "state" : "translated", - "value" : "वर्तमान में, आप अधिकतम 10 चैनल जोड़ सकते हैं। यदि आप और अधिक जोड़ना चाहते हैं, तो कृपया सेटिंग्स में दिए गए लिंक का उपयोग करके मुझसे संपर्क करें और मैं सीमा बढ़ा दूंगा।" - } - }, - "pt-BR" : { - "stringUnit" : { - "state" : "translated", - "value" : "Atualmente, você pode adicionar até 10 canais. Se quiser adicionar mais, por favor, entre em contato comigo usando o link em Configurações e eu aumentarei o limite." - } - }, - "ta" : { - "stringUnit" : { - "state" : "translated", - "value" : "தற்போது, நீங்கள் 10 சேனல்கள் வரை சேர்க்கலாம். நீங்கள் மேலும் சேர்க்க விரும்பினால், அமைப்புகளில் உள்ள இணைப்பைப் பயன்படுத்தி என்னை தொடர்பு கொள்ளவும், நான் வரம்பை அதிகரிக்கிறேன்." - } - } - } - }, "Deepsea" : { "localizations" : { "ar" : { @@ -1664,104 +1848,196 @@ } } }, - "Find My ID" : { + "FIND A CHANNEL" : { "localizations" : { "ar" : { "stringUnit" : { "state" : "translated", - "value" : "اعثر على معرفي" + "value" : "ابحث عن قناة" } }, "de" : { "stringUnit" : { "state" : "translated", - "value" : "Meine ID finden" + "value" : "KANAL FINDEN" } }, "es" : { "stringUnit" : { "state" : "translated", - "value" : "Encuentra mi ID" + "value" : "BUSCA UN CANAL" } }, "fr" : { "stringUnit" : { "state" : "translated", - "value" : "Trouvez mon ID" + "value" : "TROUVEZ UNE CHAÎNE" } }, "hi" : { "stringUnit" : { "state" : "translated", - "value" : "मेरी आईडी खोजें" + "value" : "कोई चैनल खोजें" } }, "pt-BR" : { "stringUnit" : { "state" : "translated", - "value" : "Encontrar Meu ID" + "value" : "ENCONTRE UM CANAL" } }, "ta" : { "stringUnit" : { "state" : "translated", - "value" : "எனது ஐடியைக் கண்டுபிடி" + "value" : "ஒரு சேனலைத் தேடுங்கள்" } } } }, - "First, tap and hold on your Lock Screen and tap 'Customize'. Then you can add the widget and tap it to select the channel." : { + "Find My ID" : { "localizations" : { "ar" : { "stringUnit" : { "state" : "translated", - "value" : "أولاً، انقر واستمر في الضغط على شاشة القفل الخاصة بك وانقر على 'تخصيص'. بعد ذلك، يمكنك إضافة الواجهة والنقر عليها لتحديد القناة." + "value" : "اعثر على معرفي" } }, "de" : { "stringUnit" : { "state" : "translated", - "value" : "Zuerst tippen und halten Sie auf Ihrem Sperrbildschirm und tippen Sie auf 'Anpassen'. Dann können Sie das Widget hinzufügen und darauf tippen, um den Kanal auszuwählen." + "value" : "Meine ID finden" } }, "es" : { "stringUnit" : { "state" : "translated", - "value" : "Primero, toca y mantén presionado en tu pantalla de bloqueo y toca 'Personalizar'. Luego puedes agregar el widget y tocarlo para seleccionar el canal." + "value" : "Encuentra mi ID" } }, "fr" : { "stringUnit" : { "state" : "translated", - "value" : "D'abord, appuyez longuement sur votre écran de verrouillage et appuyez sur 'Personnaliser'. Ensuite, vous pouvez ajouter le widget et appuyer dessus pour sélectionner la chaîne." + "value" : "Trouvez mon ID" } }, "hi" : { "stringUnit" : { "state" : "translated", - "value" : "सबसे पहले, अपनी लॉक स्क्रीन पर टैप करके रखें और 'कस्टमाइज़' पर टैप करें। फिर आप विजेट जोड़ सकते हैं और उसे टैप करके चैनल चुन सकते हैं।" + "value" : "मेरी आईडी खोजें" } }, "pt-BR" : { "stringUnit" : { "state" : "translated", - "value" : "Primeiro, toque e segure na sua Tela de Bloqueio e toque em 'Personalizar'. Depois, você pode adicionar o widget e tocar nele para selecionar o canal." + "value" : "Encontrar Meu ID" } }, "ta" : { "stringUnit" : { "state" : "translated", - "value" : "முதலில், உங்கள் பூட்டு திரையில் தட்டி அழுத்தி 'விருப்பமைக்கவும்' என்று தட்டவும். பின்னர் நீங்கள் விஜெட்டைச் சேர்க்கலாம் மற்றும் சேனலைத் தெரிவு செய்ய அதை தட்டலாம்." + "value" : "எனது ஐடியைக் கண்டுபிடி" } } } }, - "Get SubWidget Pro to use this widget" : { + "First, tap and hold on your Lock Screen and tap 'Customize'. Then you can add the widget and tap it to select the channel." : { "localizations" : { "ar" : { "stringUnit" : { "state" : "translated", - "value" : "احصل على SubWidget Pro لاستخدام هذه الأداة" + "value" : "أولاً، انقر واستمر في الضغط على شاشة القفل الخاصة بك وانقر على 'تخصيص'. بعد ذلك، يمكنك إضافة الواجهة والنقر عليها لتحديد القناة." + } + }, + "de" : { + "stringUnit" : { + "state" : "translated", + "value" : "Zuerst tippen und halten Sie auf Ihrem Sperrbildschirm und tippen Sie auf 'Anpassen'. Dann können Sie das Widget hinzufügen und darauf tippen, um den Kanal auszuwählen." + } + }, + "es" : { + "stringUnit" : { + "state" : "translated", + "value" : "Primero, toca y mantén presionado en tu pantalla de bloqueo y toca 'Personalizar'. Luego puedes agregar el widget y tocarlo para seleccionar el canal." + } + }, + "fr" : { + "stringUnit" : { + "state" : "translated", + "value" : "D'abord, appuyez longuement sur votre écran de verrouillage et appuyez sur 'Personnaliser'. Ensuite, vous pouvez ajouter le widget et appuyer dessus pour sélectionner la chaîne." + } + }, + "hi" : { + "stringUnit" : { + "state" : "translated", + "value" : "सबसे पहले, अपनी लॉक स्क्रीन पर टैप करके रखें और 'कस्टमाइज़' पर टैप करें। फिर आप विजेट जोड़ सकते हैं और उसे टैप करके चैनल चुन सकते हैं।" + } + }, + "pt-BR" : { + "stringUnit" : { + "state" : "translated", + "value" : "Primeiro, toque e segure na sua Tela de Bloqueio e toque em 'Personalizar'. Depois, você pode adicionar o widget e tocar nele para selecionar o canal." + } + }, + "ta" : { + "stringUnit" : { + "state" : "translated", + "value" : "முதலில், உங்கள் பூட்டு திரையில் தட்டி அழுத்தி 'விருப்பமைக்கவும்' என்று தட்டவும். பின்னர் நீங்கள் விஜெட்டைச் சேர்க்கலாம் மற்றும் சேனலைத் தெரிவு செய்ய அதை தட்டலாம்." + } + } + } + }, + "Get Started" : { + "localizations" : { + "ar" : { + "stringUnit" : { + "state" : "translated", + "value" : "ابدأ" + } + }, + "de" : { + "stringUnit" : { + "state" : "translated", + "value" : "Los geht’s" + } + }, + "es" : { + "stringUnit" : { + "state" : "translated", + "value" : "Comenzar" + } + }, + "fr" : { + "stringUnit" : { + "state" : "translated", + "value" : "Commencer" + } + }, + "hi" : { + "stringUnit" : { + "state" : "translated", + "value" : "शुरू करें" + } + }, + "pt-BR" : { + "stringUnit" : { + "state" : "translated", + "value" : "Começar" + } + }, + "ta" : { + "stringUnit" : { + "state" : "translated", + "value" : "தொடங்குங்கள்" + } + } + } + }, + "Get SubWidget Pro to use this widget" : { + "localizations" : { + "ar" : { + "stringUnit" : { + "state" : "translated", + "value" : "احصل على SubWidget Pro لاستخدام هذه الأداة" } }, "de" : { @@ -1779,7 +2055,7 @@ "fr" : { "stringUnit" : { "state" : "translated", - "value" : "Obtenez SubWidget Pro pour utiliser ce widget" + "value" : "Passez à SubWidget Pro pour utiliser ce widget" } }, "hi" : { @@ -1797,7 +2073,7 @@ "ta" : { "stringUnit" : { "state" : "translated", - "value" : "இந்த விட்ஜெட்டை பயன்படுத்த SubWidget Pro பெறுங்கள்" + "value" : "இந்த விட்ஜெட்டை பயன்படுத்த SubWidget Pro ஐப் பெறுங்கள்" } } } @@ -1851,75 +2127,21 @@ "Hold and tap 'Edit Widget'" : { "localizations" : { "ar" : { - "variations" : { - "device" : { - "mac" : { - "stringUnit" : { - "state" : "translated", - "value" : "انقر بزر الماوس الأيمن وحدد 'تحرير الواجهة الفرعية'" - } - }, - "other" : { - "stringUnit" : { - "state" : "translated", - "value" : "استمر في الضغط وانقر على 'تحرير الواجهة'" - } - } - } + "stringUnit" : { + "state" : "translated", + "value" : "اضغط مطولاً ثم اضغط على 'تحرير الأداة'" } }, "de" : { - "variations" : { - "device" : { - "mac" : { - "stringUnit" : { - "state" : "translated", - "value" : "Rechtsklick und 'SubWidget bearbeiten' auswählen" - } - }, - "other" : { - "stringUnit" : { - "state" : "translated", - "value" : "Halten und 'Widget bearbeiten' tippen" - } - } - } - } - }, - "en" : { - "variations" : { - "device" : { - "mac" : { - "stringUnit" : { - "state" : "translated", - "value" : "Right click and select 'Edit SubWidget'" - } - }, - "other" : { - "stringUnit" : { - "state" : "new", - "value" : "Hold and tap 'Edit Widget'" - } - } - } + "stringUnit" : { + "state" : "translated", + "value" : "Gedrückt halten und auf 'Widget bearbeiten' tippen" } }, "es" : { - "variations" : { - "device" : { - "mac" : { - "stringUnit" : { - "state" : "translated", - "value" : "Haz clic derecho y selecciona 'Editar SubWidget'" - } - }, - "other" : { - "stringUnit" : { - "state" : "translated", - "value" : "Mantén presionado y toca 'Editar Widget'" - } - } - } + "stringUnit" : { + "state" : "translated", + "value" : "Mantén presionado y toca 'Editar Widget'" } }, "fr" : { @@ -1934,64 +2156,28 @@ "other" : { "stringUnit" : { "state" : "translated", - "value" : "Maintenez enfoncé et appuyez sur 'Modifier le widget'" + "value" : "Maintenez le doigt puis touchez « Modifier le widget »" } } } } }, "hi" : { - "variations" : { - "device" : { - "mac" : { - "stringUnit" : { - "state" : "translated", - "value" : "राइट क्लिक करें और 'एडिट SubWidget' चुनें" - } - }, - "other" : { - "stringUnit" : { - "state" : "translated", - "value" : "'एडिट विजेट' पर टैप करके रखें" - } - } - } + "stringUnit" : { + "state" : "translated", + "value" : "दबाकर रखें, फिर 'एडिट विजेट' पर टैप करें" } }, "pt-BR" : { - "variations" : { - "device" : { - "mac" : { - "stringUnit" : { - "state" : "translated", - "value" : "Clique com o botão direito e selecione 'Editar SubWidget'" - } - }, - "other" : { - "stringUnit" : { - "state" : "translated", - "value" : "Segure e toque em 'Editar Widget'" - } - } - } + "stringUnit" : { + "state" : "translated", + "value" : "Toque e segure, depois toque em 'Editar Widget'" } }, "ta" : { - "variations" : { - "device" : { - "mac" : { - "stringUnit" : { - "state" : "translated", - "value" : "வலது சொடுக்கி அழுத்தி 'சப்விஜெட்டை திருத்து' என்று தெரிவு செய்க" - } - }, - "other" : { - "stringUnit" : { - "state" : "translated", - "value" : "'விஜெட்டை திருத்து' என்று தட்டி அழுத்தவும்" - } - } - } + "stringUnit" : { + "state" : "translated", + "value" : "அழுத்திப் பிடித்து, பின்னர் 'விட்ஜெட்டைத் திருத்து' என்பதைத் தட்டவும்" } } } @@ -2134,6 +2320,52 @@ } } }, + "How do I delete a channel?" : { + "localizations" : { + "ar" : { + "stringUnit" : { + "state" : "translated", + "value" : "كيف أحذف قناة؟" + } + }, + "de" : { + "stringUnit" : { + "state" : "translated", + "value" : "Wie lösche ich einen Kanal?" + } + }, + "es" : { + "stringUnit" : { + "state" : "translated", + "value" : "¿Cómo elimino un canal?" + } + }, + "fr" : { + "stringUnit" : { + "state" : "translated", + "value" : "Comment supprimer une chaîne ?" + } + }, + "hi" : { + "stringUnit" : { + "state" : "translated", + "value" : "मैं किसी चैनल को कैसे हटाऊँ?" + } + }, + "pt-BR" : { + "stringUnit" : { + "state" : "translated", + "value" : "Como excluo um canal?" + } + }, + "ta" : { + "stringUnit" : { + "state" : "translated", + "value" : "ஒரு சேனலை எப்படி நீக்குவது?" + } + } + } + }, "How many channels can I add?" : { "localizations" : { "ar" : { @@ -2169,1212 +2401,1531 @@ "pt-BR" : { "stringUnit" : { "state" : "translated", - "value" : "Quantos canais posso adicionar?" + "value" : "Quantos canais posso adicionar?" + } + }, + "ta" : { + "stringUnit" : { + "state" : "translated", + "value" : "நான் எத்தனை சேனல்களை சேர்க்கலாம்?" + } + } + } + }, + "How often does the subscriber count update?" : { + "localizations" : { + "ar" : { + "stringUnit" : { + "state" : "translated", + "value" : "كم مرة يتم تحديث عدد المشتركين؟" + } + }, + "de" : { + "stringUnit" : { + "state" : "translated", + "value" : "Wie oft wird die Abonnentenzahl aktualisiert?" + } + }, + "es" : { + "stringUnit" : { + "state" : "translated", + "value" : "¿Con qué frecuencia se actualiza el contador de suscriptores?" + } + }, + "fr" : { + "stringUnit" : { + "state" : "translated", + "value" : "À quelle fréquence le nombre d'abonnés est-il mis à jour ?" + } + }, + "hi" : { + "stringUnit" : { + "state" : "translated", + "value" : "सब्सक्राइबर काउंट कितनी बार अपडेट होता है?" + } + }, + "pt-BR" : { + "stringUnit" : { + "state" : "translated", + "value" : "Com que frequência a contagem de inscritos é atualizada?" + } + }, + "ta" : { + "stringUnit" : { + "state" : "translated", + "value" : "சந்தாதாரர் எண்ணிக்கை எவ்வளவு அடிக்கடி புதுப்பிக்கப்படுகிறது?" + } + } + } + }, + "I can't find my channel." : { + "localizations" : { + "ar" : { + "stringUnit" : { + "state" : "translated", + "value" : "لا أستطيع العثور على قناتي." + } + }, + "de" : { + "stringUnit" : { + "state" : "translated", + "value" : "Ich kann meinen Kanal nicht finden." + } + }, + "es" : { + "stringUnit" : { + "state" : "translated", + "value" : "No puedo encontrar mi canal." + } + }, + "fr" : { + "stringUnit" : { + "state" : "translated", + "value" : "Je ne trouve pas ma chaîne." + } + }, + "hi" : { + "stringUnit" : { + "state" : "translated", + "value" : "मुझे अपना चैनल नहीं मिल रहा है।" + } + }, + "pt-BR" : { + "stringUnit" : { + "state" : "translated", + "value" : "Não consigo encontrar meu canal." + } + }, + "ta" : { + "stringUnit" : { + "state" : "translated", + "value" : "எனது சேனலை கண்டுபிடிக்க முடியவில்லை." + } + } + } + }, + "I have a feature idea I want to request." : { + "localizations" : { + "ar" : { + "stringUnit" : { + "state" : "translated", + "value" : "لدي فكرة ميزة أريد طلبها." + } + }, + "de" : { + "stringUnit" : { + "state" : "translated", + "value" : "Ich habe eine Idee für eine Funktion, die ich anfragen möchte." + } + }, + "es" : { + "stringUnit" : { + "state" : "translated", + "value" : "Tengo una idea de función que quiero solicitar." + } + }, + "fr" : { + "stringUnit" : { + "state" : "translated", + "value" : "J'ai une idée de fonctionnalité que je souhaite demander." + } + }, + "hi" : { + "stringUnit" : { + "state" : "translated", + "value" : "मेरे पास एक फीचर आइडिया है जिसे मैं अनुरोध करना चाहता हूँ।" + } + }, + "pt-BR" : { + "stringUnit" : { + "state" : "translated", + "value" : "Eu tenho uma ideia de recurso que quero solicitar." + } + }, + "ta" : { + "stringUnit" : { + "state" : "translated", + "value" : "நான் கேட்க விரும்பும் ஒரு அம்ச யோசனை உள்ளது." + } + } + } + }, + "Minted" : { + "localizations" : { + "ar" : { + "stringUnit" : { + "state" : "translated", + "value" : "منقوش" + } + }, + "de" : { + "stringUnit" : { + "state" : "translated", + "value" : "Geprägt" + } + }, + "es" : { + "stringUnit" : { + "state" : "translated", + "value" : "Acuñado" + } + }, + "fr" : { + "stringUnit" : { + "state" : "translated", + "value" : "Menthe" + } + }, + "hi" : { + "stringUnit" : { + "state" : "translated", + "value" : "मिंटेड" + } + }, + "pt-BR" : { + "stringUnit" : { + "state" : "translated", + "value" : "Cunhado" + } + }, + "ta" : { + "stringUnit" : { + "state" : "translated", + "value" : "மின்டெட்" + } + } + } + }, + "My question is not listed here." : { + "localizations" : { + "ar" : { + "stringUnit" : { + "state" : "translated", + "value" : "سؤالي غير موجود هنا." + } + }, + "de" : { + "stringUnit" : { + "state" : "translated", + "value" : "Meine Frage ist hier nicht aufgeführt." + } + }, + "es" : { + "stringUnit" : { + "state" : "translated", + "value" : "Mi pregunta no aparece aquí." + } + }, + "fr" : { + "stringUnit" : { + "state" : "translated", + "value" : "Ma question ne figure pas ici." + } + }, + "hi" : { + "stringUnit" : { + "state" : "translated", + "value" : "मेरा सवाल यहां सूचीबद्ध नहीं है।" + } + }, + "pt-BR" : { + "stringUnit" : { + "state" : "translated", + "value" : "Minha pergunta não está listada aqui." + } + }, + "ta" : { + "stringUnit" : { + "state" : "translated", + "value" : "என் கேள்வி இங்கே பட்டியலிடப்படவில்லை." + } + } + } + }, + "Nautical" : { + "localizations" : { + "ar" : { + "stringUnit" : { + "state" : "translated", + "value" : "بحري" + } + }, + "de" : { + "stringUnit" : { + "state" : "translated", + "value" : "Nautisch" + } + }, + "es" : { + "stringUnit" : { + "state" : "translated", + "value" : "Náutico" + } + }, + "fr" : { + "stringUnit" : { + "state" : "translated", + "value" : "Nautique" + } + }, + "hi" : { + "stringUnit" : { + "state" : "translated", + "value" : "नॉटिकल" + } + }, + "pt-BR" : { + "stringUnit" : { + "state" : "translated", + "value" : "Náutico" + } + }, + "ta" : { + "stringUnit" : { + "state" : "translated", + "value" : "கடல்வாழ்" + } + } + } + }, + "Network error. Please try again later." : { + "localizations" : { + "ar" : { + "stringUnit" : { + "state" : "translated", + "value" : "خطأ في الشبكة. يرجى المحاولة مرة أخرى لاحقًا." + } + }, + "de" : { + "stringUnit" : { + "state" : "translated", + "value" : "Netzwerkfehler. Bitte versuchen Sie es später noch einmal." + } + }, + "es" : { + "stringUnit" : { + "state" : "translated", + "value" : "Error de red. Por favor, inténtalo de nuevo más tarde." + } + }, + "fr" : { + "stringUnit" : { + "state" : "translated", + "value" : "Erreur réseau. Veuillez réessayer plus tard." + } + }, + "hi" : { + "stringUnit" : { + "state" : "translated", + "value" : "नेटवर्क त्रुटि। कृपया बाद में पुनः प्रयास करें।" + } + }, + "pt-BR" : { + "stringUnit" : { + "state" : "translated", + "value" : "Erro de rede. Por favor, tente novamente mais tarde." } }, "ta" : { "stringUnit" : { "state" : "translated", - "value" : "நான் எத்தனை சேனல்களை சேர்க்கலாம்?" + "value" : "நெட்வொர்க் பிழை. தயவுசெய்து பின்னர் முயற்சிக்கவும்." } } } }, - "How often does the subscriber count update?" : { + "Network error. Please try again." : { "localizations" : { "ar" : { "stringUnit" : { "state" : "translated", - "value" : "كم مرة يتم تحديث عدد المشتركين؟" + "value" : "خطأ في الشبكة. يرجى المحاولة مرة أخرى." } }, "de" : { "stringUnit" : { "state" : "translated", - "value" : "Wie oft wird die Abonnentenzahl aktualisiert?" + "value" : "Netzwerkfehler. Bitte versuchen Sie es erneut." } }, "es" : { "stringUnit" : { "state" : "translated", - "value" : "¿Con qué frecuencia se actualiza el contador de suscriptores?" + "value" : "Error de red. Por favor, inténtalo de nuevo." } }, "fr" : { "stringUnit" : { "state" : "translated", - "value" : "À quelle fréquence le nombre d'abonnés est-il mis à jour ?" + "value" : "Erreur réseau. Veuillez réessayer." } }, "hi" : { "stringUnit" : { "state" : "translated", - "value" : "सब्सक्राइबर काउंट कितनी बार अपडेट होता है?" + "value" : "नेटवर्क त्रुटि। कृपया पुनः प्रयास करें।" } }, "pt-BR" : { "stringUnit" : { "state" : "translated", - "value" : "Com que frequência a contagem de inscritos é atualizada?" + "value" : "Erro de rede. Por favor, tente novamente." } }, "ta" : { "stringUnit" : { "state" : "translated", - "value" : "சந்தாதாரர் எண்ணிக்கை எவ்வளவு அடிக்கடி புதுப்பிக்கப்படுகிறது?" + "value" : "நெட்வொர்க் பிழை. தயவுசெய்து மீண்டும் முயற்சிக்கவும்." } } } }, - "I can't find my channel." : { + "No channels added yet" : { "localizations" : { "ar" : { "stringUnit" : { "state" : "translated", - "value" : "لا أستطيع العثور على قناتي." + "value" : "لم يتم إضافة قنوات بعد" } }, "de" : { "stringUnit" : { "state" : "translated", - "value" : "Ich kann meinen Kanal nicht finden." + "value" : "Noch keine Kanäle hinzugefügt" } }, "es" : { "stringUnit" : { "state" : "translated", - "value" : "No puedo encontrar mi canal." + "value" : "Aún no se han agregado canales." } }, "fr" : { "stringUnit" : { "state" : "translated", - "value" : "Je ne trouve pas ma chaîne." + "value" : "Aucune chaîne ajoutée pour le moment" } }, "hi" : { "stringUnit" : { "state" : "translated", - "value" : "मुझे अपना चैनल नहीं मिल रहा है।" + "value" : "अभी तक कोई चैनल नहीं जोड़ा गया है" } }, "pt-BR" : { "stringUnit" : { "state" : "translated", - "value" : "Não consigo encontrar meu canal." + "value" : "Nenhum canal adicionado ainda" } }, "ta" : { "stringUnit" : { "state" : "translated", - "value" : "எனது சேனலை கண்டுபிடிக்க முடியவில்லை." + "value" : "இன்னும் எந்த சேனல்களும் சேர்க்கப்படவில்லை" } } } }, - "I have a feature idea I want to request." : { + "Number" : { "localizations" : { "ar" : { "stringUnit" : { "state" : "translated", - "value" : "لدي فكرة ميزة أريد طلبها." + "value" : "رقم" } }, "de" : { "stringUnit" : { "state" : "translated", - "value" : "Ich habe eine Idee für eine Funktion, die ich anfragen möchte." + "value" : "Nummer" } }, "es" : { "stringUnit" : { "state" : "translated", - "value" : "Tengo una idea de función que quiero solicitar." + "value" : "Número" } }, "fr" : { "stringUnit" : { "state" : "translated", - "value" : "J'ai une idée de fonctionnalité que je souhaite demander." + "value" : "Numéro" } }, "hi" : { "stringUnit" : { "state" : "translated", - "value" : "मेरे पास एक फीचर आइडिया है जिसे मैं अनुरोध करना चाहता हूँ।" + "value" : "संख्या" } }, "pt-BR" : { "stringUnit" : { "state" : "translated", - "value" : "Eu tenho uma ideia de recurso que quero solicitar." + "value" : "Número" } }, "ta" : { "stringUnit" : { "state" : "translated", - "value" : "நான் கேட்க விரும்பும் ஒரு அம்ச யோசனை உள்ளது." + "value" : "எண்" } } } }, - "Minted" : { + "OK" : { "localizations" : { "ar" : { "stringUnit" : { "state" : "translated", - "value" : "منقوش" + "value" : "حسنًا" } }, "de" : { "stringUnit" : { "state" : "translated", - "value" : "Geprägt" + "value" : "OK" } }, "es" : { "stringUnit" : { "state" : "translated", - "value" : "Acuñado" + "value" : "OK" } }, "fr" : { "stringUnit" : { "state" : "translated", - "value" : "Menthe" + "value" : "OK" } }, "hi" : { "stringUnit" : { "state" : "translated", - "value" : "मिंटेड" + "value" : "ठीक है" } }, "pt-BR" : { "stringUnit" : { "state" : "translated", - "value" : "Cunhado" + "value" : "OK" } }, "ta" : { "stringUnit" : { "state" : "translated", - "value" : "மின்டெட்" + "value" : "சரி" } } } }, - "Nautical" : { + "Onyx" : { "localizations" : { "ar" : { "stringUnit" : { "state" : "translated", - "value" : "بحري" + "value" : "العقيق" } }, "de" : { "stringUnit" : { "state" : "translated", - "value" : "Nautisch" + "value" : "Onyx" } }, "es" : { "stringUnit" : { "state" : "translated", - "value" : "Náutico" + "value" : "Ónix" } }, "fr" : { "stringUnit" : { "state" : "translated", - "value" : "Nautique" + "value" : "Onyx" } }, "hi" : { "stringUnit" : { "state" : "translated", - "value" : "नॉटिकल" + "value" : "ओनिक्स" } }, "pt-BR" : { "stringUnit" : { "state" : "translated", - "value" : "Náutico" + "value" : "Ônix" } }, "ta" : { "stringUnit" : { "state" : "translated", - "value" : "கடல்வாழ்" + "value" : "ஆனிக்ஸ்" } } } }, - "Network error. Please try again later." : { + "Orchid" : { "localizations" : { "ar" : { "stringUnit" : { "state" : "translated", - "value" : "خطأ في الشبكة. يرجى المحاولة مرة أخرى لاحقًا." + "value" : "الأوركيد" } }, "de" : { "stringUnit" : { "state" : "translated", - "value" : "Netzwerkfehler. Bitte versuchen Sie es später noch einmal." + "value" : "Orchidee" } }, "es" : { "stringUnit" : { "state" : "translated", - "value" : "Error de red. Por favor, inténtalo de nuevo más tarde." + "value" : "Orquídea" } }, "fr" : { "stringUnit" : { "state" : "translated", - "value" : "Erreur réseau. Veuillez réessayer plus tard." + "value" : "Orchidée" } }, "hi" : { "stringUnit" : { "state" : "translated", - "value" : "नेटवर्क त्रुटि। कृपया बाद में पुनः प्रयास करें।" + "value" : "ऑर्किड" } }, "pt-BR" : { "stringUnit" : { "state" : "translated", - "value" : "Erro de rede. Por favor, tente novamente mais tarde." + "value" : "Orquídea" } }, "ta" : { "stringUnit" : { "state" : "translated", - "value" : "நெட்வொர்க் பிழை. தயவுசெய்து பின்னர் முயற்சிக்கவும்." + "value" : "ஆர்க்கிட்" } } } }, - "Network error. Please try again." : { + "Palettes" : { "localizations" : { "ar" : { "stringUnit" : { "state" : "translated", - "value" : "خطأ في الشبكة. يرجى المحاولة مرة أخرى." + "value" : "لوحات" } }, "de" : { "stringUnit" : { "state" : "translated", - "value" : "Netzwerkfehler. Bitte versuchen Sie es erneut." + "value" : "Paletten" } }, "es" : { "stringUnit" : { "state" : "translated", - "value" : "Error de red. Por favor, inténtalo de nuevo." + "value" : "Paletas" } }, "fr" : { "stringUnit" : { "state" : "translated", - "value" : "Erreur réseau. Veuillez réessayer." + "value" : "Palettes" } }, "hi" : { "stringUnit" : { "state" : "translated", - "value" : "नेटवर्क त्रुटि। कृपया पुनः प्रयास करें।" + "value" : "पैलेट्स" } }, "pt-BR" : { "stringUnit" : { "state" : "translated", - "value" : "Erro de rede. Por favor, tente novamente." + "value" : "Paletas" } }, "ta" : { "stringUnit" : { "state" : "translated", - "value" : "நெட்வொர்க் பிழை. தயவுசெய்து மீண்டும் முயற்சிக்கவும்." + "value" : "பேலட்டுகள்" } } } }, - "No channels added yet" : { + "Patriot" : { "localizations" : { "ar" : { "stringUnit" : { "state" : "translated", - "value" : "لم يتم إضافة قنوات بعد" + "value" : "وطني" } }, "de" : { "stringUnit" : { "state" : "translated", - "value" : "Noch keine Kanäle hinzugefügt" + "value" : "Patriot" } }, "es" : { "stringUnit" : { "state" : "translated", - "value" : "Aún no se han agregado canales." + "value" : "Patriota" } }, "fr" : { "stringUnit" : { "state" : "translated", - "value" : "Aucune chaîne ajoutée pour le moment" + "value" : "Patriote" } }, "hi" : { "stringUnit" : { "state" : "translated", - "value" : "अभी तक कोई चैनल नहीं जोड़ा गया है" + "value" : "पैट्रियट" } }, "pt-BR" : { "stringUnit" : { "state" : "translated", - "value" : "Nenhum canal adicionado ainda" + "value" : "Patriota" } }, "ta" : { "stringUnit" : { "state" : "translated", - "value" : "இன்னும் எந்த சேனல்களும் சேர்க்கப்படவில்லை" + "value" : "தேசபக்தன்" } } } }, - "Number" : { + "Place the widget" : { "localizations" : { "ar" : { "stringUnit" : { "state" : "translated", - "value" : "رقم" + "value" : "ضع الأداة" } }, "de" : { "stringUnit" : { "state" : "translated", - "value" : "Nummer" + "value" : "Platzieren Sie das Widget" } }, "es" : { "stringUnit" : { "state" : "translated", - "value" : "Número" + "value" : "Coloca el widget" } }, "fr" : { "stringUnit" : { "state" : "translated", - "value" : "Numéro" + "value" : "Placez le widget" } }, "hi" : { "stringUnit" : { "state" : "translated", - "value" : "संख्या" + "value" : "विजेट लगाएं" } }, "pt-BR" : { "stringUnit" : { "state" : "translated", - "value" : "Número" + "value" : "Coloque o widget" } }, "ta" : { "stringUnit" : { "state" : "translated", - "value" : "எண்" + "value" : "விட்ஜெட்டை இடவும்" } } } }, - "OK" : { + "Please contact me from the settings screen and I will answer any question you have. I typically respond within a few hours!." : { "localizations" : { "ar" : { "stringUnit" : { "state" : "translated", - "value" : "حسنًا" + "value" : "يرجى التواصل معي من شاشة الإعدادات وسأجيب عن أي سؤال لديك. عادةً ما أرد خلال بضع ساعات!" } }, "de" : { "stringUnit" : { "state" : "translated", - "value" : "OK" + "value" : "Kontaktieren Sie mich bitte über den Einstellungsbildschirm, dann beantworte ich gerne jede Frage. Ich antworte in der Regel innerhalb weniger Stunden!" } }, "es" : { "stringUnit" : { "state" : "translated", - "value" : "OK" + "value" : "Contáctame desde la pantalla de ajustes y responderé cualquier pregunta que tengas. Normalmente respondo en unas pocas horas." } }, "fr" : { "stringUnit" : { "state" : "translated", - "value" : "OK" + "value" : "Contactez-moi depuis l’écran des réglages et je répondrai à toutes vos questions. Je réponds généralement en quelques heures !" } }, "hi" : { "stringUnit" : { "state" : "translated", - "value" : "ठीक है" + "value" : "सेटिंग्स स्क्रीन से मुझसे संपर्क करें, मैं आपके किसी भी सवाल का जवाब दूंगा। मैं आमतौर पर कुछ घंटों के भीतर जवाब देता हूं!" } }, "pt-BR" : { "stringUnit" : { "state" : "translated", - "value" : "OK" + "value" : "Entre em contato comigo pela tela de Ajustes e eu responderei qualquer pergunta que você tiver. Normalmente respondo em poucas horas!" } }, "ta" : { "stringUnit" : { "state" : "translated", - "value" : "சரி" + "value" : "அமைப்புகள் திரையிலிருந்து என்னை தொடர்பு கொள்ளுங்கள்; உங்களிடம் உள்ள எந்தக் கேள்விக்கும் நான் பதில் அளிப்பேன். பொதுவாக சில மணி நேரங்களுக்குள் பதிலளிக்கிறேன்!" } } } }, - "Onyx" : { + "Please install a mail app to continue" : { "localizations" : { "ar" : { "stringUnit" : { "state" : "translated", - "value" : "العقيق" + "value" : "يرجى تثبيت تطبيق بريد للمتابعة" } }, "de" : { "stringUnit" : { "state" : "translated", - "value" : "Onyx" + "value" : "Bitte installieren Sie eine Mail-App, um fortzufahren" } }, "es" : { "stringUnit" : { "state" : "translated", - "value" : "Ónix" + "value" : "Por favor, instala una aplicación de correo para continuar" } }, "fr" : { "stringUnit" : { "state" : "translated", - "value" : "Onyx" + "value" : "Veuillez installer une application de messagerie pour continuer" } }, "hi" : { "stringUnit" : { "state" : "translated", - "value" : "ओनिक्स" + "value" : "कृपया जारी रखने के लिए एक मेल ऐप स्थापित करें" } }, "pt-BR" : { "stringUnit" : { "state" : "translated", - "value" : "Ônix" + "value" : "Por favor, instale um aplicativo de e-mail para continuar" } }, "ta" : { "stringUnit" : { "state" : "translated", - "value" : "ஆனிக்ஸ்" + "value" : "தொடர ஒரு மின்னஞ்சல் செயலியை நிறுவவும்" } } } }, - "Orchid" : { + "Please remove the widget from your homescreen and add it back." : { "localizations" : { "ar" : { "stringUnit" : { "state" : "translated", - "value" : "الأوركيد" + "value" : "يرجى إزالة الواجهة من الشاشة الرئيسية وإضافتها مرة أخرى." } }, "de" : { "stringUnit" : { "state" : "translated", - "value" : "Orchidee" + "value" : "Bitte entfernen Sie das Widget von Ihrem Startbildschirm und fügen Sie es wieder hinzu." } }, "es" : { "stringUnit" : { "state" : "translated", - "value" : "Orquídea" + "value" : "Por favor, quita el widget de tu pantalla de inicio y vuelve a agregarlo." } }, "fr" : { "stringUnit" : { "state" : "translated", - "value" : "Orchidée" + "value" : "Veuillez retirer le widget de votre écran d'accueil et l'ajouter à nouveau." } }, "hi" : { "stringUnit" : { "state" : "translated", - "value" : "ऑर्किड" + "value" : "कृपया अपनी होमस्क्रीन से विजेट को हटा दें और उसे फिर से जोड़ें।" } }, "pt-BR" : { "stringUnit" : { "state" : "translated", - "value" : "Orquídea" + "value" : "Por favor, remova o widget da sua tela inicial e adicione-o novamente." } }, "ta" : { "stringUnit" : { "state" : "translated", - "value" : "ஆர்க்கிட்" + "value" : "தயவுசெய்து உங்கள் முகப்புத் திரையிலிருந்து விஜெட்டை நீக்கி மீண்டும் சேர்க்கவும்." } } } }, - "Palettes" : { + "Preview" : { "localizations" : { "ar" : { "stringUnit" : { "state" : "translated", - "value" : "لوحات" + "value" : "معاينة" } }, "de" : { "stringUnit" : { "state" : "translated", - "value" : "Paletten" + "value" : "Vorschau" } }, "es" : { "stringUnit" : { "state" : "translated", - "value" : "Paletas" + "value" : "Vista previa" } }, "fr" : { "stringUnit" : { "state" : "translated", - "value" : "Palettes" + "value" : "Aperçu" } }, "hi" : { "stringUnit" : { "state" : "translated", - "value" : "पैलेट्स" + "value" : "पूर्वावलोकन" } }, "pt-BR" : { "stringUnit" : { "state" : "translated", - "value" : "Paletas" + "value" : "Pré-visualização" } }, "ta" : { "stringUnit" : { "state" : "translated", - "value" : "பேலட்டுகள்" + "value" : "முன்னோட்டம்" } } } }, - "Patriot" : { + "Privacy Policy" : { "localizations" : { "ar" : { "stringUnit" : { "state" : "translated", - "value" : "وطني" + "value" : "سياسة الخصوصية" } }, "de" : { "stringUnit" : { "state" : "translated", - "value" : "Patriot" + "value" : "Datenschutzrichtlinie" } }, "es" : { "stringUnit" : { "state" : "translated", - "value" : "Patriota" + "value" : "Política de privacidad" } }, "fr" : { "stringUnit" : { "state" : "translated", - "value" : "Patriote" + "value" : "Politique de confidentialité" } }, "hi" : { "stringUnit" : { "state" : "translated", - "value" : "पैट्रियट" + "value" : "गोपनीयता नीति" } }, "pt-BR" : { "stringUnit" : { "state" : "translated", - "value" : "Patriota" + "value" : "Política de Privacidade" } }, "ta" : { "stringUnit" : { "state" : "translated", - "value" : "தேசபக்தன்" + "value" : "தனியுரிமை கொள்கை" } } } }, - "Please install a mail app to continue" : { + "Pro" : { "localizations" : { "ar" : { "stringUnit" : { "state" : "translated", - "value" : "يرجى تثبيت تطبيق بريد للمتابعة" + "value" : "Pro" } }, "de" : { "stringUnit" : { "state" : "translated", - "value" : "Bitte installieren Sie eine Mail-App, um fortzufahren" + "value" : "Pro" } }, "es" : { "stringUnit" : { "state" : "translated", - "value" : "Por favor, instala una aplicación de correo para continuar" + "value" : "Pro" } }, "fr" : { "stringUnit" : { "state" : "translated", - "value" : "Veuillez installer une application de messagerie pour continuer" + "value" : "Pro" } }, "hi" : { "stringUnit" : { "state" : "translated", - "value" : "कृपया जारी रखने के लिए एक मेल ऐप स्थापित करें" + "value" : "Pro" } }, "pt-BR" : { "stringUnit" : { "state" : "translated", - "value" : "Por favor, instale um aplicativo de e-mail para continuar" + "value" : "Pro" } }, "ta" : { "stringUnit" : { "state" : "translated", - "value" : "தொடர ஒரு மின்னஞ்சல் செயலியை நிறுவவும்" + "value" : "Pro" } } } }, - "Please remove the widget from your homescreen and add it back." : { + "PRO" : { "localizations" : { "ar" : { "stringUnit" : { "state" : "translated", - "value" : "يرجى إزالة الواجهة من الشاشة الرئيسية وإضافتها مرة أخرى." + "value" : "PRO" } }, "de" : { "stringUnit" : { "state" : "translated", - "value" : "Bitte entfernen Sie das Widget von Ihrem Startbildschirm und fügen Sie es wieder hinzu." + "value" : "PRO" } }, "es" : { "stringUnit" : { "state" : "translated", - "value" : "Por favor, quita el widget de tu pantalla de inicio y vuelve a agregarlo." + "value" : "PRO" } }, "fr" : { "stringUnit" : { "state" : "translated", - "value" : "Veuillez retirer le widget de votre écran d'accueil et l'ajouter à nouveau." + "value" : "PRO" } }, "hi" : { "stringUnit" : { "state" : "translated", - "value" : "कृपया अपनी होमस्क्रीन से विजेट को हटा दें और उसे फिर से जोड़ें।" + "value" : "PRO" } }, "pt-BR" : { "stringUnit" : { "state" : "translated", - "value" : "Por favor, remova o widget da sua tela inicial e adicione-o novamente." + "value" : "PRO" } }, "ta" : { "stringUnit" : { "state" : "translated", - "value" : "தயவுசெய்து உங்கள் முகப்புத் திரையிலிருந்து விஜெட்டை நீக்கி மீண்டும் சேர்க்கவும்." + "value" : "PRO" } } } }, - "Preview" : { + "Rate" : { "localizations" : { "ar" : { "stringUnit" : { "state" : "translated", - "value" : "معاينة" + "value" : "معدل" } }, "de" : { "stringUnit" : { "state" : "translated", - "value" : "Vorschau" + "value" : "Bewerten" } }, "es" : { "stringUnit" : { "state" : "translated", - "value" : "Vista previa" + "value" : "Calificar" } }, "fr" : { "stringUnit" : { "state" : "translated", - "value" : "Aperçu" + "value" : "Évaluer" } }, "hi" : { "stringUnit" : { "state" : "translated", - "value" : "पूर्वावलोकन" + "value" : "मूल्यांकन करें" } }, "pt-BR" : { "stringUnit" : { "state" : "translated", - "value" : "Pré-visualização" + "value" : "Classificar" } }, "ta" : { "stringUnit" : { "state" : "translated", - "value" : "முன்னோட்டம்" + "value" : "விகிதம்" } } } }, - "Privacy Policy" : { + "Reset" : { "localizations" : { "ar" : { "stringUnit" : { "state" : "translated", - "value" : "سياسة الخصوصية" + "value" : "إعادة ضبط" } }, "de" : { "stringUnit" : { "state" : "translated", - "value" : "Datenschutzrichtlinie" + "value" : "Zurücksetzen" } }, "es" : { "stringUnit" : { "state" : "translated", - "value" : "Política de privacidad" + "value" : "Restablecer" } }, "fr" : { "stringUnit" : { "state" : "translated", - "value" : "Politique de confidentialité" + "value" : "Réinitialiser" } }, "hi" : { "stringUnit" : { "state" : "translated", - "value" : "गोपनीयता नीति" + "value" : "रीसेट" } }, "pt-BR" : { "stringUnit" : { "state" : "translated", - "value" : "Política de Privacidade" + "value" : "Resetar" } }, "ta" : { "stringUnit" : { "state" : "translated", - "value" : "தனியுரிமை கொள்கை" + "value" : "மீட்டமை" } } } }, - "Pro" : { - - }, - "PRO" : { + "Restores the widget's colors to the defaults - White in light mode and black in dark mode." : { "localizations" : { "ar" : { "stringUnit" : { "state" : "translated", - "value" : "PRO" + "value" : "يعيد ألوان الأداة إلى الإعدادات الافتراضية: الأبيض في الوضع الفاتح والأسود في الوضع الداكن." } }, "de" : { "stringUnit" : { "state" : "translated", - "value" : "PRO" + "value" : "Setzt die Widget-Farben auf die Standardwerte zurück: Weiß im hellen Modus und Schwarz im dunklen Modus." } }, "es" : { "stringUnit" : { "state" : "translated", - "value" : "PRO" + "value" : "Restablece los colores del widget a los predeterminados: blanco en modo claro y negro en modo oscuro." } }, "fr" : { "stringUnit" : { "state" : "translated", - "value" : "PRO" + "value" : "Restaure les couleurs du widget par défaut : blanc en mode clair et noir en mode sombre." } }, "hi" : { "stringUnit" : { "state" : "translated", - "value" : "PRO" + "value" : "विजेट के रंग डिफ़ॉल्ट पर वापस कर देता है - लाइट मोड में सफेद और डार्क मोड में काला।" } }, "pt-BR" : { "stringUnit" : { "state" : "translated", - "value" : "PRO" + "value" : "Restaura as cores do widget para o padrão: branco no modo claro e preto no modo escuro." } }, "ta" : { "stringUnit" : { "state" : "translated", - "value" : "PRO" + "value" : "விட்ஜெட்டின் நிறங்களை இயல்புநிலைக்கு மீட்டமைக்கும் - ஒளி முறையில் வெள்ளை, இருள் முறையில் கருப்பு." } } } }, - "Rate" : { + "Rosewood" : { "localizations" : { "ar" : { "stringUnit" : { "state" : "translated", - "value" : "معدل" + "value" : "خشب الورد" } }, "de" : { "stringUnit" : { "state" : "translated", - "value" : "Bewerten" + "value" : "Rosenholz" } }, "es" : { "stringUnit" : { "state" : "translated", - "value" : "Calificar" + "value" : "Palo de rosa" } }, "fr" : { "stringUnit" : { "state" : "translated", - "value" : "Évaluer" + "value" : "Bois de rose" } }, "hi" : { "stringUnit" : { "state" : "translated", - "value" : "मूल्यांकन करें" + "value" : "रोज़वुड" } }, "pt-BR" : { "stringUnit" : { "state" : "translated", - "value" : "Classificar" + "value" : "Pau-rosa" } }, "ta" : { "stringUnit" : { "state" : "translated", - "value" : "விகிதம்" + "value" : "ரோஸ்வுட்" } } } }, - "Reset" : { + "Ruby" : { "localizations" : { "ar" : { "stringUnit" : { "state" : "translated", - "value" : "إعادة ضبط" + "value" : "الياقوت" } }, "de" : { "stringUnit" : { "state" : "translated", - "value" : "Zurücksetzen" + "value" : "Rubin" } }, "es" : { "stringUnit" : { "state" : "translated", - "value" : "Restablecer" + "value" : "Rubí" } }, "fr" : { "stringUnit" : { "state" : "translated", - "value" : "Réinitialiser" + "value" : "Rubis" } }, "hi" : { "stringUnit" : { "state" : "translated", - "value" : "रीसेट" + "value" : "रूबी" } }, "pt-BR" : { "stringUnit" : { "state" : "translated", - "value" : "Resetar" + "value" : "Rubi" } }, "ta" : { "stringUnit" : { "state" : "translated", - "value" : "மீட்டமை" + "value" : "ரூபி" } } } }, - "Restores the widget's colors to the defaults." : { + "Sandstone" : { "localizations" : { "ar" : { "stringUnit" : { "state" : "translated", - "value" : "استعادة ألوان الواجهة إلى الإعدادات الافتراضية." + "value" : "الحجر الرملي" } }, "de" : { "stringUnit" : { "state" : "translated", - "value" : "Stellt die Standardfarben des Widgets wieder her." + "value" : "Sandstein" } }, "es" : { "stringUnit" : { "state" : "translated", - "value" : "Restablece los colores del widget a los predeterminados." + "value" : "Arenisca" } }, "fr" : { "stringUnit" : { "state" : "translated", - "value" : "Restaure les couleurs du widget aux valeurs par défaut." + "value" : "Grès" } }, "hi" : { "stringUnit" : { "state" : "translated", - "value" : "विजेट के रंगों को डिफ़ॉल्ट में पुनर्स्थापित करता है।" + "value" : "रेत-पत्थर" } }, "pt-BR" : { "stringUnit" : { "state" : "translated", - "value" : "Restaura as cores do widget para os padrões." + "value" : "Arenito" } }, "ta" : { "stringUnit" : { "state" : "translated", - "value" : "விஜெட்டின் நிறங்களை இயல்புநிலைக்கு மீட்டமைக்கிறது." + "value" : "மணற்கல்" } } } }, - "Rosewood" : { + "Search a channel. Add the widget." : { "localizations" : { "ar" : { "stringUnit" : { "state" : "translated", - "value" : "خشب الورد" + "value" : "ابحث عن قناة. ثم أضف الأداة." } }, "de" : { "stringUnit" : { "state" : "translated", - "value" : "Rosenholz" + "value" : "Suchen Sie einen Kanal. Fügen Sie das Widget hinzu." } }, "es" : { "stringUnit" : { "state" : "translated", - "value" : "Palo de rosa" + "value" : "Busca un canal. Agrega el widget." } }, "fr" : { "stringUnit" : { "state" : "translated", - "value" : "Bois de rose" + "value" : "Recherchez une chaîne. Ajoutez le widget." } }, "hi" : { "stringUnit" : { "state" : "translated", - "value" : "रोज़वुड" + "value" : "एक चैनल खोजें। विजेट जोड़ें।" } }, "pt-BR" : { "stringUnit" : { "state" : "translated", - "value" : "Pau-rosa" + "value" : "Pesquise um canal. Adicione o widget." } }, "ta" : { "stringUnit" : { "state" : "translated", - "value" : "ரோஸ்வுட்" + "value" : "ஒரு சேனலைத் தேடுங்கள். விட்ஜெட்டைச் சேர்க்கவும்." } } } }, - "Ruby" : { + "Search by name, @handle, or channel ID. See a preview instantly." : { "localizations" : { "ar" : { "stringUnit" : { "state" : "translated", - "value" : "الياقوت" + "value" : "ابحث بالاسم أو @المعرّف أو رقم القناة. شاهد المعاينة فورًا." } }, "de" : { "stringUnit" : { "state" : "translated", - "value" : "Rubin" + "value" : "Suchen Sie nach Name, @Handle oder Kanal-ID. Sehen Sie sofort eine Vorschau." } }, "es" : { "stringUnit" : { "state" : "translated", - "value" : "Rubí" + "value" : "Busca por nombre, @usuario o ID del canal. Mira la vista previa al instante." } }, "fr" : { "stringUnit" : { "state" : "translated", - "value" : "Rubis" + "value" : "Recherchez par nom, @identifiant ou ID de chaîne. Voyez l’aperçu instantanément." } }, "hi" : { "stringUnit" : { "state" : "translated", - "value" : "रूबी" + "value" : "नाम, @हैंडल या चैनल आईडी से खोजें। तुरंत प्रीव्यू देखें।" } }, "pt-BR" : { "stringUnit" : { "state" : "translated", - "value" : "Rubi" + "value" : "Pesquise por nome, @perfil ou ID do canal. Veja a prévia na hora." } }, "ta" : { "stringUnit" : { "state" : "translated", - "value" : "ரூபி" + "value" : "பெயர், @handle அல்லது சேனல் ஐடி மூலம் தேடுங்கள். முன்னோட்டத்தை உடனே பாருங்கள்." } } } }, - "Sandstone" : { + "Search for your channel" : { "localizations" : { "ar" : { "stringUnit" : { "state" : "translated", - "value" : "الحجر الرملي" + "value" : "ابحث عن قناتك" } }, "de" : { "stringUnit" : { "state" : "translated", - "value" : "Sandstein" + "value" : "Suchen Sie nach Ihrem Kanal" } }, "es" : { "stringUnit" : { "state" : "translated", - "value" : "Arenisca" + "value" : "Busca tu canal" } }, "fr" : { "stringUnit" : { "state" : "translated", - "value" : "Grès" + "value" : "Recherchez votre chaîne" } }, "hi" : { "stringUnit" : { "state" : "translated", - "value" : "रेत-पत्थर" + "value" : "अपना चैनल खोजें" } }, "pt-BR" : { "stringUnit" : { "state" : "translated", - "value" : "Arenito" + "value" : "Pesquise seu canal" } }, "ta" : { "stringUnit" : { "state" : "translated", - "value" : "மணற்கல்" + "value" : "உங்கள் சேனலைத் தேடுங்கள்" } } } }, - "Search for your channel" : { + "See subscriber and view counts on your home screen or lock screen." : { "localizations" : { "ar" : { "stringUnit" : { "state" : "translated", - "value" : "ابحث عن قناتك" + "value" : "شاهد عدد المشتركين والمشاهدات على الشاشة الرئيسية أو شاشة القفل." } }, "de" : { "stringUnit" : { "state" : "translated", - "value" : "Suchen Sie nach Ihrem Kanal" + "value" : "Sehen Sie Abonnenten- und Aufrufzahlen auf Ihrem Home-Bildschirm oder Sperrbildschirm." } }, "es" : { "stringUnit" : { "state" : "translated", - "value" : "Busca tu canal" + "value" : "Ve los suscriptores y las vistas en tu pantalla de inicio o pantalla bloqueada." } }, "fr" : { "stringUnit" : { "state" : "translated", - "value" : "Recherchez votre chaîne" + "value" : "Consultez vos abonnés et vos vues sur l’écran d’accueil ou l’écran verrouillé." } }, "hi" : { "stringUnit" : { "state" : "translated", - "value" : "अपना चैनल खोजें" + "value" : "अपने होम स्क्रीन या लॉक स्क्रीन पर सब्सक्राइबर और व्यू काउंट देखें।" } }, "pt-BR" : { "stringUnit" : { "state" : "translated", - "value" : "Pesquise seu canal" + "value" : "Veja inscritos e visualizações na tela inicial ou na tela bloqueada." } }, "ta" : { "stringUnit" : { "state" : "translated", - "value" : "உங்கள் சேனலைத் தேடுங்கள்" + "value" : "உங்கள் ஹோம் ஸ்கிரீன் அல்லது லாக் ஸ்கிரீனில் சந்தாதாரர் மற்றும் காட்சி எண்ணிக்கைகளைப் பாருங்கள்." } } } @@ -3414,7 +3965,7 @@ "pt-BR" : { "stringUnit" : { "state" : "translated", - "value" : "Selecione Seu Canal" + "value" : "Selecione seu canal" } }, "ta" : { @@ -3563,6 +4114,52 @@ } } }, + "START FREE" : { + "localizations" : { + "ar" : { + "stringUnit" : { + "state" : "translated", + "value" : "ابدأ مجانًا" + } + }, + "de" : { + "stringUnit" : { + "state" : "translated", + "value" : "KOSTENLOS STARTEN" + } + }, + "es" : { + "stringUnit" : { + "state" : "translated", + "value" : "EMPIEZA GRATIS" + } + }, + "fr" : { + "stringUnit" : { + "state" : "translated", + "value" : "COMMENCEZ GRATUITEMENT" + } + }, + "hi" : { + "stringUnit" : { + "state" : "translated", + "value" : "मुफ्त में शुरू करें" + } + }, + "pt-BR" : { + "stringUnit" : { + "state" : "translated", + "value" : "COMECE GRÁTIS" + } + }, + "ta" : { + "stringUnit" : { + "state" : "translated", + "value" : "இலவசமாக தொடங்குங்கள்" + } + } + } + }, "Submit" : { "localizations" : { "ar" : { @@ -3839,12 +4436,58 @@ } } }, + "Swipe right on the channel to delete it" : { + "localizations" : { + "ar" : { + "stringUnit" : { + "state" : "translated", + "value" : "اسحب إلى اليمين على القناة لحذفها" + } + }, + "de" : { + "stringUnit" : { + "state" : "translated", + "value" : "Wischen Sie auf dem Kanal nach rechts, um ihn zu löschen" + } + }, + "es" : { + "stringUnit" : { + "state" : "translated", + "value" : "Desliza hacia la derecha sobre el canal para eliminarlo" + } + }, + "fr" : { + "stringUnit" : { + "state" : "translated", + "value" : "Faites glisser la chaîne vers la droite pour la supprimer" + } + }, + "hi" : { + "stringUnit" : { + "state" : "translated", + "value" : "चैनल को हटाने के लिए उस पर दाईं ओर स्वाइप करें" + } + }, + "pt-BR" : { + "stringUnit" : { + "state" : "translated", + "value" : "Deslize o canal para a direita para excluí-lo" + } + }, + "ta" : { + "stringUnit" : { + "state" : "translated", + "value" : "சேனலை நீக்க அதில் வலமாக ஸ்வைப் செய்யவும்" + } + } + } + }, "Tap and hold anywhere on your homescreen and tap the plus button in the top left. Look for SubWidget and add it to your homescreen. Finally, tap and hold on the widget and select your channel." : { "localizations" : { "ar" : { "stringUnit" : { "state" : "translated", - "value" : "انقر واستمر في الضغط في أي مكان على شاشتك الرئيسية وانقر على زر الإضافة في الجزء العلوي الأيسر. ابحث عن الواجهة الفرعية وأضفها إلى شاشتك الرئيسية. أخيرًا، انقر واستمر في الضغط على الواجهة وحدد قناتك." + "value" : "انقر واستمر في الضغط في أي مكان على شاشتك الرئيسية وانقر على زر الإضافة في الجزء العلوي الأيسر. ابحث عن SubWidget وأضفه إلى الشاشة الرئيسية. أخيرًا، انقر واستمر في الضغط على الواجهة وحدد قناتك." } }, "de" : { @@ -4046,7 +4689,7 @@ "fr" : { "stringUnit" : { "state" : "translated", - "value" : "Total abonnés" + "value" : "Total des abonnés" } }, "hi" : { @@ -4350,13 +4993,13 @@ "ar" : { "stringUnit" : { "state" : "translated", - "value" : "فتح" + "value" : "افتح" } }, "de" : { "stringUnit" : { "state" : "translated", - "value" : "Freischalten" + "value" : "Entsperren" } }, "es" : { @@ -4386,7 +5029,7 @@ "ta" : { "stringUnit" : { "state" : "translated", - "value" : "திறக்கவும்" + "value" : "திற" } } } @@ -4667,6 +5310,52 @@ } } }, + "With SubWidget Pro, you can add up to 10 channels. If you would like to add more, please contact me using the link in Settings and I will increase the limit." : { + "localizations" : { + "ar" : { + "stringUnit" : { + "state" : "translated", + "value" : "مع SubWidget Pro، يمكنك إضافة ما يصل إلى 10 قنوات. إذا كنت ترغب في إضافة المزيد، يرجى التواصل معي باستخدام الرابط في الإعدادات وسأزيد الحد." + } + }, + "de" : { + "stringUnit" : { + "state" : "translated", + "value" : "Mit SubWidget Pro können Sie bis zu 10 Kanäle hinzufügen. Wenn Sie mehr hinzufügen möchten, kontaktieren Sie mich bitte über den Link in den Einstellungen, dann erhöhe ich das Limit." + } + }, + "es" : { + "stringUnit" : { + "state" : "translated", + "value" : "Con SubWidget Pro, puedes agregar hasta 10 canales. Si quieres agregar más, contáctame usando el enlace en Ajustes y aumentaré el límite." + } + }, + "fr" : { + "stringUnit" : { + "state" : "translated", + "value" : "Avec SubWidget Pro, vous pouvez ajouter jusqu’à 10 chaînes. Si vous souhaitez en ajouter plus, contactez-moi via le lien dans Réglages et j’augmenterai la limite." + } + }, + "hi" : { + "stringUnit" : { + "state" : "translated", + "value" : "SubWidget Pro के साथ, आप 10 तक चैनल जोड़ सकते हैं। यदि आप इससे अधिक जोड़ना चाहते हैं, तो सेटिंग्स में दिए गए लिंक से मुझसे संपर्क करें और मैं सीमा बढ़ा दूंगा।" + } + }, + "pt-BR" : { + "stringUnit" : { + "state" : "translated", + "value" : "Com o SubWidget Pro, você pode adicionar até 10 canais. Se quiser adicionar mais, fale comigo pelo link em Ajustes e eu aumentarei o limite." + } + }, + "ta" : { + "stringUnit" : { + "state" : "translated", + "value" : "SubWidget Pro உடன், நீங்கள் 10 சேனல்கள் வரை சேர்க்கலாம். அதற்கு மேல் சேர்க்க விரும்பினால், அமைப்புகளில் உள்ள இணைப்பைப் பயன்படுத்தி என்னை தொடர்பு கொள்ளுங்கள்; நான் வரம்பை உயர்த்துவேன்." + } + } + } + }, "You can only add 10 channels. Swipe left on a channel to delete it." : { "localizations" : { "ar" : { @@ -4730,7 +5419,7 @@ "es" : { "stringUnit" : { "state" : "translated", - "value" : "Puedes solicitar una función usando la pestaña de Lista de deseos a continuación." + "value" : "Puedes solicitar una función usando la pestaña Lista de deseos a continuación." } }, "fr" : { @@ -4804,6 +5493,52 @@ } } } + }, + "YouTube channel statistics, at a glance" : { + "localizations" : { + "ar" : { + "stringUnit" : { + "state" : "translated", + "value" : "إحصاءات قناة YouTube بنظرة سريعة" + } + }, + "de" : { + "stringUnit" : { + "state" : "translated", + "value" : "YouTube-Kanalstatistiken auf einen Blick" + } + }, + "es" : { + "stringUnit" : { + "state" : "translated", + "value" : "Estadísticas de canales de YouTube de un vistazo" + } + }, + "fr" : { + "stringUnit" : { + "state" : "translated", + "value" : "Les statistiques de chaîne YouTube en un coup d’œil" + } + }, + "hi" : { + "stringUnit" : { + "state" : "translated", + "value" : "YouTube चैनल आँकड़े, एक नज़र में" + } + }, + "pt-BR" : { + "stringUnit" : { + "state" : "translated", + "value" : "Estatísticas do canal do YouTube de relance" + } + }, + "ta" : { + "stringUnit" : { + "state" : "translated", + "value" : "YouTube சேனல் புள்ளிவிவரங்கள், ஒரே பார்வையில்" + } + } + } } }, "version" : "1.0" diff --git a/SubscriberWidget/Model/FAQItem.swift b/SubscriberWidget/Model/FAQItem.swift index b5e1ffa..ae71668 100644 --- a/SubscriberWidget/Model/FAQItem.swift +++ b/SubscriberWidget/Model/FAQItem.swift @@ -29,6 +29,10 @@ struct FAQItem: Identifiable, Equatable { question: "I can't find my channel.", answer: "Try entering your channel ID instead of your channel name. If that still doesn't work, please contact me and I will help you." ), + .init( + question: "How do I delete a channel?", + answer: "Swipe right on the channel to delete it" + ), .init( question: "The channels aren't loading.", answer: "This indicates a server issue. Please try again later." @@ -44,7 +48,7 @@ struct FAQItem: Identifiable, Equatable { .init( question: "How many channels can I add?", answer: """ - Currently, you can add up to 10 channels. If you would like to add more, \ + With SubWidget Pro, you can add up to 10 channels. If you would like to add more, \ please contact me using the link in Settings and I will increase the limit. """ ), @@ -54,11 +58,15 @@ struct FAQItem: Identifiable, Equatable { ), .init( question: "What does the reset button do?", - answer: "Restores the widget's colors to the defaults." + answer: "Restores the widget's colors to the defaults - White in light mode and black in dark mode." ), .init( question: "I have a feature idea I want to request.", answer: "You can request a feature using the Wishlist tab below." + ), + .init( + question: "My question is not listed here.", + answer: "Please contact me from the settings screen and I will answer any question you have. I typically respond within a few hours!." ) ] diff --git a/SubscriberWidget/Services/AnalyticsService.swift b/SubscriberWidget/Services/AnalyticsService.swift index aa46a59..bef93df 100644 --- a/SubscriberWidget/Services/AnalyticsService.swift +++ b/SubscriberWidget/Services/AnalyticsService.swift @@ -248,6 +248,22 @@ class AnalyticsService { ]) } + func logOnboardingShown() { + logEvent("onboarding.shown") + } + + func logOnboardingStepViewed() { + logEvent("onboarding.step_viewed") + } + + func logOnboardingAdvanced() { + logEvent("onboarding.advanced") + } + + func logOnboardingCompleted() { + logEvent("onboarding.completed") + } + func logSubscriptionPurchaseCompleted() { logEvent("subscription.purchase_completed") } diff --git a/SubscriberWidget/View/MainView.swift b/SubscriberWidget/View/MainView.swift index e7977e8..cb22ad8 100644 --- a/SubscriberWidget/View/MainView.swift +++ b/SubscriberWidget/View/MainView.swift @@ -12,12 +12,21 @@ import WidgetKit import ConfettiSwiftUI struct MainView: View { + private enum OnboardingAction { + case none + case completed + } + @StateObject var viewModel: ViewModel = ViewModel() @State private var currentTab = 0 @State private var confettiTrigger = 0 @State private var showPaywall = false + @State private var showOnboarding = false + @State private var hasPreparedInitialPresentation = false + @State private var onboardingAction: OnboardingAction = .none @AppStorage("pendingPaywallFromWidget", store: .shared) private var pendingPaywallFromWidget: Bool = false @AppStorage("hasProAccess", store: .shared) private var hasProAccess: Bool = false + @AppStorage("hasCompletedOnboarding", store: .shared) private var hasCompletedOnboarding: Bool = false init() { WishKit.configure(with: Constants.wishKitApiKey) @@ -73,12 +82,20 @@ struct MainView: View { .onReceive(NotificationCenter.default.publisher(for: .paywallRequested)) { _ in handleWidgetPaywallRequest(source: "widget") } + .onChange(of: hasProAccess) { hasProAccess in + guard hasProAccess else { return } + hasCompletedOnboarding = true + showOnboarding = false + } .onAppear { if pendingPaywallFromWidget { pendingPaywallFromWidget = false handleWidgetPaywallRequest(source: "widget_cold_start") } } + .task { + await prepareInitialPresentation() + } .confettiCannon( trigger: $confettiTrigger, num: 40, @@ -86,6 +103,14 @@ struct MainView: View { radius: 500, repetitions: 1, ) + .sheet( + isPresented: $showOnboarding, + onDismiss: handleOnboardingDismissed + ) { + OnboardingView(onComplete: completeOnboarding) + .interactiveDismissDisabled() + .presentationDragIndicator(.hidden) + } .paywallSheet(isPresented: $showPaywall) } @@ -99,6 +124,48 @@ struct MainView: View { } } } + + @MainActor + private func prepareInitialPresentation() async { + guard !hasPreparedInitialPresentation else { return } + hasPreparedInitialPresentation = true + + await SubscriptionService().checkAccess() + let hasSavedChannels = !ChannelStorageService().getChannels().isEmpty + + guard !hasProAccess else { + hasCompletedOnboarding = true + return + } + + guard !hasSavedChannels else { + hasCompletedOnboarding = true + return + } + + guard !hasCompletedOnboarding else { return } + + showOnboarding = true + } + + private func completeOnboarding() { + onboardingAction = .completed + hasCompletedOnboarding = true + showOnboarding = false + } + + private func handleOnboardingDismissed() { + hasCompletedOnboarding = true + + if onboardingAction == .completed { + currentTab = 0 + DispatchQueue.main.asyncAfter(deadline: .now() + 0.2) { + NotificationCenter.default.post(name: .addWidgetRequested, object: nil) + } + } + + onboardingAction = .none + } } struct MainView_Previews: PreviewProvider { diff --git a/SubscriberWidget/View/Onboarding/OnboardingAddWidgetCard.swift b/SubscriberWidget/View/Onboarding/OnboardingAddWidgetCard.swift new file mode 100644 index 0000000..4c73138 --- /dev/null +++ b/SubscriberWidget/View/Onboarding/OnboardingAddWidgetCard.swift @@ -0,0 +1,75 @@ +// +// OnboardingAddWidgetCard.swift +// SubscriberWidget +// + +import SwiftUI + +struct OnboardingAddWidgetCard: View { + private let mediumScale: CGFloat = 0.90 + + var body: some View { + let entry = OnboardingPreviewData.mrBeastEntry(widgetType: .combined) + + VStack(spacing: 16) { + MediumWidget(entry: entry) + .widgetBackground(bgColor: nil, size: .medium) + .frame(width: WidgetSize.medium.width, height: 155) + .scaleEffect(mediumScale) + .frame(width: WidgetSize.medium.width * mediumScale, height: 155 * mediumScale) + + OnboardingInlineStepsView( + firstStep: "Add your channel", + secondStep: "Place the widget" + ) + } + .padding(18) + .background( + RoundedRectangle(cornerRadius: 26) + .fill(Color(UIColor.secondarySystemGroupedBackground)) + .overlay( + RoundedRectangle(cornerRadius: 26) + .stroke(Color.primary.opacity(0.05), lineWidth: 1) + ) + ) + } +} + +struct OnboardingInlineStepsView: View { + let firstStep: LocalizedStringKey + let secondStep: LocalizedStringKey + + var body: some View { + VStack(alignment: .leading, spacing: 10) { + OnboardingInlineStepRow(number: "1", text: firstStep) + OnboardingInlineStepRow(number: "2", text: secondStep) + } + .frame(maxWidth: .infinity, alignment: .leading) + .padding(.horizontal, 4) + } +} + +struct OnboardingInlineStepRow: View { + let number: String + let text: LocalizedStringKey + + var body: some View { + HStack(spacing: 10) { + Text(number) + .font(.caption.bold()) + .foregroundStyle(.white) + .frame(width: 22, height: 22) + .background( + Circle() + .fill(Color.youtubeRed) + ) + + Text(text) + .font(.subheadline.weight(.semibold)) + .foregroundStyle(.secondary) + .fixedSize(horizontal: false, vertical: true) + + Spacer(minLength: 0) + } + } +} diff --git a/SubscriberWidget/View/Onboarding/OnboardingSearchCard.swift b/SubscriberWidget/View/Onboarding/OnboardingSearchCard.swift new file mode 100644 index 0000000..4dfd921 --- /dev/null +++ b/SubscriberWidget/View/Onboarding/OnboardingSearchCard.swift @@ -0,0 +1,103 @@ +// +// OnboardingSearchCard.swift +// SubscriberWidget +// + +import SwiftUI + +struct OnboardingSearchCard: View { + @Environment(\.colorScheme) private var colorScheme + private let mediumScale: CGFloat = 0.90 + + var body: some View { + let entry = OnboardingPreviewData.mkbhdEntry(widgetType: .subscribers) + + VStack(spacing: 14) { + HStack(spacing: 10) { + Image(systemName: "magnifyingglass") + .foregroundStyle(.secondary) + + Text("@mkbhd") + .font(.headline) + .foregroundStyle(colorScheme == .dark ? Color.white : Color.primary) + + Spacer() + } + .padding(.horizontal, 16) + .frame(minHeight: 50) + .background(searchBarFill) + .cornerRadius(16) + + VStack(spacing: 12) { + OnboardingSearchResultCard( + name: "Marques Brownlee", + imageName: OnboardingPreviewData.mkbhdImageName + ) + + Image(systemName: "arrow.down") + .font(.system(size: 14, weight: .bold)) + .foregroundStyle(Color.youtubeRed) + + MediumWidget(entry: entry) + .widgetBackground(bgColor: nil, size: .medium) + .frame(width: WidgetSize.medium.width, height: 155) + .scaleEffect(mediumScale) + .frame(width: WidgetSize.medium.width * mediumScale, height: 155 * mediumScale) + } + .frame(maxWidth: .infinity) + } + .padding(16) + .background(cardBackground) + .cornerRadius(26) + } + + private var searchBarFill: Color { + colorScheme == .dark ? Color.white.opacity(0.08) : Color(UIColor.systemBackground) + } + + private var cardBackground: some View { + RoundedRectangle(cornerRadius: 26) + .fill(colorScheme == .dark ? Color.white.opacity(0.04) : Color.youtubeRed.opacity(0.05)) + .overlay( + RoundedRectangle(cornerRadius: 26) + .stroke(colorScheme == .dark ? Color.white.opacity(0.08) : Color.youtubeRed.opacity(0.10), lineWidth: 1) + ) + } +} + +struct OnboardingSearchResultCard: View { + let name: String + let imageName: String + + var body: some View { + HStack(spacing: 12) { + Image(imageName) + .resizable() + .scaledToFill() + .frame(width: 44, height: 44) + .clipShape(Circle()) + .shadow(radius: 2) + + Text(name) + .font(.headline) + .foregroundStyle(.primary) + .lineLimit(1) + .minimumScaleFactor(0.9) + + Spacer() + + Image(systemName: "checkmark.circle.fill") + .font(.system(size: 20)) + .foregroundStyle(Color.youtubeRed) + } + .padding(14) + .background( + RoundedRectangle(cornerRadius: 18) + .fill(Color(UIColor.secondarySystemGroupedBackground)) + .overlay( + RoundedRectangle(cornerRadius: 18) + .stroke(Color.primary.opacity(0.05), lineWidth: 1) + ) + ) + } +} diff --git a/SubscriberWidget/View/Onboarding/OnboardingView.swift b/SubscriberWidget/View/Onboarding/OnboardingView.swift new file mode 100644 index 0000000..f206713 --- /dev/null +++ b/SubscriberWidget/View/Onboarding/OnboardingView.swift @@ -0,0 +1,233 @@ +// +// OnboardingView.swift +// SubscriberWidget +// + +import SwiftUI +import UIKit + +struct OnboardingView: View { + @Environment(\.colorScheme) private var colorScheme + @State private var currentStep: OnboardingStep = .welcome + + let onComplete: () -> Void + + var body: some View { + VStack(spacing: 0) { + OnboardingHeaderView(currentStep: currentStep) + + TabView(selection: $currentStep) { + ForEach(OnboardingStep.allCases) { step in + OnboardingPageView(step: step) + .tag(step) + } + } + .tabViewStyle(.page(indexDisplayMode: .never)) + + OnboardingFooterView( + currentStep: currentStep, + onContinue: handleContinueTapped + ) + } + .background(backgroundColor.ignoresSafeArea()) + .onAppear { + AnalyticsService.shared.logOnboardingShown() + AnalyticsService.shared.logOnboardingStepViewed() + } + .onChange(of: currentStep) { _ in + AnalyticsService.shared.logOnboardingStepViewed() + } + } + + private var backgroundColor: Color { + colorScheme == .dark ? .black : Color(UIColor.systemGray6) + } + + private func handleContinueTapped() { + if currentStep == .addWidget { + AnalyticsService.shared.logOnboardingCompleted() + onComplete() + return + } + + guard let nextStep = OnboardingStep(rawValue: currentStep.rawValue + 1) else { return } + AnalyticsService.shared.logOnboardingAdvanced() + withAnimation(.easeInOut(duration: 0.25)) { + currentStep = nextStep + } + } +} + +#Preview { + OnboardingView(onComplete: {}) +} + +struct OnboardingHeaderView: View { + let currentStep: OnboardingStep + + var body: some View { + VStack(spacing: 16) { + HStack(spacing: 8) { + ForEach(OnboardingStep.allCases) { step in + Capsule() + .fill(step == currentStep ? Color.youtubeRed : Color.primary.opacity(0.10)) + .frame(height: 6) + } + } + } + .padding(.top, 24) + .padding(.horizontal, 24) + .padding(.bottom, 18) + } +} + +struct OnboardingFooterView: View { + let currentStep: OnboardingStep + let onContinue: () -> Void + + var body: some View { + VStack(spacing: 16) { + Divider() + + Button(action: onContinue) { + Text(currentStep.buttonTitle) + .font(.headline) + .foregroundStyle(.white) + .frame(maxWidth: .infinity) + .frame(minHeight: 54) + } + .background(Color.youtubeRed) + .clipShape(RoundedRectangle(cornerRadius: 18)) + .padding(.horizontal, 24) + .padding(.bottom, 18) + } + } +} + +struct OnboardingPageView: View { + @Environment(\.colorScheme) private var colorScheme + + let step: OnboardingStep + + var body: some View { + VStack(alignment: .leading, spacing: 14) { + VStack(alignment: .leading, spacing: 6) { + Text(step.eyebrow) + .font(.caption.bold()) + .tracking(1.4) + .foregroundStyle(Color.youtubeRed) + + Text(step.title) + .font(.title.bold()) + .foregroundStyle(colorScheme == .dark ? Color.white : Color.primary) + .fixedSize(horizontal: false, vertical: true) + + Text(step.subtitle) + .font(.subheadline) + .foregroundStyle(.secondary) + .fixedSize(horizontal: false, vertical: true) + } + + switch step { + case .welcome: + OnboardingWelcomeCard() + case .addChannels: + OnboardingSearchCard() + case .addWidget: + OnboardingAddWidgetCard() + } + + Spacer(minLength: 0) + } + .padding(.horizontal, 24) + .padding(.bottom, 8) + } +} + +enum OnboardingStep: Int, CaseIterable, Identifiable { + case welcome + case addChannels + case addWidget + + var id: Int { rawValue } + + var eyebrow: LocalizedStringKey { + switch self { + case .welcome: + "AT A GLANCE" + case .addChannels: + "FIND A CHANNEL" + case .addWidget: + "START FREE" + } + } + + var title: LocalizedStringKey { + switch self { + case .welcome: + "YouTube channel statistics, at a glance" + case .addChannels: + "Search a channel. Add the widget." + case .addWidget: + "Add your first channel for free" + } + } + + var subtitle: LocalizedStringKey { + switch self { + case .welcome: + "See subscriber and view counts on your home screen or lock screen." + case .addChannels: + "Search by name, @handle, or channel ID. See a preview instantly." + case .addWidget: + "Add your first channel now, then place the widget when you’re ready." + } + } + + var buttonTitle: LocalizedStringKey { + switch self { + case .addWidget: + "Get Started" + default: + "Continue" + } + } +} + +enum OnboardingPreviewData { + static let mkbhdImageName = "OnboardingAvatar-mkbhd" + static let mrBeastImageName = "OnboardingAvatar-mrbeast" + + static func mkbhdChannel(displayName: String = "Marques Brownlee") -> YouTubeChannel { + var preview = YouTubeChannel.preview + preview.channelName = displayName + preview.profileImage = mkbhdImageName + preview.subCount = "20100000" + preview.viewCount = "4700000000" + preview.channelId = "UCBJycsmduvYEL83R_U4JriQ" + return preview + } + + static func mkbhdEntry(widgetType: WidgetType, displayName: String = "Marques Brownlee") -> SimpleEntry { + SimpleEntry( + channel: mkbhdChannel(displayName: displayName), + channelImage: UIImage(named: mkbhdImageName) ?? UIImage(systemName: "person.circle")!, + widgetType: widgetType + ) + } + + static func mrBeastEntry(widgetType: WidgetType, displayName: String = "MrBeast") -> SimpleEntry { + var preview = YouTubeChannel.preview + preview.channelName = displayName + preview.profileImage = mrBeastImageName + preview.subCount = "389000000" + preview.viewCount = "75800000000" + preview.channelId = "UCX6OQ3DkcsbYNE6H8uQQuVA" + + return SimpleEntry( + channel: preview, + channelImage: UIImage(named: mrBeastImageName) ?? UIImage(systemName: "person.circle")!, + widgetType: widgetType + ) + } +} diff --git a/SubscriberWidget/View/Onboarding/OnboardingWelcomeCard.swift b/SubscriberWidget/View/Onboarding/OnboardingWelcomeCard.swift new file mode 100644 index 0000000..0d93119 --- /dev/null +++ b/SubscriberWidget/View/Onboarding/OnboardingWelcomeCard.swift @@ -0,0 +1,323 @@ +// +// OnboardingWelcomeCard.swift +// SubscriberWidget +// + +import SwiftUI +import UIKit + +struct OnboardingWelcomeCard: View { + private let widgetScale: CGFloat = 0.86 + private let carouselSpacing: CGFloat = 8 + private let lockscreenWidth: CGFloat = 172 + private let lockscreenHeight: CGFloat = 78 + private let rowSpacing: CGFloat = 16 + private let showcaseItems: [OnboardingWidgetShowcaseItem] = Self.makeShowcaseItems() + + var body: some View { + let cardShape = RoundedRectangle(cornerRadius: 26, style: .continuous) + + ZStack { + cardShape + .fill(Color(UIColor.secondarySystemGroupedBackground)) + + VStack(spacing: rowSpacing) { + showcaseRow(phaseOffset: 0.0) + showcaseRow(phaseOffset: 0.36) + } + .padding(.vertical, 14) + } + .clipShape(cardShape) + .overlay { + cardShape + .stroke(Color.primary.opacity(0.05), lineWidth: 1) + } + .frame(height: 350) + .frame(maxWidth: .infinity) + } + + @ViewBuilder + private func showcaseRow(phaseOffset: Double) -> some View { + OnboardingCarousel( + pointsPerSecond: 32, + contentWidth: sequenceWidth, + spacing: carouselSpacing, + phaseOffset: phaseOffset + ) { + HStack(spacing: carouselSpacing) { + ForEach(showcaseItems) { item in + OnboardingWidgetShowcaseItemView( + item: item, + widgetScale: widgetScale, + lockscreenWidth: lockscreenWidth, + lockscreenHeight: lockscreenHeight + ) + } + } + } + } + + private var sequenceWidth: CGFloat { + showcaseItems.reduce(CGFloat.zero) { partialResult, item in + partialResult + item.displaySize( + widgetScale: widgetScale, + lockscreenWidth: lockscreenWidth, + lockscreenHeight: lockscreenHeight + ).width + } + (CGFloat(showcaseItems.count - 1) * carouselSpacing) + } + + private static func makeShowcaseItems() -> [OnboardingWidgetShowcaseItem] { + let palettes = Palette.presets + + return [ + .init(channel: channel( + name: "MrBeast", + subscribers: "389000000", + views: "75800000000", + imageName: "OnboardingAvatar-mrbeast", + palette: palettes[6] + ), family: .small, widgetType: .subscribers, channelImage: channelImage(named: "OnboardingAvatar-mrbeast")), + .init(channel: channel( + name: "Marques Brownlee", + subscribers: "20100000", + views: "4700000000", + imageName: "OnboardingAvatar-mkbhd", + palette: palettes[11] + ), family: .medium, widgetType: .combined, channelImage: channelImage(named: "OnboardingAvatar-mkbhd")), + .init(channel: channel( + name: "PewDiePie", + subscribers: "111000000", + views: "29300000000", + imageName: "OnboardingAvatar-pewdiepie", + palette: palettes[13] + ), family: .lockscreen, widgetType: .subscribers, channelImage: channelImage(named: "OnboardingAvatar-pewdiepie")), + .init(channel: channel( + name: "iShowSpeed", + subscribers: "39500000", + views: "4300000000", + imageName: "OnboardingAvatar-ishowspeed", + palette: palettes[14], + ), family: .small, widgetType: .views, channelImage: channelImage(named: "OnboardingAvatar-ishowspeed")), + .init(channel: channel( + name: "Mark Rober", + subscribers: "69800000", + views: "140000000", + imageName: "OnboardingAvatar-mark-rober", + palette: palettes[0], + ), family: .medium, widgetType: .views, channelImage: channelImage(named: "OnboardingAvatar-mark-rober")), + .init(channel: channel( + name: "Linus Tech Tips", + subscribers: "16200000", + views: "4600000000", + imageName: "OnboardingAvatar-linus-tech-tips", + palette: palettes[10] + ), family: .lockscreen, widgetType: .views, channelImage: channelImage(named: "OnboardingAvatar-linus-tech-tips")), + .init(channel: channel( + name: "Veritasium", + subscribers: "17100000", + views: "2300000000", + imageName: "OnboardingAvatar-veritasium", + palette: palettes[2] + ), family: .medium, widgetType: .subscribers, channelImage: channelImage(named: "OnboardingAvatar-veritasium")) + ] + } + + private static func channel( + name: String, + subscribers: String, + views: String, + imageName: String, + palette: Palette + ) -> YouTubeChannel { + YouTubeChannel( + channelName: name, + profileImage: imageName, + subCount: subscribers, + viewCount: views, + channelId: "", + bgColor: UIColor(palette.background), + accentColor: UIColor(palette.accent), + numberColor: UIColor(palette.number) + ) + } + + private static func channelImage(named name: String) -> UIImage { + UIImage(named: name) ?? UIImage(systemName: "person.circle")! + } +} + +struct OnboardingWidgetShowcaseItem: Identifiable { + static let widgetHeight: CGFloat = 155 + static let shadowPadding: CGFloat = 12 + + enum Family { + case small + case medium + case lockscreen + } + + let id: String + let channel: YouTubeChannel + let family: Family + let widgetType: WidgetType + let channelImage: UIImage + + init(channel: YouTubeChannel, family: Family, widgetType: WidgetType, channelImage: UIImage) { + self.id = "\(channel.channelId)-\(String(describing: family))-\(widgetType.rawValue)" + self.channel = channel + self.family = family + self.widgetType = widgetType + self.channelImage = channelImage + } + + var widgetSize: WidgetSize? { + switch family { + case .small: + .small + case .medium: + .medium + case .lockscreen: + nil + } + } + + var baseSize: CGSize { + switch family { + case .small: + CGSize(width: WidgetSize.small.width, height: Self.widgetHeight) + case .medium: + CGSize(width: WidgetSize.medium.width, height: Self.widgetHeight) + case .lockscreen: + .zero + } + } + + func displaySize(widgetScale: CGFloat, lockscreenWidth: CGFloat, lockscreenHeight: CGFloat) -> CGSize { + switch family { + case .small, .medium: + let baseSize = baseSize + return CGSize( + width: (baseSize.width * widgetScale) + (Self.shadowPadding * 2), + height: (baseSize.height * widgetScale) + (Self.shadowPadding * 2) + ) + case .lockscreen: + return CGSize(width: lockscreenWidth, height: lockscreenHeight) + } + } +} + +struct OnboardingWidgetShowcaseItemView: View { + let item: OnboardingWidgetShowcaseItem + let widgetScale: CGFloat + let lockscreenWidth: CGFloat + let lockscreenHeight: CGFloat + + var body: some View { + switch item.family { + case .small: + scaledWidgetPreview { + SmallWidget(entry: entry) + } + case .medium: + scaledWidgetPreview { + MediumWidget(entry: entry) + } + case .lockscreen: + ZStack { + RoundedRectangle(cornerRadius: 20, style: .continuous) + .fill(Color(UIColor.secondarySystemBackground)) + .overlay( + RoundedRectangle(cornerRadius: 20, style: .continuous) + .stroke(Color.primary.opacity(0.08), lineWidth: 1) + ) + + LockscreenWidget(entry: entry) + .padding(.horizontal, 12) + .padding(.vertical, 10) + } + .frame(width: lockscreenWidth, height: lockscreenHeight) + } + } + + @ViewBuilder + private func scaledWidgetPreview(@ViewBuilder content: () -> Content) -> some View { + let baseSize = item.baseSize + let displaySize = item.displaySize( + widgetScale: widgetScale, + lockscreenWidth: lockscreenWidth, + lockscreenHeight: lockscreenHeight + ) + + content() + .widgetBackground(bgColor: item.channel.bgColor, size: item.widgetSize ?? .small) + .frame(width: baseSize.width, height: baseSize.height) + .scaleEffect(widgetScale) + .padding(OnboardingWidgetShowcaseItem.shadowPadding) + .frame(width: displaySize.width, height: displaySize.height) + } + + private var entry: SimpleEntry { + SimpleEntry( + channel: item.channel, + channelImage: item.channelImage, + widgetType: item.widgetType + ) + } +} + +struct OnboardingCarousel: View { + @Environment(\.accessibilityReduceMotion) private var reduceMotion + + let pointsPerSecond: CGFloat + let contentWidth: CGFloat + let spacing: CGFloat + let phaseOffset: Double + @ViewBuilder let content: () -> Content + + @State private var startDate = Date() + + var body: some View { + GeometryReader { proxy in + TimelineView(.periodic(from: startDate, by: 1.0 / 60.0)) { timeline in + ZStack(alignment: .leading) { + HStack(spacing: spacing) { + content() + content() + } + .fixedSize(horizontal: true, vertical: false) + .compositingGroup() + .drawingGroup() + .offset(x: offset(for: timeline.date)) + } + .transaction { transaction in + transaction.animation = nil + } + } + .frame(width: proxy.size.width, height: proxy.size.height, alignment: .leading) + .clipped() + } + .onAppear { + startDate = .now + } + } + + private var travelDistance: CGFloat { + contentWidth + spacing + } + + private var animationDuration: Double { + Double(travelDistance / pointsPerSecond) + } + + private func offset(for date: Date) -> CGFloat { + guard !reduceMotion else { + return -travelDistance * CGFloat((0.18 + phaseOffset).truncatingRemainder(dividingBy: 1)) + } + + let elapsed = date.timeIntervalSince(startDate) + let progress = (elapsed.truncatingRemainder(dividingBy: animationDuration) / animationDuration + phaseOffset) + .truncatingRemainder(dividingBy: 1) + return -travelDistance * progress + } +} diff --git a/SubscriberWidget/View/View+Extensions.swift b/SubscriberWidget/View/View+Extensions.swift index 7e6d0ee..5d26aaa 100644 --- a/SubscriberWidget/View/View+Extensions.swift +++ b/SubscriberWidget/View/View+Extensions.swift @@ -13,6 +13,7 @@ import Foundation extension Notification.Name { static let revenueCatPurchaseCompleted = Notification.Name("revenueCatPurchaseCompleted") static let paywallRequested = Notification.Name("paywallRequested") + static let addWidgetRequested = Notification.Name("addWidgetRequested") } extension View { diff --git a/SubscriberWidget/View/WidgetListView/WidgetListView.swift b/SubscriberWidget/View/WidgetListView/WidgetListView.swift index 3b36e21..ae79e98 100644 --- a/SubscriberWidget/View/WidgetListView/WidgetListView.swift +++ b/SubscriberWidget/View/WidgetListView/WidgetListView.swift @@ -120,6 +120,9 @@ struct WidgetListView: View { } } .navigationViewStyle(StackNavigationViewStyle()) + .onReceive(NotificationCenter.default.publisher(for: .addWidgetRequested)) { _ in + addWidgetTapped() + } .task { await viewModel.loadChannels()