mirror of
https://gitlab.opencode.de/bwi/bundesmessenger/clients/bundesmessenger-ios.git
synced 2026-05-20 14:42:09 +02:00
Merge pull request #6000 from vector-im/doug/5996_nse_strings
Make sure strings fall back to English if missing in the Notification extension
This commit is contained in:
@@ -19,7 +19,7 @@ import Foundation
|
||||
public extension Bundle {
|
||||
/// Returns the real app bundle.
|
||||
/// Can also be used in app extensions.
|
||||
static var app: Bundle {
|
||||
@objc static var app: Bundle {
|
||||
let bundle = main
|
||||
if bundle.bundleURL.pathExtension == "appex" {
|
||||
// Peel off two directory levels - MY_APP.app/PlugIns/MY_APP_EXTENSION.appex
|
||||
@@ -31,6 +31,14 @@ public extension Bundle {
|
||||
return bundle
|
||||
}
|
||||
|
||||
/// Get an lproj language bundle from the main app bundle.
|
||||
/// - Parameter language: The language to try to load.
|
||||
/// - Returns: The lproj bundle if found otherwise `nil`.
|
||||
@objc static func lprojBundle(for language: String) -> Bundle? {
|
||||
guard let lprojURL = Bundle.app.url(forResource: language, withExtension: "lproj") else { return nil }
|
||||
return Bundle(url: lprojURL)
|
||||
}
|
||||
|
||||
/// Whether or not the bundle is the RiotShareExtension.
|
||||
var isShareExtension: Bool {
|
||||
bundleURL.lastPathComponent.contains("RiotShareExtension.appex")
|
||||
|
||||
@@ -8006,7 +8006,7 @@ public class VectorL10n: NSObject {
|
||||
|
||||
extension VectorL10n {
|
||||
static func tr(_ table: String, _ key: String, _ args: CVarArg...) -> String {
|
||||
let format = NSLocalizedString(key, tableName: table, bundle: Bundle(for: BundleToken.self), comment: "")
|
||||
let format = NSLocalizedString(key, tableName: table, bundle: Bundle.app, comment: "")
|
||||
let locale: Locale
|
||||
if let providedLocale = LocaleProvider.locale {
|
||||
locale = providedLocale
|
||||
@@ -8018,4 +8018,3 @@ extension VectorL10n {
|
||||
}
|
||||
}
|
||||
|
||||
private final class BundleToken {}
|
||||
|
||||
@@ -15,6 +15,7 @@
|
||||
*/
|
||||
|
||||
#import "NSBundle+MXKLanguage.h"
|
||||
#import "GeneratedInterface-Swift.h"
|
||||
|
||||
#import <objc/runtime.h>
|
||||
|
||||
@@ -55,37 +56,37 @@ static const char _fallbackLanguage = 0;
|
||||
[self setupMXKLanguageBundle];
|
||||
|
||||
// [NSBundle localizedStringForKey] calls will be redirected to the bundle corresponding
|
||||
// to "language"
|
||||
objc_setAssociatedObject([NSBundle mainBundle],
|
||||
&_bundle, language ? [NSBundle bundleWithPath:[[NSBundle mainBundle] pathForResource:language ofType:@"lproj"]] : nil,
|
||||
// to "language". `lprojBundleFor` loads this from the main app bundle as we might be running in an extension.
|
||||
objc_setAssociatedObject(NSBundle.app,
|
||||
&_bundle, language ? [NSBundle lprojBundleFor:language] : nil,
|
||||
OBJC_ASSOCIATION_RETAIN_NONATOMIC);
|
||||
|
||||
objc_setAssociatedObject([NSBundle mainBundle],
|
||||
objc_setAssociatedObject(NSBundle.app,
|
||||
&_language, language,
|
||||
OBJC_ASSOCIATION_RETAIN_NONATOMIC);
|
||||
}
|
||||
|
||||
+ (NSString *)mxk_language
|
||||
{
|
||||
return objc_getAssociatedObject([NSBundle mainBundle], &_language);
|
||||
return objc_getAssociatedObject(NSBundle.app, &_language);
|
||||
}
|
||||
|
||||
+ (void)mxk_setFallbackLanguage:(NSString *)language
|
||||
{
|
||||
[self setupMXKLanguageBundle];
|
||||
|
||||
objc_setAssociatedObject([NSBundle mainBundle],
|
||||
&_fallbackBundle, language ? [NSBundle bundleWithPath:[[NSBundle mainBundle] pathForResource:language ofType:@"lproj"]] : nil,
|
||||
objc_setAssociatedObject(NSBundle.app,
|
||||
&_fallbackBundle, language ? [NSBundle lprojBundleFor:language] : nil,
|
||||
OBJC_ASSOCIATION_RETAIN_NONATOMIC);
|
||||
|
||||
objc_setAssociatedObject([NSBundle mainBundle],
|
||||
objc_setAssociatedObject(NSBundle.app,
|
||||
&_fallbackLanguage, language,
|
||||
OBJC_ASSOCIATION_RETAIN_NONATOMIC);
|
||||
}
|
||||
|
||||
+ (NSString *)mxk_fallbackLanguage
|
||||
{
|
||||
return objc_getAssociatedObject([NSBundle mainBundle], &_fallbackLanguage);
|
||||
return objc_getAssociatedObject(NSBundle.app, &_fallbackLanguage);
|
||||
}
|
||||
|
||||
#pragma mark - Private methods
|
||||
@@ -96,7 +97,7 @@ static const char _fallbackLanguage = 0;
|
||||
dispatch_once(&onceToken, ^{
|
||||
|
||||
// Use MXKLanguageBundle as the [NSBundle mainBundle] class
|
||||
object_setClass([NSBundle mainBundle], [MXKLanguageBundle class]);
|
||||
object_setClass(NSBundle.app, MXKLanguageBundle.class);
|
||||
});
|
||||
}
|
||||
|
||||
|
||||
@@ -74,6 +74,17 @@ class NotificationService: UNNotificationServiceExtension {
|
||||
private static let backgroundServiceInitQueue = DispatchQueue(label: "io.element.NotificationService.backgroundServiceInitQueue")
|
||||
// MARK: - Method Overrides
|
||||
|
||||
override init() {
|
||||
super.init()
|
||||
|
||||
// Set up runtime language and fallback by considering the userDefaults object shared within the application group.
|
||||
let sharedUserDefaults = MXKAppSettings.standard().sharedUserDefaults
|
||||
if let language = sharedUserDefaults?.string(forKey: "appLanguage") {
|
||||
Bundle.mxk_setLanguage(language)
|
||||
}
|
||||
Bundle.mxk_setFallbackLanguage("en")
|
||||
}
|
||||
|
||||
override func didReceive(_ request: UNNotificationRequest, withContentHandler contentHandler: @escaping (UNNotificationContent) -> Void) {
|
||||
let userInfo = request.content.userInfo
|
||||
|
||||
@@ -341,9 +352,9 @@ class NotificationService: UNNotificationServiceExtension {
|
||||
let isVideoCall = sdp?.contains("m=video") ?? false
|
||||
|
||||
if isVideoCall {
|
||||
notificationBody = NSString.localizedUserNotificationString(forKey: "VIDEO_CALL_FROM_USER", arguments: [eventSenderName as Any])
|
||||
notificationBody = NotificationService.localizedString(forKey: "VIDEO_CALL_FROM_USER", eventSenderName)
|
||||
} else {
|
||||
notificationBody = NSString.localizedUserNotificationString(forKey: "VOICE_CALL_FROM_USER", arguments: [eventSenderName as Any])
|
||||
notificationBody = NotificationService.localizedString(forKey: "VOICE_CALL_FROM_USER", eventSenderName)
|
||||
}
|
||||
|
||||
// call notifications should stand out from normal messages, so we don't stack them
|
||||
@@ -405,7 +416,7 @@ class NotificationService: UNNotificationServiceExtension {
|
||||
}
|
||||
|
||||
let msgType = event.content[kMXMessageTypeKey] as? String
|
||||
let messageContent = event.content[kMXMessageBodyKey] as? String
|
||||
let messageContent = event.content[kMXMessageBodyKey] as? String ?? ""
|
||||
let isReply = event.isReply()
|
||||
|
||||
if isReply {
|
||||
@@ -416,30 +427,30 @@ class NotificationService: UNNotificationServiceExtension {
|
||||
|
||||
if event.isEncrypted && !self.showDecryptedContentInNotifications {
|
||||
// Hide the content
|
||||
notificationBody = NSString.localizedUserNotificationString(forKey: "MESSAGE", arguments: [])
|
||||
notificationBody = NotificationService.localizedString(forKey: "MESSAGE")
|
||||
break
|
||||
}
|
||||
|
||||
if event.location != nil {
|
||||
notificationBody = NSString.localizedUserNotificationString(forKey: "LOCATION_FROM_USER", arguments: [eventSenderName])
|
||||
notificationBody = NotificationService.localizedString(forKey: "LOCATION_FROM_USER", eventSenderName)
|
||||
break
|
||||
}
|
||||
|
||||
switch msgType {
|
||||
case kMXMessageTypeEmote:
|
||||
notificationBody = NSString.localizedUserNotificationString(forKey: "ACTION_FROM_USER", arguments: [eventSenderName, messageContent as Any])
|
||||
notificationBody = NotificationService.localizedString(forKey: "ACTION_FROM_USER", eventSenderName, messageContent)
|
||||
case kMXMessageTypeImage:
|
||||
notificationBody = NSString.localizedUserNotificationString(forKey: "PICTURE_FROM_USER", arguments: [eventSenderName])
|
||||
notificationBody = NotificationService.localizedString(forKey: "PICTURE_FROM_USER", eventSenderName)
|
||||
case kMXMessageTypeVideo:
|
||||
notificationBody = NSString.localizedUserNotificationString(forKey: "VIDEO_FROM_USER", arguments: [eventSenderName])
|
||||
notificationBody = NotificationService.localizedString(forKey: "VIDEO_FROM_USER", eventSenderName)
|
||||
case kMXMessageTypeAudio:
|
||||
if event.isVoiceMessage() {
|
||||
notificationBody = NSString.localizedUserNotificationString(forKey: "VOICE_MESSAGE_FROM_USER", arguments: [eventSenderName])
|
||||
notificationBody = NotificationService.localizedString(forKey: "VOICE_MESSAGE_FROM_USER", eventSenderName)
|
||||
} else {
|
||||
notificationBody = NSString.localizedUserNotificationString(forKey: "AUDIO_FROM_USER", arguments: [eventSenderName, messageContent as Any])
|
||||
notificationBody = NotificationService.localizedString(forKey: "AUDIO_FROM_USER", eventSenderName, messageContent)
|
||||
}
|
||||
case kMXMessageTypeFile:
|
||||
notificationBody = NSString.localizedUserNotificationString(forKey: "FILE_FROM_USER", arguments: [eventSenderName, messageContent as Any])
|
||||
notificationBody = NotificationService.localizedString(forKey: "FILE_FROM_USER", eventSenderName, messageContent)
|
||||
|
||||
// All other message types such as text, notice, server notice etc
|
||||
default:
|
||||
@@ -469,50 +480,50 @@ class NotificationService: UNNotificationServiceExtension {
|
||||
// If there was a change, use the sender's userID if one was blank and show the change.
|
||||
if let oldDisplayname = oldContent.displayname ?? event.sender,
|
||||
let displayname = newContent.displayname ?? event.sender {
|
||||
notificationBody = NSString.localizedUserNotificationString(forKey: "USER_UPDATED_DISPLAYNAME", arguments: [oldDisplayname, displayname])
|
||||
notificationBody = NotificationService.localizedString(forKey: "USER_UPDATED_DISPLAYNAME", oldDisplayname, displayname)
|
||||
} else {
|
||||
// Should never be reached as the event should always have a sender.
|
||||
notificationBody = NSString.localizedUserNotificationString(forKey: "GENERIC_USER_UPDATED_DISPLAYNAME", arguments: [eventSenderName])
|
||||
notificationBody = NotificationService.localizedString(forKey: "GENERIC_USER_UPDATED_DISPLAYNAME", eventSenderName)
|
||||
}
|
||||
} else {
|
||||
// If the display name hasn't changed, handle as an avatar change.
|
||||
notificationBody = NSString.localizedUserNotificationString(forKey: "USER_UPDATED_AVATAR", arguments: [eventSenderName])
|
||||
notificationBody = NotificationService.localizedString(forKey: "USER_UPDATED_AVATAR", eventSenderName)
|
||||
}
|
||||
} else {
|
||||
// No known reports of having reached this situation for a membership notification
|
||||
// So use a generic membership updated fallback.
|
||||
notificationBody = NSString.localizedUserNotificationString(forKey: "USER_MEMBERSHIP_UPDATED", arguments: [eventSenderName])
|
||||
notificationBody = NotificationService.localizedString(forKey: "USER_MEMBERSHIP_UPDATED", eventSenderName)
|
||||
}
|
||||
// Otherwise treat the notification as an invite.
|
||||
// This is the expected notification content for a membership event.
|
||||
} else {
|
||||
if roomDisplayName != nil && roomDisplayName != eventSenderName {
|
||||
notificationBody = NSString.localizedUserNotificationString(forKey: "USER_INVITE_TO_NAMED_ROOM", arguments: [eventSenderName, roomDisplayName as Any])
|
||||
if let roomDisplayName = roomDisplayName, roomDisplayName != eventSenderName {
|
||||
notificationBody = NotificationService.localizedString(forKey: "USER_INVITE_TO_NAMED_ROOM", eventSenderName, roomDisplayName)
|
||||
} else {
|
||||
notificationBody = NSString.localizedUserNotificationString(forKey: "USER_INVITE_TO_CHAT", arguments: [eventSenderName])
|
||||
notificationBody = NotificationService.localizedString(forKey: "USER_INVITE_TO_CHAT", eventSenderName)
|
||||
}
|
||||
}
|
||||
|
||||
case .sticker:
|
||||
notificationTitle = self.messageTitle(for: eventSenderName, in: roomDisplayName)
|
||||
notificationBody = NSString.localizedUserNotificationString(forKey: "STICKER_FROM_USER", arguments: [eventSenderName as Any])
|
||||
notificationBody = NotificationService.localizedString(forKey: "STICKER_FROM_USER", eventSenderName)
|
||||
|
||||
// Reactions are unexpected notification types, but have been seen in some circumstances.
|
||||
case .reaction:
|
||||
notificationTitle = self.messageTitle(for: eventSenderName, in: roomDisplayName)
|
||||
if let reactionKey = event.relatesTo?.key {
|
||||
// Try to show the reaction key in the notification.
|
||||
notificationBody = NSString.localizedUserNotificationString(forKey: "REACTION_FROM_USER", arguments: [eventSenderName, reactionKey])
|
||||
notificationBody = NotificationService.localizedString(forKey: "REACTION_FROM_USER", eventSenderName, reactionKey)
|
||||
} else {
|
||||
// Otherwise show a generic reaction.
|
||||
notificationBody = NSString.localizedUserNotificationString(forKey: "GENERIC_REACTION_FROM_USER", arguments: [eventSenderName])
|
||||
notificationBody = NotificationService.localizedString(forKey: "GENERIC_REACTION_FROM_USER", eventSenderName)
|
||||
}
|
||||
|
||||
case .custom:
|
||||
if (event.type == kWidgetMatrixEventTypeString || event.type == kWidgetModularEventTypeString),
|
||||
let type = event.content?["type"] as? String,
|
||||
(type == kWidgetTypeJitsiV1 || type == kWidgetTypeJitsiV2) {
|
||||
notificationBody = NSString.localizedUserNotificationString(forKey: "GROUP_CALL_STARTED", arguments: nil)
|
||||
notificationBody = NotificationService.localizedString(forKey: "GROUP_CALL_STARTED")
|
||||
notificationTitle = roomDisplayName
|
||||
|
||||
// call notifications should stand out from normal messages, so we don't stack them
|
||||
@@ -566,7 +577,7 @@ class NotificationService: UNNotificationServiceExtension {
|
||||
var validatedNotificationTitle: String? = notificationTitle
|
||||
if self.localAuthenticationService.isProtectionSet {
|
||||
MXLog.debug("[NotificationService] validateNotificationContentAndComplete: Resetting title and body because app protection is set")
|
||||
validatedNotificationBody = NSString.localizedUserNotificationString(forKey: "MESSAGE_PROTECTED", arguments: [])
|
||||
validatedNotificationBody = NotificationService.localizedString(forKey: "MESSAGE_PROTECTED")
|
||||
validatedNotificationTitle = nil
|
||||
}
|
||||
|
||||
@@ -596,7 +607,7 @@ class NotificationService: UNNotificationServiceExtension {
|
||||
private func messageTitle(for eventSenderName: String, in roomDisplayName: String?) -> String {
|
||||
// Display the room name only if it is different than the sender name
|
||||
if let roomDisplayName = roomDisplayName, roomDisplayName != eventSenderName {
|
||||
return NSString.localizedUserNotificationString(forKey: "MSG_FROM_USER_IN_ROOM_TITLE", arguments: [eventSenderName, roomDisplayName])
|
||||
return NotificationService.localizedString(forKey: "MSG_FROM_USER_IN_ROOM_TITLE", eventSenderName, roomDisplayName)
|
||||
} else {
|
||||
return eventSenderName
|
||||
}
|
||||
@@ -605,9 +616,9 @@ class NotificationService: UNNotificationServiceExtension {
|
||||
private func replyTitle(for eventSenderName: String, in roomDisplayName: String?) -> String {
|
||||
// Display the room name only if it is different than the sender name
|
||||
if let roomDisplayName = roomDisplayName, roomDisplayName != eventSenderName {
|
||||
return NSString.localizedUserNotificationString(forKey: "REPLY_FROM_USER_IN_ROOM_TITLE", arguments: [eventSenderName, roomDisplayName])
|
||||
return NotificationService.localizedString(forKey: "REPLY_FROM_USER_IN_ROOM_TITLE", eventSenderName, roomDisplayName)
|
||||
} else {
|
||||
return NSString.localizedUserNotificationString(forKey: "REPLY_FROM_USER_TITLE", arguments: [eventSenderName])
|
||||
return NotificationService.localizedString(forKey: "REPLY_FROM_USER_TITLE", eventSenderName)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -816,4 +827,13 @@ class NotificationService: UNNotificationServiceExtension {
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private static func localizedString(forKey key: String, _ args: CVarArg...) -> String {
|
||||
// The bundle needs to be an MXKLanguageBundle and contain the lproj files.
|
||||
// MatrixKit now sets the app bundle as the MXKLanguageBundle
|
||||
let format = NSLocalizedString(key, bundle: Bundle.app, comment: "")
|
||||
let locale = LocaleProvider.locale ?? Locale.current
|
||||
|
||||
return String(format: format, locale: locale, arguments: args)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -64,7 +64,7 @@ import Foundation
|
||||
|
||||
extension {{className}} {
|
||||
static func tr(_ table: String, _ key: String, _ args: CVarArg...) -> String {
|
||||
let format = NSLocalizedString(key, tableName: table, bundle: Bundle(for: BundleToken.self), comment: "")
|
||||
let format = NSLocalizedString(key, tableName: table, bundle: Bundle.app, comment: "")
|
||||
let locale: Locale
|
||||
|
||||
if let providedLocale = LocaleProvider.locale {
|
||||
@@ -77,7 +77,6 @@ extension {{className}} {
|
||||
}
|
||||
}
|
||||
|
||||
private final class BundleToken {}
|
||||
{% else %}
|
||||
// No string found
|
||||
{% endif %}
|
||||
|
||||
@@ -0,0 +1 @@
|
||||
Notifications: Strings now fall back to English if they're missing for the current language.
|
||||
Reference in New Issue
Block a user