/*
* Famedly Matrix SDK
* Copyright (C) 2019, 2020, 2021 Famedly GmbH
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU Affero General Public License as
* published by the Free Software Foundation, either version 3 of the
* License, or (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Affero General Public License for more details.
*
* You should have received a copy of the GNU Affero General Public License
* along with this program. If not, see .
*/
import 'package:collection/collection.dart';
import 'package:matrix/encryption.dart';
import 'package:matrix/matrix.dart';
abstract class EventLocalizations {
// As we need to create the localized body off of a different set of parameters, we
// might create it with `event.plaintextBody`, maybe with `event.body`, maybe with the
// reply fallback stripped, and maybe with the new body in `event.content['m.new_content']`.
// Thus, it seems easier to offload that logic into `Event.getLocalizedBody()` and pass the
// `body` variable around here.
static String _localizedBodyNormalMessage(
Event event,
MatrixLocalizations i18n,
String body,
) {
switch (event.messageType) {
case MessageTypes.Image:
return i18n.sentAPicture(
event.senderFromMemoryOrFallback.calcDisplayname(i18n: i18n),
);
case MessageTypes.File:
return i18n.sentAFile(
event.senderFromMemoryOrFallback.calcDisplayname(i18n: i18n),
);
case MessageTypes.Audio:
if (event.content.tryGetMap('org.matrix.msc3245.voice') != null) {
final durationInt = event.content
.tryGetMap('info')
?.tryGet('duration');
return i18n.voiceMessage(
event.senderFromMemoryOrFallback.calcDisplayname(i18n: i18n),
durationInt == null ? null : Duration(milliseconds: durationInt),
);
}
return i18n.sentAnAudio(
event.senderFromMemoryOrFallback.calcDisplayname(i18n: i18n),
);
case MessageTypes.Video:
return i18n.sentAVideo(
event.senderFromMemoryOrFallback.calcDisplayname(i18n: i18n),
);
case MessageTypes.Location:
return i18n.sharedTheLocation(
event.senderFromMemoryOrFallback.calcDisplayname(i18n: i18n),
);
case MessageTypes.Sticker:
return i18n.sentASticker(
event.senderFromMemoryOrFallback.calcDisplayname(i18n: i18n),
);
case MessageTypes.Emote:
return '* $body';
case EventTypes.KeyVerificationRequest:
return i18n.requestedKeyVerification(
event.senderFromMemoryOrFallback.calcDisplayname(i18n: i18n),
);
case EventTypes.KeyVerificationCancel:
return i18n.canceledKeyVerification(
event.senderFromMemoryOrFallback.calcDisplayname(i18n: i18n),
);
case EventTypes.KeyVerificationDone:
return i18n.completedKeyVerification(
event.senderFromMemoryOrFallback.calcDisplayname(i18n: i18n),
);
case EventTypes.KeyVerificationReady:
return i18n.isReadyForKeyVerification(
event.senderFromMemoryOrFallback.calcDisplayname(i18n: i18n),
);
case EventTypes.KeyVerificationAccept:
return i18n.acceptedKeyVerification(
event.senderFromMemoryOrFallback.calcDisplayname(i18n: i18n),
);
case EventTypes.KeyVerificationStart:
return i18n.startedKeyVerification(
event.senderFromMemoryOrFallback.calcDisplayname(i18n: i18n),
);
case MessageTypes.BadEncrypted:
String errorText;
switch (event.body) {
case DecryptException.channelCorrupted:
errorText = '${i18n.channelCorruptedDecryptError}.';
break;
case DecryptException.notEnabled:
errorText = '${i18n.encryptionNotEnabled}.';
break;
case DecryptException.unknownAlgorithm:
errorText = '${i18n.unknownEncryptionAlgorithm}.';
break;
case DecryptException.unknownSession:
errorText = '${i18n.noPermission}.';
break;
default:
errorText = body;
break;
}
return i18n.couldNotDecryptMessage(errorText);
case MessageTypes.Text:
case MessageTypes.Notice:
case MessageTypes.None:
default:
return body;
}
}
// This map holds how to localize event types, and thus which event types exist.
// If an event exists but it does not have a localized body, set its callback to null
static final Map
localizationsMap = {
EventTypes.Sticker: (event, i18n, body) => i18n.sentASticker(
event.senderFromMemoryOrFallback.calcDisplayname(i18n: i18n),
),
EventTypes.Redaction: (event, i18n, body) => i18n.redactedAnEvent(event),
EventTypes.RoomAliases: (event, i18n, body) => i18n.changedTheRoomAliases(
event.senderFromMemoryOrFallback.calcDisplayname(i18n: i18n),
),
EventTypes.RoomCanonicalAlias: (event, i18n, body) =>
i18n.changedTheRoomInvitationLink(
event.senderFromMemoryOrFallback.calcDisplayname(i18n: i18n),
),
EventTypes.RoomCreate: (event, i18n, body) => i18n.createdTheChat(
event.senderFromMemoryOrFallback.calcDisplayname(i18n: i18n),
),
EventTypes.RoomTombstone: (event, i18n, body) => i18n.roomHasBeenUpgraded,
EventTypes.RoomJoinRules: (event, i18n, body) {
final joinRules = JoinRules.values.firstWhereOrNull(
(r) =>
r.toString().replaceAll('JoinRules.', '') ==
event.content['join_rule'],
);
if (joinRules == null) {
return i18n.changedTheJoinRules(
event.senderFromMemoryOrFallback.calcDisplayname(i18n: i18n),
);
} else {
return i18n.changedTheJoinRulesTo(
event.senderFromMemoryOrFallback.calcDisplayname(i18n: i18n),
joinRules.getLocalizedString(i18n),
);
}
},
EventTypes.RoomMember: (event, i18n, body) {
final targetName = event.stateKeyUser?.calcDisplayname(i18n: i18n) ?? '';
final senderName =
event.senderFromMemoryOrFallback.calcDisplayname(i18n: i18n);
final userIsTarget = event.stateKey == event.room.client.userID;
final userIsSender = event.senderId == event.room.client.userID;
switch (event.roomMemberChangeType) {
case RoomMemberChangeType.avatar:
return i18n.changedTheProfileAvatar(targetName);
case RoomMemberChangeType.displayname:
final newDisplayname =
event.content.tryGet('displayname') ?? '';
final oldDisplayname =
event.prevContent?.tryGet('displayname') ?? '';
return i18n.changedTheDisplaynameTo(oldDisplayname, newDisplayname);
case RoomMemberChangeType.join:
return userIsTarget
? i18n.youJoinedTheChat
: i18n.joinedTheChat(targetName);
case RoomMemberChangeType.acceptInvite:
return userIsTarget
? i18n.youAcceptedTheInvitation
: i18n.acceptedTheInvitation(targetName);
case RoomMemberChangeType.rejectInvite:
return userIsTarget
? i18n.youRejectedTheInvitation
: i18n.rejectedTheInvitation(targetName);
case RoomMemberChangeType.withdrawInvitation:
return userIsSender
? i18n.youHaveWithdrawnTheInvitationFor(targetName)
: i18n.hasWithdrawnTheInvitationFor(senderName, targetName);
case RoomMemberChangeType.leave:
return i18n.userLeftTheChat(targetName);
case RoomMemberChangeType.kick:
return userIsSender
? i18n.youKicked(targetName)
: i18n.kicked(senderName, targetName);
case RoomMemberChangeType.invite:
return userIsSender
? i18n.youInvitedUser(targetName)
: userIsTarget
? i18n.youInvitedBy(senderName)
: i18n.invitedUser(senderName, targetName);
case RoomMemberChangeType.ban:
return userIsSender
? i18n.youBannedUser(targetName)
: i18n.bannedUser(senderName, targetName);
case RoomMemberChangeType.unban:
return userIsSender
? i18n.youUnbannedUser(targetName)
: i18n.unbannedUser(senderName, targetName);
case RoomMemberChangeType.knock:
return i18n.hasKnocked(targetName);
case RoomMemberChangeType.other:
return userIsTarget
? i18n.youJoinedTheChat
: i18n.joinedTheChat(targetName);
}
},
EventTypes.RoomPowerLevels: (event, i18n, body) =>
i18n.changedTheChatPermissions(
event.senderFromMemoryOrFallback.calcDisplayname(i18n: i18n),
),
EventTypes.RoomName: (event, i18n, body) => i18n.changedTheChatNameTo(
event.senderFromMemoryOrFallback.calcDisplayname(i18n: i18n),
event.content.tryGet('name') ?? '',
),
EventTypes.RoomTopic: (event, i18n, body) =>
i18n.changedTheChatDescriptionTo(
event.senderFromMemoryOrFallback.calcDisplayname(i18n: i18n),
event.content.tryGet('topic') ?? '',
),
EventTypes.RoomAvatar: (event, i18n, body) => i18n.changedTheChatAvatar(
event.senderFromMemoryOrFallback.calcDisplayname(i18n: i18n),
),
EventTypes.GuestAccess: (event, i18n, body) {
final guestAccess = GuestAccess.values.firstWhereOrNull(
(r) =>
r.toString().replaceAll('GuestAccess.', '') ==
event.content['guest_access'],
);
if (guestAccess == null) {
return i18n.changedTheGuestAccessRules(
event.senderFromMemoryOrFallback.calcDisplayname(i18n: i18n),
);
} else {
return i18n.changedTheGuestAccessRulesTo(
event.senderFromMemoryOrFallback.calcDisplayname(i18n: i18n),
guestAccess.getLocalizedString(i18n),
);
}
},
EventTypes.HistoryVisibility: (event, i18n, body) {
final historyVisibility = HistoryVisibility.values.firstWhereOrNull(
(r) =>
r.toString().replaceAll('HistoryVisibility.', '') ==
event.content['history_visibility'],
);
if (historyVisibility == null) {
return i18n.changedTheHistoryVisibility(
event.senderFromMemoryOrFallback.calcDisplayname(i18n: i18n),
);
} else {
return i18n.changedTheHistoryVisibilityTo(
event.senderFromMemoryOrFallback.calcDisplayname(i18n: i18n),
historyVisibility.getLocalizedString(i18n),
);
}
},
EventTypes.Encryption: (event, i18n, body) {
var localizedBody = i18n.activatedEndToEndEncryption(
event.senderFromMemoryOrFallback.calcDisplayname(i18n: i18n),
);
if (event.room.client.encryptionEnabled == false) {
localizedBody += '. ${i18n.needPantalaimonWarning}';
}
return localizedBody;
},
EventTypes.CallAnswer: (event, i18n, body) => i18n.answeredTheCall(
event.senderFromMemoryOrFallback.calcDisplayname(i18n: i18n),
),
EventTypes.CallHangup: (event, i18n, body) => i18n.endedTheCall(
event.senderFromMemoryOrFallback.calcDisplayname(i18n: i18n),
),
EventTypes.CallInvite: (event, i18n, body) => i18n.startedACall(
event.senderFromMemoryOrFallback.calcDisplayname(i18n: i18n),
),
EventTypes.CallCandidates: (event, i18n, body) => i18n.sentCallInformations(
event.senderFromMemoryOrFallback.calcDisplayname(i18n: i18n),
),
EventTypes.Encrypted: (event, i18n, body) =>
_localizedBodyNormalMessage(event, i18n, body),
EventTypes.Message: (event, i18n, body) =>
_localizedBodyNormalMessage(event, i18n, body),
EventTypes.Reaction: (event, i18n, body) => i18n.sentReaction(
event.senderFromMemoryOrFallback.calcDisplayname(i18n: i18n),
event.content
.tryGetMap('m.relates_to')
?.tryGet('key') ??
body,
),
};
}