mirror of
https://github.com/lumaa-dev/BubbleApp.git
synced 2025-02-01 11:07:09 +01:00
Push Notifications test
This commit is contained in:
parent
3818ad9fe3
commit
ff034e6591
@ -150,6 +150,7 @@
|
||||
B9A80E9B2C67D56900DE3D88 /* FollowGoalWidget.swift in Sources */ = {isa = PBXBuildFile; fileRef = B9B469AF2B9A275F00AD5585 /* FollowGoalWidget.swift */; };
|
||||
B9A80E9C2C67D56900DE3D88 /* CreatePostWidget.swift in Sources */ = {isa = PBXBuildFile; fileRef = B9A80DDD2C67BFF800DE3D88 /* CreatePostWidget.swift */; };
|
||||
B9A8DABA2BB7364300A890CC /* PostsView.swift in Sources */ = {isa = PBXBuildFile; fileRef = B9A8DAB92BB7364300A890CC /* PostsView.swift */; };
|
||||
B9AC6BD12CF341C2009C65C7 /* Secret.plist in Resources */ = {isa = PBXBuildFile; fileRef = B9AC6BD02CF341C2009C65C7 /* Secret.plist */; };
|
||||
B9B469B02B9A275F00AD5585 /* FollowGoalWidget.swift in Sources */ = {isa = PBXBuildFile; fileRef = B9B469AF2B9A275F00AD5585 /* FollowGoalWidget.swift */; };
|
||||
B9B469B22B9A6E8300AD5585 /* PrivacyView.swift in Sources */ = {isa = PBXBuildFile; fileRef = B9B469B12B9A6E8300AD5585 /* PrivacyView.swift */; };
|
||||
B9B469DB2B9B2EDB00AD5585 /* ComingSoonView.swift in Sources */ = {isa = PBXBuildFile; fileRef = B9B469DA2B9B2EDB00AD5585 /* ComingSoonView.swift */; };
|
||||
@ -185,6 +186,8 @@
|
||||
B9D173E22CA0555800CB575F /* MetricsManager.swift in Sources */ = {isa = PBXBuildFile; fileRef = B9D173E12CA0555800CB575F /* MetricsManager.swift */; };
|
||||
B9D173E32CA0555800CB575F /* MetricsManager.swift in Sources */ = {isa = PBXBuildFile; fileRef = B9D173E12CA0555800CB575F /* MetricsManager.swift */; };
|
||||
B9D365612B79A1BE004C1255 /* MailView.swift in Sources */ = {isa = PBXBuildFile; fileRef = B9D365602B79A1BE004C1255 /* MailView.swift */; };
|
||||
B9D700722CF295AB00A6DB81 /* AppNotification.swift in Sources */ = {isa = PBXBuildFile; fileRef = B9D700712CF295AB00A6DB81 /* AppNotification.swift */; };
|
||||
B9D700732CF295AB00A6DB81 /* AppNotification.swift in Sources */ = {isa = PBXBuildFile; fileRef = B9D700712CF295AB00A6DB81 /* AppNotification.swift */; };
|
||||
B9D9C6C12B6A56E000C26A41 /* Notification.swift in Sources */ = {isa = PBXBuildFile; fileRef = B9D9C6C02B6A56E000C26A41 /* Notification.swift */; };
|
||||
B9D9C6C32B6A576C00C26A41 /* NotificationsView.swift in Sources */ = {isa = PBXBuildFile; fileRef = B9D9C6C22B6A576C00C26A41 /* NotificationsView.swift */; };
|
||||
B9D9C6C52B6A587700C26A41 /* NotificationRow.swift in Sources */ = {isa = PBXBuildFile; fileRef = B9D9C6C42B6A587700C26A41 /* NotificationRow.swift */; };
|
||||
@ -317,6 +320,7 @@
|
||||
B9A80DDD2C67BFF800DE3D88 /* CreatePostWidget.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = CreatePostWidget.swift; sourceTree = "<group>"; };
|
||||
B9A80DE12C67C38E00DE3D88 /* URLNavigator.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = URLNavigator.swift; sourceTree = "<group>"; };
|
||||
B9A8DAB92BB7364300A890CC /* PostsView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = PostsView.swift; sourceTree = "<group>"; };
|
||||
B9AC6BD02CF341C2009C65C7 /* Secret.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = Secret.plist; sourceTree = "<group>"; };
|
||||
B9B469AF2B9A275F00AD5585 /* FollowGoalWidget.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = FollowGoalWidget.swift; sourceTree = "<group>"; };
|
||||
B9B469B12B9A6E8300AD5585 /* PrivacyView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = PrivacyView.swift; sourceTree = "<group>"; };
|
||||
B9B469DA2B9B2EDB00AD5585 /* ComingSoonView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ComingSoonView.swift; sourceTree = "<group>"; };
|
||||
@ -348,6 +352,7 @@
|
||||
B9CFC43A2B4F08C9004CFCB7 /* LaunchStoryboard.storyboard */ = {isa = PBXFileReference; lastKnownFileType = file.storyboard; path = LaunchStoryboard.storyboard; sourceTree = "<group>"; };
|
||||
B9D173E12CA0555800CB575F /* MetricsManager.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MetricsManager.swift; sourceTree = "<group>"; };
|
||||
B9D365602B79A1BE004C1255 /* MailView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MailView.swift; sourceTree = "<group>"; };
|
||||
B9D700712CF295AB00A6DB81 /* AppNotification.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AppNotification.swift; sourceTree = "<group>"; };
|
||||
B9D9C6C02B6A56E000C26A41 /* Notification.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Notification.swift; sourceTree = "<group>"; };
|
||||
B9D9C6C22B6A576C00C26A41 /* NotificationsView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = NotificationsView.swift; sourceTree = "<group>"; };
|
||||
B9D9C6C42B6A587700C26A41 /* NotificationRow.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = NotificationRow.swift; sourceTree = "<group>"; };
|
||||
@ -548,6 +553,7 @@
|
||||
B9D9C6BF2B6A56D500C26A41 /* Notifications */ = {
|
||||
isa = PBXGroup;
|
||||
children = (
|
||||
B9D700712CF295AB00A6DB81 /* AppNotification.swift */,
|
||||
B9D9C6C02B6A56E000C26A41 /* Notification.swift */,
|
||||
B999DE5D2B76F9D100509868 /* Message.swift */,
|
||||
);
|
||||
@ -592,6 +598,7 @@
|
||||
children = (
|
||||
B9C20D592B923D53004DC9B3 /* Bubble.entitlements */,
|
||||
B9FB94A02B2EF23100D81C07 /* Info.plist */,
|
||||
B9AC6BD02CF341C2009C65C7 /* Secret.plist */,
|
||||
B9FB945A2B2DEECE00D81C07 /* BubbleApp.swift */,
|
||||
B9EBE8572B474FD600FB594D /* AppDelegate.swift */,
|
||||
B9FB946E2B2DF3BB00D81C07 /* Components */,
|
||||
@ -875,6 +882,7 @@
|
||||
files = (
|
||||
B97BCE282B3ED2A80044756D /* .gitignore in Resources */,
|
||||
B9CDE7AD2C9FF536004B1BDD /* PolySans-BulkyWide.ttf in Resources */,
|
||||
B9AC6BD12CF341C2009C65C7 /* Secret.plist in Resources */,
|
||||
B9DC69302B79378400E625B9 /* BubblePlus.storekit in Resources */,
|
||||
B9FB94642B2DEECF00D81C07 /* Preview Assets.xcassets in Resources */,
|
||||
B9FB94612B2DEECF00D81C07 /* Assets.xcassets in Resources */,
|
||||
@ -917,6 +925,7 @@
|
||||
B9A80E272C67C4B700DE3D88 /* PostDetailsView.swift in Sources */,
|
||||
B9A80E282C67C4B700DE3D88 /* IconView.swift in Sources */,
|
||||
B9A80E292C67C4B700DE3D88 /* UpdateView.swift in Sources */,
|
||||
B9D700722CF295AB00A6DB81 /* AppNotification.swift in Sources */,
|
||||
B9A80E2A2C67C4B700DE3D88 /* ReportStatusView.swift in Sources */,
|
||||
B9A80E2B2C67C4B700DE3D88 /* PrivacyView.swift in Sources */,
|
||||
B9A80E2C2C67C4B700DE3D88 /* SafariView.swift in Sources */,
|
||||
@ -1067,6 +1076,7 @@
|
||||
B9A80E9A2C67D56900DE3D88 /* FollowCountWidget.swift in Sources */,
|
||||
B9A80E9B2C67D56900DE3D88 /* FollowGoalWidget.swift in Sources */,
|
||||
B9A80E9C2C67D56900DE3D88 /* CreatePostWidget.swift in Sources */,
|
||||
B9D700732CF295AB00A6DB81 /* AppNotification.swift in Sources */,
|
||||
B9C7F46C2C387D3B009C36DC /* WarningView.swift in Sources */,
|
||||
B9EBE8562B47256900FB594D /* PostAttachment.swift in Sources */,
|
||||
B9EBE8582B474FD600FB594D /* AppDelegate.swift in Sources */,
|
||||
|
@ -1,18 +1,21 @@
|
||||
//Made by Lumaa
|
||||
|
||||
import SwiftUI
|
||||
import SwiftData
|
||||
import UIKit
|
||||
import RevenueCat
|
||||
import UserNotifications
|
||||
|
||||
@Observable
|
||||
public class AppDelegate: NSObject, UIWindowSceneDelegate, Sendable, UIApplicationDelegate {
|
||||
public class AppDelegate: NSObject, UIWindowSceneDelegate, Sendable, UIApplicationDelegate, UNUserNotificationCenterDelegate {
|
||||
public var window: UIWindow?
|
||||
public private(set) var windowWidth: CGFloat = UIScreen.main.bounds.size.width
|
||||
public private(set) var windowHeight: CGFloat = UIScreen.main.bounds.size.height
|
||||
public private(set) var secret: [String: String] = [:]
|
||||
|
||||
public static var premium: Bool = false
|
||||
|
||||
public static var tokenized: Bool = false
|
||||
|
||||
public func scene(_ scene: UIScene, willConnectTo _: UISceneSession, options _: UIScene.ConnectionOptions) {
|
||||
guard let windowScene = scene as? UIWindowScene else { return }
|
||||
window = windowScene.keyWindow
|
||||
@ -20,7 +23,31 @@ public class AppDelegate: NSObject, UIWindowSceneDelegate, Sendable, UIApplicati
|
||||
|
||||
override public init() {
|
||||
super.init()
|
||||
|
||||
|
||||
AppNotification.requestAuthorization { success in
|
||||
guard !Self.tokenized else { return }
|
||||
Self.tokenized = true
|
||||
let ownedAccs: [LoggedAccount] = self.getAccounts()
|
||||
ownedAccs.forEach { acc in
|
||||
Task {
|
||||
let tempCli: Client = .init(server: acc.app?.server ?? "mastodon.social", oauthToken: acc.token)
|
||||
await AppNotification.sendToken(client: tempCli, oauth: acc.token)
|
||||
}
|
||||
}
|
||||
}
|
||||
#if !WIDGET
|
||||
if !UIApplication.shared.isRegisteredForRemoteNotifications {
|
||||
print("Registering REMOTE NOTIFICATION")
|
||||
|
||||
Task {
|
||||
UIApplication.shared.registerForRemoteNotifications()
|
||||
}
|
||||
} else {
|
||||
let token: String? = UserDefaults.standard.string(forKey: "deviceToken")
|
||||
print("ALREADY registered REMOTE NOTIFICATION \(token ?? "???")")
|
||||
}
|
||||
#endif
|
||||
|
||||
if let path = Bundle.main.path(forResource: "Secret", ofType: "plist") {
|
||||
let url = URL(fileURLWithPath: path)
|
||||
let data = try! Data(contentsOf: url)
|
||||
@ -39,7 +66,21 @@ public class AppDelegate: NSObject, UIWindowSceneDelegate, Sendable, UIApplicati
|
||||
Self.observedSceneDelegate.insert(self)
|
||||
_ = Self.observer // just for activating the lazy static property
|
||||
}
|
||||
|
||||
public static var deviceToken: String = "[X]"
|
||||
|
||||
public func application(_ application: UIApplication, didRegisterForRemoteNotificationsWithDeviceToken deviceToken: Data) {
|
||||
let token = deviceToken.map { String(format: "%02.2hhx", $0) }.joined()
|
||||
AppDelegate.deviceToken = token
|
||||
UserDefaults.standard.setValue(token, forKey: "deviceToken")
|
||||
|
||||
print("[TOKEN] Got deviceToken: \(token)")
|
||||
// send device token to server
|
||||
}
|
||||
|
||||
public func application(_ application: UIApplication, didFailToRegisterForRemoteNotificationsWithError error: any Error) {
|
||||
print("[TOKEN]: \(error)")
|
||||
}
|
||||
|
||||
static func readSecret() -> [String: String]? {
|
||||
if let path = Bundle.main.path(forResource: "Secret", ofType: "plist") {
|
||||
let url = URL(fileURLWithPath: path)
|
||||
@ -51,7 +92,15 @@ public class AppDelegate: NSObject, UIWindowSceneDelegate, Sendable, UIApplicati
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
|
||||
private func getAccounts() -> [LoggedAccount] {
|
||||
guard let modelContainer: ModelContainer = try? ModelContainer(for: LoggedAccount.self, configurations: ModelConfiguration(isStoredInMemoryOnly: false)) else { return [] }
|
||||
let modelContext = ModelContext(modelContainer)
|
||||
let loggedAccounts = try? modelContext.fetch(FetchDescriptor<LoggedAccount>())
|
||||
|
||||
return loggedAccounts ?? []
|
||||
}
|
||||
|
||||
/// This function uses the REAL customer info to access the premium state
|
||||
// static func hasPlus(completionHandler: @escaping (Bool) -> Void) {
|
||||
// Purchases.shared.getCustomerInfo { (customerInfo, error) in
|
||||
|
@ -2,6 +2,8 @@
|
||||
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
|
||||
<plist version="1.0">
|
||||
<dict>
|
||||
<key>aps-environment</key>
|
||||
<string>development</string>
|
||||
<key>com.apple.security.application-groups</key>
|
||||
<array>
|
||||
<string>group.lumaa.ThreadedApp</string>
|
||||
|
@ -6,6 +6,8 @@ import RevenueCat
|
||||
|
||||
@main
|
||||
struct BubbleApp: App {
|
||||
@UIApplicationDelegateAdaptor private var appDelegate: AppDelegate
|
||||
|
||||
init() {
|
||||
BubbleShortcuts.updateAppShortcutParameters() //might not work?
|
||||
|
||||
|
121
Bubble/Data/Notifications/AppNotification.swift
Normal file
121
Bubble/Data/Notifications/AppNotification.swift
Normal file
@ -0,0 +1,121 @@
|
||||
// Made by Lumaa
|
||||
|
||||
import UIKit
|
||||
import SwiftUI
|
||||
import Foundation
|
||||
import UserNotifications
|
||||
|
||||
class AppNotification {
|
||||
/// Authorizes the app to send the device's APNS token to the "Push\_URL" string in the `Secret.plist` file
|
||||
private static var registerToken: Bool = false
|
||||
|
||||
@AppStorage("sentToken") private static var sentToken: Bool = false
|
||||
static var hasSentToken: Bool {
|
||||
get {
|
||||
self.sentToken
|
||||
}
|
||||
}
|
||||
static var allowedNotifications: Bool = false
|
||||
|
||||
init() {}
|
||||
|
||||
static func requestAuthorization(completionHandler: @escaping (Bool) -> Void = {_ in}) {
|
||||
UNUserNotificationCenter.current().requestAuthorization(options: [.alert, .badge, .sound]) { success, err in
|
||||
completionHandler(success)
|
||||
self.allowedNotifications = success
|
||||
|
||||
guard success else { print("Did not validate"); return }
|
||||
|
||||
print("REQUESTED PUSH NOTIFICATION")
|
||||
Task {
|
||||
#if !WIDGET
|
||||
await UIApplication.shared.registerForRemoteNotifications()
|
||||
#endif
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
static func sendToken(client: Client, oauth: OauthToken) async {
|
||||
guard let acc: Account = try? await client.get(endpoint: Accounts.verifyCredentials), let accUrl: URL = acc.url, let server: String = accUrl.host() else { return }
|
||||
self.sendToken(instanceUrl: server, accessToken: oauth.accessToken)
|
||||
}
|
||||
|
||||
static func sendToken(account: Account, oauth: OauthToken) {
|
||||
guard let server = account.acct.split(separator: "@").first else { return }
|
||||
self.sendToken(instanceUrl: String(server), accessToken: oauth.accessToken)
|
||||
}
|
||||
|
||||
static private func sendToken(instanceUrl: String, accessToken: String) {
|
||||
guard let plist = AppDelegate.readSecret(), let baseUrl = plist["Push_URL"], !Self.hasSentToken, Self.allowedNotifications && Self.registerToken else {
|
||||
return
|
||||
}
|
||||
|
||||
let header: [String: String] = [
|
||||
"deviceToken": AppDelegate.deviceToken,
|
||||
"instance": instanceUrl,
|
||||
"accessToken": accessToken
|
||||
]
|
||||
|
||||
var formatted: String = ""
|
||||
|
||||
for (key, value) in header {
|
||||
if value == header.values.reversed().first {
|
||||
formatted += "\(key)=\(value)"
|
||||
} else {
|
||||
formatted += "\(key)=\(value)&"
|
||||
}
|
||||
}
|
||||
|
||||
print(formatted)
|
||||
|
||||
// let encoder: JSONEncoder = .init()
|
||||
// if let json = try? encoder.encode(header) {
|
||||
// var req = URLRequest(url: URL(string: "\(baseUrl)/push/add")!)
|
||||
// req.httpMethod = "POST"
|
||||
//// req.addValue("application/json", forHTTPHeaderField: "Content-Type")
|
||||
// req.addValue("application/x-www-form-urlencoded", forHTTPHeaderField: "Content-Type")
|
||||
// req.httpBody = json
|
||||
//
|
||||
// URLSession.shared.dataTask(with: req) { data, res, err in
|
||||
// if err != nil {
|
||||
// print(err?.localizedDescription ?? "No error details")
|
||||
// return
|
||||
// }
|
||||
//
|
||||
// let decoder: JSONDecoder = .init()
|
||||
// if let genRes: GenericResponse = try? decoder.decode(GenericResponse.self, from: data ?? Data()) {
|
||||
// let resType: String = genRes.success ? "successfully" : "incorrectly" // incorrect cause idk how to say "failed" with -ly
|
||||
// print("Server \(resType) replied with: \(genRes.message ?? "[NO MESSAGE]")")
|
||||
// } else {
|
||||
// print("No valid GenericResponse was output: \(String(data: data ?? Data(), encoding: .utf8) ?? "[NOT DECODED]")")
|
||||
// }
|
||||
// }.resume()
|
||||
// }
|
||||
|
||||
var req = URLRequest(url: URL(string: "\(baseUrl)/push/add")!, timeoutInterval: Double.infinity)
|
||||
req.httpMethod = "POST"
|
||||
req.addValue("application/x-www-form-urlencoded", forHTTPHeaderField: "Content-Type")
|
||||
req.httpBody = formatted.data(using: .utf8)
|
||||
|
||||
URLSession.shared.dataTask(with: req) { data, res, err in
|
||||
if err != nil {
|
||||
print(err?.localizedDescription ?? "No error details")
|
||||
return
|
||||
}
|
||||
|
||||
let decoder: JSONDecoder = .init()
|
||||
if let genRes: GenericResponse = try? decoder.decode(GenericResponse.self, from: data ?? Data()) {
|
||||
let resType: String = genRes.success ? "successfully" : "incorrectly" // incorrect cause idk how to say "failed" with -ly
|
||||
print("Server \(resType) replied with: \(genRes.message ?? "[NO MESSAGE]")")
|
||||
} else {
|
||||
print("No valid GenericResponse was output: \(String(data: data ?? Data(), encoding: .utf8) ?? "[NOT DECODED]")")
|
||||
}
|
||||
}.resume()
|
||||
}
|
||||
|
||||
/// This is a generic server response from the APNS server
|
||||
struct GenericResponse: Decodable {
|
||||
let success: Bool
|
||||
let message: String?
|
||||
}
|
||||
}
|
@ -5,6 +5,27 @@ import Foundation
|
||||
public struct Notification: Decodable, Identifiable, Equatable {
|
||||
public enum NotificationType: String, CaseIterable {
|
||||
case follow, follow_request, mention, reblog, status, favourite, poll, update
|
||||
|
||||
var localizedPush: String {
|
||||
switch self {
|
||||
case .follow:
|
||||
String(localized: "push.follow", comment: "PUSH NOTIFICATION \\w BODY")
|
||||
case .follow_request:
|
||||
String(localized: "push.follow_request", comment: "PUSH NOTIFICATION \\w BODY")
|
||||
case .mention:
|
||||
String(localized: "push.mention", comment: "PUSH NOTIFICATION \\w BODY")
|
||||
case .reblog:
|
||||
String(localized: "push.reblog", comment: "PUSH NOTIFICATION \\w BODY")
|
||||
case .status:
|
||||
String(localized: "push.status", comment: "PUSH NOTIFICATION \\w BODY")
|
||||
case .favourite:
|
||||
String(localized: "push.favourite", comment: "PUSH NOTIFICATION \\w BODY")
|
||||
case .poll:
|
||||
String(localized: "push.poll", comment: "PUSH NOTIFICATION \\w BODY")
|
||||
case .update:
|
||||
String(localized: "push.update", comment: "PUSH NOTIFICATION")
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public let id: String
|
||||
|
@ -21,5 +21,9 @@
|
||||
<array>
|
||||
<string>PolySans-BulkyWide.ttf</string>
|
||||
</array>
|
||||
<key>UIBackgroundModes</key>
|
||||
<array>
|
||||
<string>remote-notification</string>
|
||||
</array>
|
||||
</dict>
|
||||
</plist>
|
||||
|
@ -2320,6 +2320,142 @@
|
||||
}
|
||||
}
|
||||
},
|
||||
"push.favourite" : {
|
||||
"comment" : "PUSH NOTIFICATION \\w BODY",
|
||||
"localizations" : {
|
||||
"en" : {
|
||||
"stringUnit" : {
|
||||
"state" : "translated",
|
||||
"value" : "liked your post: %@"
|
||||
}
|
||||
},
|
||||
"fr" : {
|
||||
"stringUnit" : {
|
||||
"state" : "translated",
|
||||
"value" : "a aimé votre publication : %@"
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"push.follow" : {
|
||||
"comment" : "PUSH NOTIFICATION \\w BODY",
|
||||
"localizations" : {
|
||||
"en" : {
|
||||
"stringUnit" : {
|
||||
"state" : "translated",
|
||||
"value" : "followed you: %@"
|
||||
}
|
||||
},
|
||||
"fr" : {
|
||||
"stringUnit" : {
|
||||
"state" : "translated",
|
||||
"value" : "vous a suivi : %@"
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"push.follow_request" : {
|
||||
"comment" : "PUSH NOTIFICATION \\w BODY",
|
||||
"localizations" : {
|
||||
"en" : {
|
||||
"stringUnit" : {
|
||||
"state" : "translated",
|
||||
"value" : "wants to follow you: %@"
|
||||
}
|
||||
},
|
||||
"fr" : {
|
||||
"stringUnit" : {
|
||||
"state" : "translated",
|
||||
"value" : "veut vous suivre : %@"
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"push.mention" : {
|
||||
"comment" : "PUSH NOTIFICATION \\w BODY",
|
||||
"localizations" : {
|
||||
"en" : {
|
||||
"stringUnit" : {
|
||||
"state" : "translated",
|
||||
"value" : "mentioned you: %@"
|
||||
}
|
||||
},
|
||||
"fr" : {
|
||||
"stringUnit" : {
|
||||
"state" : "translated",
|
||||
"value" : "vous a mentionné : %@"
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"push.poll" : {
|
||||
"comment" : "PUSH NOTIFICATION \\w BODY",
|
||||
"localizations" : {
|
||||
"en" : {
|
||||
"stringUnit" : {
|
||||
"state" : "translated",
|
||||
"value" : "Poll ended: %@"
|
||||
}
|
||||
},
|
||||
"fr" : {
|
||||
"stringUnit" : {
|
||||
"state" : "translated",
|
||||
"value" : "Vote terminé : %@"
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"push.reblog" : {
|
||||
"comment" : "PUSH NOTIFICATION \\w BODY",
|
||||
"localizations" : {
|
||||
"en" : {
|
||||
"stringUnit" : {
|
||||
"state" : "translated",
|
||||
"value" : "reblogged your post: %@"
|
||||
}
|
||||
},
|
||||
"fr" : {
|
||||
"stringUnit" : {
|
||||
"state" : "translated",
|
||||
"value" : "a reposté votre publication : %@"
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"push.status" : {
|
||||
"comment" : "PUSH NOTIFICATION \\w BODY",
|
||||
"localizations" : {
|
||||
"en" : {
|
||||
"stringUnit" : {
|
||||
"state" : "translated",
|
||||
"value" : "posted: %@"
|
||||
}
|
||||
},
|
||||
"fr" : {
|
||||
"stringUnit" : {
|
||||
"state" : "translated",
|
||||
"value" : "a posté : %@"
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"push.update" : {
|
||||
"comment" : "PUSH NOTIFICATION",
|
||||
"localizations" : {
|
||||
"en" : {
|
||||
"stringUnit" : {
|
||||
"state" : "translated",
|
||||
"value" : "Update? Idk…"
|
||||
}
|
||||
},
|
||||
"fr" : {
|
||||
"stringUnit" : {
|
||||
"state" : "translated",
|
||||
"value" : "Mise à jour ? Jsp…"
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"restricted.blocked-domain" : {
|
||||
"extractionState" : "manual",
|
||||
"localizations" : {
|
||||
@ -5337,4 +5473,4 @@
|
||||
}
|
||||
},
|
||||
"version" : "1.0"
|
||||
}
|
||||
}
|
||||
|
Loading…
x
Reference in New Issue
Block a user