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