Line data Source code
1 : /*
2 : * Famedly Matrix SDK
3 : * Copyright (C) 2019, 2020, 2021 Famedly GmbH
4 : *
5 : * This program is free software: you can redistribute it and/or modify
6 : * it under the terms of the GNU Affero General Public License as
7 : * published by the Free Software Foundation, either version 3 of the
8 : * License, or (at your option) any later version.
9 : *
10 : * This program is distributed in the hope that it will be useful,
11 : * but WITHOUT ANY WARRANTY; without even the implied warranty of
12 : * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
13 : * GNU Affero General Public License for more details.
14 : *
15 : * You should have received a copy of the GNU Affero General Public License
16 : * along with this program. If not, see <https://www.gnu.org/licenses/>.
17 : */
18 :
19 : import 'package:collection/collection.dart';
20 :
21 : import 'package:matrix/encryption.dart';
22 : import 'package:matrix/matrix.dart';
23 :
24 : abstract class EventLocalizations {
25 : // As we need to create the localized body off of a different set of parameters, we
26 : // might create it with `event.plaintextBody`, maybe with `event.body`, maybe with the
27 : // reply fallback stripped, and maybe with the new body in `event.content['m.new_content']`.
28 : // Thus, it seems easier to offload that logic into `Event.getLocalizedBody()` and pass the
29 : // `body` variable around here.
30 2 : static String _localizedBodyNormalMessage(
31 : Event event, MatrixLocalizations i18n, String body) {
32 2 : switch (event.messageType) {
33 2 : case MessageTypes.Image:
34 2 : return i18n.sentAPicture(
35 4 : event.senderFromMemoryOrFallback.calcDisplayname(i18n: i18n));
36 2 : case MessageTypes.File:
37 2 : return i18n.sentAFile(
38 4 : event.senderFromMemoryOrFallback.calcDisplayname(i18n: i18n));
39 2 : case MessageTypes.Audio:
40 2 : return i18n.sentAnAudio(
41 4 : event.senderFromMemoryOrFallback.calcDisplayname(i18n: i18n));
42 2 : case MessageTypes.Video:
43 2 : return i18n.sentAVideo(
44 4 : event.senderFromMemoryOrFallback.calcDisplayname(i18n: i18n));
45 2 : case MessageTypes.Location:
46 2 : return i18n.sharedTheLocation(
47 4 : event.senderFromMemoryOrFallback.calcDisplayname(i18n: i18n));
48 2 : case MessageTypes.Sticker:
49 0 : return i18n.sentASticker(
50 0 : event.senderFromMemoryOrFallback.calcDisplayname(i18n: i18n));
51 2 : case MessageTypes.Emote:
52 2 : return '* $body';
53 2 : case EventTypes.KeyVerificationRequest:
54 2 : return i18n.requestedKeyVerification(
55 4 : event.senderFromMemoryOrFallback.calcDisplayname(i18n: i18n));
56 2 : case EventTypes.KeyVerificationCancel:
57 2 : return i18n.canceledKeyVerification(
58 4 : event.senderFromMemoryOrFallback.calcDisplayname(i18n: i18n));
59 2 : case EventTypes.KeyVerificationDone:
60 2 : return i18n.completedKeyVerification(
61 4 : event.senderFromMemoryOrFallback.calcDisplayname(i18n: i18n));
62 2 : case EventTypes.KeyVerificationReady:
63 2 : return i18n.isReadyForKeyVerification(
64 4 : event.senderFromMemoryOrFallback.calcDisplayname(i18n: i18n));
65 2 : case EventTypes.KeyVerificationAccept:
66 2 : return i18n.acceptedKeyVerification(
67 4 : event.senderFromMemoryOrFallback.calcDisplayname(i18n: i18n));
68 2 : case EventTypes.KeyVerificationStart:
69 2 : return i18n.startedKeyVerification(
70 4 : event.senderFromMemoryOrFallback.calcDisplayname(i18n: i18n));
71 2 : case MessageTypes.BadEncrypted:
72 : String errorText;
73 0 : switch (event.body) {
74 0 : case DecryptException.channelCorrupted:
75 0 : errorText = '${i18n.channelCorruptedDecryptError}.';
76 : break;
77 0 : case DecryptException.notEnabled:
78 0 : errorText = '${i18n.encryptionNotEnabled}.';
79 : break;
80 0 : case DecryptException.unknownAlgorithm:
81 0 : errorText = '${i18n.unknownEncryptionAlgorithm}.';
82 : break;
83 0 : case DecryptException.unknownSession:
84 0 : errorText = '${i18n.noPermission}.';
85 : break;
86 : default:
87 : errorText = body;
88 : break;
89 : }
90 0 : return i18n.couldNotDecryptMessage(errorText);
91 : case MessageTypes.Text:
92 : case MessageTypes.Notice:
93 : case MessageTypes.None:
94 : default:
95 : return body;
96 : }
97 : }
98 :
99 : // This map holds how to localize event types, and thus which event types exist.
100 : // If an event exists but it does not have a localized body, set its callback to null
101 : static final Map<String,
102 : String Function(Event event, MatrixLocalizations i18n, String body)?>
103 6 : localizationsMap = {
104 4 : EventTypes.Sticker: (event, i18n, body) => i18n.sentASticker(
105 4 : event.senderFromMemoryOrFallback.calcDisplayname(i18n: i18n)),
106 4 : EventTypes.Redaction: (event, i18n, body) => i18n.redactedAnEvent(event),
107 4 : EventTypes.RoomAliases: (event, i18n, body) => i18n.changedTheRoomAliases(
108 4 : event.senderFromMemoryOrFallback.calcDisplayname(i18n: i18n)),
109 2 : EventTypes.RoomCanonicalAlias: (event, i18n, body) =>
110 2 : i18n.changedTheRoomInvitationLink(
111 4 : event.senderFromMemoryOrFallback.calcDisplayname(i18n: i18n)),
112 4 : EventTypes.RoomCreate: (event, i18n, body) => i18n.createdTheChat(
113 4 : event.senderFromMemoryOrFallback.calcDisplayname(i18n: i18n)),
114 4 : EventTypes.RoomTombstone: (event, i18n, body) => i18n.roomHasBeenUpgraded,
115 2 : EventTypes.RoomJoinRules: (event, i18n, body) {
116 4 : final joinRules = JoinRules.values.firstWhereOrNull((r) =>
117 6 : r.toString().replaceAll('JoinRules.', '') ==
118 4 : event.content['join_rule']);
119 : if (joinRules == null) {
120 0 : return i18n.changedTheJoinRules(
121 0 : event.senderFromMemoryOrFallback.calcDisplayname(i18n: i18n));
122 : } else {
123 2 : return i18n.changedTheJoinRulesTo(
124 4 : event.senderFromMemoryOrFallback.calcDisplayname(i18n: i18n),
125 2 : joinRules.getLocalizedString(i18n));
126 : }
127 : },
128 2 : EventTypes.RoomMember: (event, i18n, body) {
129 4 : final targetName = event.stateKeyUser?.calcDisplayname(i18n: i18n) ?? '';
130 : final senderName =
131 4 : event.senderFromMemoryOrFallback.calcDisplayname(i18n: i18n);
132 10 : final userIsTarget = event.stateKey == event.room.client.userID;
133 10 : final userIsSender = event.senderId == event.room.client.userID;
134 :
135 2 : switch (event.roomMemberChangeType) {
136 2 : case RoomMemberChangeType.avatar:
137 0 : return i18n.changedTheProfileAvatar(targetName);
138 2 : case RoomMemberChangeType.displayname:
139 : final newDisplayname =
140 0 : event.content.tryGet<String>('displayname') ?? '';
141 : final oldDisplayname =
142 0 : event.prevContent?.tryGet<String>('displayname') ?? '';
143 0 : return i18n.changedTheDisplaynameTo(oldDisplayname, newDisplayname);
144 2 : case RoomMemberChangeType.join:
145 : return userIsTarget
146 0 : ? i18n.youJoinedTheChat
147 2 : : i18n.joinedTheChat(targetName);
148 2 : case RoomMemberChangeType.acceptInvite:
149 : return userIsTarget
150 0 : ? i18n.youAcceptedTheInvitation
151 2 : : i18n.acceptedTheInvitation(targetName);
152 2 : case RoomMemberChangeType.rejectInvite:
153 : return userIsTarget
154 0 : ? i18n.youRejectedTheInvitation
155 2 : : i18n.rejectedTheInvitation(targetName);
156 2 : case RoomMemberChangeType.withdrawInvitation:
157 : return userIsSender
158 0 : ? i18n.youHaveWithdrawnTheInvitationFor(targetName)
159 2 : : i18n.hasWithdrawnTheInvitationFor(senderName, targetName);
160 2 : case RoomMemberChangeType.leave:
161 0 : return i18n.userLeftTheChat(targetName);
162 2 : case RoomMemberChangeType.kick:
163 : return userIsSender
164 0 : ? i18n.youKicked(targetName)
165 2 : : i18n.kicked(senderName, targetName);
166 2 : case RoomMemberChangeType.invite:
167 : return userIsSender
168 0 : ? i18n.youInvitedUser(targetName)
169 : : userIsTarget
170 0 : ? i18n.youInvitedBy(senderName)
171 2 : : i18n.invitedUser(senderName, targetName);
172 2 : case RoomMemberChangeType.ban:
173 : return userIsSender
174 0 : ? i18n.youBannedUser(targetName)
175 2 : : i18n.bannedUser(senderName, targetName);
176 0 : case RoomMemberChangeType.unban:
177 : return userIsSender
178 0 : ? i18n.youUnbannedUser(targetName)
179 0 : : i18n.unbannedUser(senderName, targetName);
180 0 : case RoomMemberChangeType.knock:
181 0 : return i18n.hasKnocked(targetName);
182 : case RoomMemberChangeType.other:
183 : default:
184 : return userIsTarget
185 0 : ? i18n.youJoinedTheChat
186 0 : : i18n.joinedTheChat(targetName);
187 : }
188 : },
189 2 : EventTypes.RoomPowerLevels: (event, i18n, body) =>
190 2 : i18n.changedTheChatPermissions(
191 4 : event.senderFromMemoryOrFallback.calcDisplayname(i18n: i18n)),
192 4 : EventTypes.RoomName: (event, i18n, body) => i18n.changedTheChatNameTo(
193 4 : event.senderFromMemoryOrFallback.calcDisplayname(i18n: i18n),
194 4 : event.content.tryGet<String>('name') ?? ''),
195 2 : EventTypes.RoomTopic: (event, i18n, body) =>
196 2 : i18n.changedTheChatDescriptionTo(
197 4 : event.senderFromMemoryOrFallback.calcDisplayname(i18n: i18n),
198 4 : event.content.tryGet<String>('topic') ?? ''),
199 4 : EventTypes.RoomAvatar: (event, i18n, body) => i18n.changedTheChatAvatar(
200 4 : event.senderFromMemoryOrFallback.calcDisplayname(i18n: i18n)),
201 0 : EventTypes.GuestAccess: (event, i18n, body) {
202 0 : final guestAccess = GuestAccess.values.firstWhereOrNull((r) =>
203 0 : r.toString().replaceAll('GuestAccess.', '') ==
204 0 : event.content['guest_access']);
205 : if (guestAccess == null) {
206 0 : return i18n.changedTheGuestAccessRules(
207 0 : event.senderFromMemoryOrFallback.calcDisplayname(i18n: i18n));
208 : } else {
209 0 : return i18n.changedTheGuestAccessRulesTo(
210 0 : event.senderFromMemoryOrFallback.calcDisplayname(i18n: i18n),
211 0 : guestAccess.getLocalizedString(i18n));
212 : }
213 : },
214 2 : EventTypes.HistoryVisibility: (event, i18n, body) {
215 4 : final historyVisibility = HistoryVisibility.values.firstWhereOrNull((r) =>
216 6 : r.toString().replaceAll('HistoryVisibility.', '') ==
217 4 : event.content['history_visibility']);
218 : if (historyVisibility == null) {
219 0 : return i18n.changedTheHistoryVisibility(
220 0 : event.senderFromMemoryOrFallback.calcDisplayname(i18n: i18n));
221 : } else {
222 2 : return i18n.changedTheHistoryVisibilityTo(
223 4 : event.senderFromMemoryOrFallback.calcDisplayname(i18n: i18n),
224 2 : historyVisibility.getLocalizedString(i18n));
225 : }
226 : },
227 2 : EventTypes.Encryption: (event, i18n, body) {
228 2 : var localizedBody = i18n.activatedEndToEndEncryption(
229 4 : event.senderFromMemoryOrFallback.calcDisplayname(i18n: i18n));
230 8 : if (event.room.client.encryptionEnabled == false) {
231 6 : localizedBody += '. ${i18n.needPantalaimonWarning}';
232 : }
233 : return localizedBody;
234 : },
235 0 : EventTypes.CallAnswer: (event, i18n, body) => i18n.answeredTheCall(
236 0 : event.senderFromMemoryOrFallback.calcDisplayname(i18n: i18n)),
237 0 : EventTypes.CallHangup: (event, i18n, body) => i18n.endedTheCall(
238 0 : event.senderFromMemoryOrFallback.calcDisplayname(i18n: i18n)),
239 0 : EventTypes.CallInvite: (event, i18n, body) => i18n.startedACall(
240 0 : event.senderFromMemoryOrFallback.calcDisplayname(i18n: i18n)),
241 0 : EventTypes.CallCandidates: (event, i18n, body) => i18n.sentCallInformations(
242 0 : event.senderFromMemoryOrFallback.calcDisplayname(i18n: i18n)),
243 0 : EventTypes.Encrypted: (event, i18n, body) =>
244 0 : _localizedBodyNormalMessage(event, i18n, body),
245 2 : EventTypes.Message: (event, i18n, body) =>
246 2 : _localizedBodyNormalMessage(event, i18n, body),
247 0 : EventTypes.Reaction: (event, i18n, body) => i18n.sentReaction(
248 0 : event.senderFromMemoryOrFallback.calcDisplayname(i18n: i18n),
249 0 : event.content
250 0 : .tryGetMap<String, Object?>('m.relates_to')
251 0 : ?.tryGet<String>('key') ??
252 : body,
253 : ),
254 : };
255 : }
|