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,
32 : MatrixLocalizations i18n,
33 : String body,
34 : ) {
35 2 : switch (event.messageType) {
36 2 : case MessageTypes.Image:
37 2 : return i18n.sentAPicture(
38 4 : event.senderFromMemoryOrFallback.calcDisplayname(i18n: i18n),
39 : );
40 2 : case MessageTypes.File:
41 2 : return i18n.sentAFile(
42 4 : event.senderFromMemoryOrFallback.calcDisplayname(i18n: i18n),
43 : );
44 2 : case MessageTypes.Audio:
45 2 : return i18n.sentAnAudio(
46 4 : event.senderFromMemoryOrFallback.calcDisplayname(i18n: i18n),
47 : );
48 2 : case MessageTypes.Video:
49 2 : return i18n.sentAVideo(
50 4 : event.senderFromMemoryOrFallback.calcDisplayname(i18n: i18n),
51 : );
52 2 : case MessageTypes.Location:
53 2 : return i18n.sharedTheLocation(
54 4 : event.senderFromMemoryOrFallback.calcDisplayname(i18n: i18n),
55 : );
56 2 : case MessageTypes.Sticker:
57 0 : return i18n.sentASticker(
58 0 : event.senderFromMemoryOrFallback.calcDisplayname(i18n: i18n),
59 : );
60 2 : case MessageTypes.Emote:
61 2 : return '* $body';
62 2 : case EventTypes.KeyVerificationRequest:
63 2 : return i18n.requestedKeyVerification(
64 4 : event.senderFromMemoryOrFallback.calcDisplayname(i18n: i18n),
65 : );
66 2 : case EventTypes.KeyVerificationCancel:
67 2 : return i18n.canceledKeyVerification(
68 4 : event.senderFromMemoryOrFallback.calcDisplayname(i18n: i18n),
69 : );
70 2 : case EventTypes.KeyVerificationDone:
71 2 : return i18n.completedKeyVerification(
72 4 : event.senderFromMemoryOrFallback.calcDisplayname(i18n: i18n),
73 : );
74 2 : case EventTypes.KeyVerificationReady:
75 2 : return i18n.isReadyForKeyVerification(
76 4 : event.senderFromMemoryOrFallback.calcDisplayname(i18n: i18n),
77 : );
78 2 : case EventTypes.KeyVerificationAccept:
79 2 : return i18n.acceptedKeyVerification(
80 4 : event.senderFromMemoryOrFallback.calcDisplayname(i18n: i18n),
81 : );
82 2 : case EventTypes.KeyVerificationStart:
83 2 : return i18n.startedKeyVerification(
84 4 : event.senderFromMemoryOrFallback.calcDisplayname(i18n: i18n),
85 : );
86 2 : case MessageTypes.BadEncrypted:
87 : String errorText;
88 0 : switch (event.body) {
89 0 : case DecryptException.channelCorrupted:
90 0 : errorText = '${i18n.channelCorruptedDecryptError}.';
91 : break;
92 0 : case DecryptException.notEnabled:
93 0 : errorText = '${i18n.encryptionNotEnabled}.';
94 : break;
95 0 : case DecryptException.unknownAlgorithm:
96 0 : errorText = '${i18n.unknownEncryptionAlgorithm}.';
97 : break;
98 0 : case DecryptException.unknownSession:
99 0 : errorText = '${i18n.noPermission}.';
100 : break;
101 : default:
102 : errorText = body;
103 : break;
104 : }
105 0 : return i18n.couldNotDecryptMessage(errorText);
106 : case MessageTypes.Text:
107 : case MessageTypes.Notice:
108 : case MessageTypes.None:
109 : default:
110 : return body;
111 : }
112 : }
113 :
114 : // This map holds how to localize event types, and thus which event types exist.
115 : // If an event exists but it does not have a localized body, set its callback to null
116 : static final Map<String,
117 : String Function(Event event, MatrixLocalizations i18n, String body)?>
118 6 : localizationsMap = {
119 4 : EventTypes.Sticker: (event, i18n, body) => i18n.sentASticker(
120 4 : event.senderFromMemoryOrFallback.calcDisplayname(i18n: i18n),
121 : ),
122 4 : EventTypes.Redaction: (event, i18n, body) => i18n.redactedAnEvent(event),
123 4 : EventTypes.RoomAliases: (event, i18n, body) => i18n.changedTheRoomAliases(
124 4 : event.senderFromMemoryOrFallback.calcDisplayname(i18n: i18n),
125 : ),
126 2 : EventTypes.RoomCanonicalAlias: (event, i18n, body) =>
127 2 : i18n.changedTheRoomInvitationLink(
128 4 : event.senderFromMemoryOrFallback.calcDisplayname(i18n: i18n),
129 : ),
130 4 : EventTypes.RoomCreate: (event, i18n, body) => i18n.createdTheChat(
131 4 : event.senderFromMemoryOrFallback.calcDisplayname(i18n: i18n),
132 : ),
133 4 : EventTypes.RoomTombstone: (event, i18n, body) => i18n.roomHasBeenUpgraded,
134 2 : EventTypes.RoomJoinRules: (event, i18n, body) {
135 2 : final joinRules = JoinRules.values.firstWhereOrNull(
136 2 : (r) =>
137 6 : r.toString().replaceAll('JoinRules.', '') ==
138 4 : event.content['join_rule'],
139 : );
140 : if (joinRules == null) {
141 0 : return i18n.changedTheJoinRules(
142 0 : event.senderFromMemoryOrFallback.calcDisplayname(i18n: i18n),
143 : );
144 : } else {
145 2 : return i18n.changedTheJoinRulesTo(
146 4 : event.senderFromMemoryOrFallback.calcDisplayname(i18n: i18n),
147 2 : joinRules.getLocalizedString(i18n),
148 : );
149 : }
150 : },
151 2 : EventTypes.RoomMember: (event, i18n, body) {
152 4 : final targetName = event.stateKeyUser?.calcDisplayname(i18n: i18n) ?? '';
153 : final senderName =
154 4 : event.senderFromMemoryOrFallback.calcDisplayname(i18n: i18n);
155 10 : final userIsTarget = event.stateKey == event.room.client.userID;
156 10 : final userIsSender = event.senderId == event.room.client.userID;
157 :
158 2 : switch (event.roomMemberChangeType) {
159 2 : case RoomMemberChangeType.avatar:
160 0 : return i18n.changedTheProfileAvatar(targetName);
161 2 : case RoomMemberChangeType.displayname:
162 : final newDisplayname =
163 0 : event.content.tryGet<String>('displayname') ?? '';
164 : final oldDisplayname =
165 0 : event.prevContent?.tryGet<String>('displayname') ?? '';
166 0 : return i18n.changedTheDisplaynameTo(oldDisplayname, newDisplayname);
167 2 : case RoomMemberChangeType.join:
168 : return userIsTarget
169 0 : ? i18n.youJoinedTheChat
170 2 : : i18n.joinedTheChat(targetName);
171 2 : case RoomMemberChangeType.acceptInvite:
172 : return userIsTarget
173 0 : ? i18n.youAcceptedTheInvitation
174 2 : : i18n.acceptedTheInvitation(targetName);
175 2 : case RoomMemberChangeType.rejectInvite:
176 : return userIsTarget
177 0 : ? i18n.youRejectedTheInvitation
178 2 : : i18n.rejectedTheInvitation(targetName);
179 2 : case RoomMemberChangeType.withdrawInvitation:
180 : return userIsSender
181 0 : ? i18n.youHaveWithdrawnTheInvitationFor(targetName)
182 2 : : i18n.hasWithdrawnTheInvitationFor(senderName, targetName);
183 2 : case RoomMemberChangeType.leave:
184 0 : return i18n.userLeftTheChat(targetName);
185 2 : case RoomMemberChangeType.kick:
186 : return userIsSender
187 0 : ? i18n.youKicked(targetName)
188 2 : : i18n.kicked(senderName, targetName);
189 2 : case RoomMemberChangeType.invite:
190 : return userIsSender
191 0 : ? i18n.youInvitedUser(targetName)
192 : : userIsTarget
193 0 : ? i18n.youInvitedBy(senderName)
194 2 : : i18n.invitedUser(senderName, targetName);
195 2 : case RoomMemberChangeType.ban:
196 : return userIsSender
197 0 : ? i18n.youBannedUser(targetName)
198 2 : : i18n.bannedUser(senderName, targetName);
199 0 : case RoomMemberChangeType.unban:
200 : return userIsSender
201 0 : ? i18n.youUnbannedUser(targetName)
202 0 : : i18n.unbannedUser(senderName, targetName);
203 0 : case RoomMemberChangeType.knock:
204 0 : return i18n.hasKnocked(targetName);
205 : case RoomMemberChangeType.other:
206 : default:
207 : return userIsTarget
208 0 : ? i18n.youJoinedTheChat
209 0 : : i18n.joinedTheChat(targetName);
210 : }
211 : },
212 2 : EventTypes.RoomPowerLevels: (event, i18n, body) =>
213 2 : i18n.changedTheChatPermissions(
214 4 : event.senderFromMemoryOrFallback.calcDisplayname(i18n: i18n),
215 : ),
216 4 : EventTypes.RoomName: (event, i18n, body) => i18n.changedTheChatNameTo(
217 4 : event.senderFromMemoryOrFallback.calcDisplayname(i18n: i18n),
218 4 : event.content.tryGet<String>('name') ?? '',
219 : ),
220 2 : EventTypes.RoomTopic: (event, i18n, body) =>
221 2 : i18n.changedTheChatDescriptionTo(
222 4 : event.senderFromMemoryOrFallback.calcDisplayname(i18n: i18n),
223 4 : event.content.tryGet<String>('topic') ?? '',
224 : ),
225 4 : EventTypes.RoomAvatar: (event, i18n, body) => i18n.changedTheChatAvatar(
226 4 : event.senderFromMemoryOrFallback.calcDisplayname(i18n: i18n),
227 : ),
228 0 : EventTypes.GuestAccess: (event, i18n, body) {
229 0 : final guestAccess = GuestAccess.values.firstWhereOrNull(
230 0 : (r) =>
231 0 : r.toString().replaceAll('GuestAccess.', '') ==
232 0 : event.content['guest_access'],
233 : );
234 : if (guestAccess == null) {
235 0 : return i18n.changedTheGuestAccessRules(
236 0 : event.senderFromMemoryOrFallback.calcDisplayname(i18n: i18n),
237 : );
238 : } else {
239 0 : return i18n.changedTheGuestAccessRulesTo(
240 0 : event.senderFromMemoryOrFallback.calcDisplayname(i18n: i18n),
241 0 : guestAccess.getLocalizedString(i18n),
242 : );
243 : }
244 : },
245 2 : EventTypes.HistoryVisibility: (event, i18n, body) {
246 2 : final historyVisibility = HistoryVisibility.values.firstWhereOrNull(
247 2 : (r) =>
248 6 : r.toString().replaceAll('HistoryVisibility.', '') ==
249 4 : event.content['history_visibility'],
250 : );
251 : if (historyVisibility == null) {
252 0 : return i18n.changedTheHistoryVisibility(
253 0 : event.senderFromMemoryOrFallback.calcDisplayname(i18n: i18n),
254 : );
255 : } else {
256 2 : return i18n.changedTheHistoryVisibilityTo(
257 4 : event.senderFromMemoryOrFallback.calcDisplayname(i18n: i18n),
258 2 : historyVisibility.getLocalizedString(i18n),
259 : );
260 : }
261 : },
262 2 : EventTypes.Encryption: (event, i18n, body) {
263 2 : var localizedBody = i18n.activatedEndToEndEncryption(
264 4 : event.senderFromMemoryOrFallback.calcDisplayname(i18n: i18n),
265 : );
266 8 : if (event.room.client.encryptionEnabled == false) {
267 6 : localizedBody += '. ${i18n.needPantalaimonWarning}';
268 : }
269 : return localizedBody;
270 : },
271 0 : EventTypes.CallAnswer: (event, i18n, body) => i18n.answeredTheCall(
272 0 : event.senderFromMemoryOrFallback.calcDisplayname(i18n: i18n),
273 : ),
274 0 : EventTypes.CallHangup: (event, i18n, body) => i18n.endedTheCall(
275 0 : event.senderFromMemoryOrFallback.calcDisplayname(i18n: i18n),
276 : ),
277 0 : EventTypes.CallInvite: (event, i18n, body) => i18n.startedACall(
278 0 : event.senderFromMemoryOrFallback.calcDisplayname(i18n: i18n),
279 : ),
280 0 : EventTypes.CallCandidates: (event, i18n, body) => i18n.sentCallInformations(
281 0 : event.senderFromMemoryOrFallback.calcDisplayname(i18n: i18n),
282 : ),
283 0 : EventTypes.Encrypted: (event, i18n, body) =>
284 0 : _localizedBodyNormalMessage(event, i18n, body),
285 2 : EventTypes.Message: (event, i18n, body) =>
286 2 : _localizedBodyNormalMessage(event, i18n, body),
287 0 : EventTypes.Reaction: (event, i18n, body) => i18n.sentReaction(
288 0 : event.senderFromMemoryOrFallback.calcDisplayname(i18n: i18n),
289 0 : event.content
290 0 : .tryGetMap<String, Object?>('m.relates_to')
291 0 : ?.tryGet<String>('key') ??
292 : body,
293 : ),
294 : };
295 : }
|