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 0 : case RoomMemberChangeType.other:
206 : return userIsTarget
207 0 : ? i18n.youJoinedTheChat
208 0 : : i18n.joinedTheChat(targetName);
209 : }
210 : },
211 2 : EventTypes.RoomPowerLevels: (event, i18n, body) =>
212 2 : i18n.changedTheChatPermissions(
213 4 : event.senderFromMemoryOrFallback.calcDisplayname(i18n: i18n),
214 : ),
215 4 : EventTypes.RoomName: (event, i18n, body) => i18n.changedTheChatNameTo(
216 4 : event.senderFromMemoryOrFallback.calcDisplayname(i18n: i18n),
217 4 : event.content.tryGet<String>('name') ?? '',
218 : ),
219 2 : EventTypes.RoomTopic: (event, i18n, body) =>
220 2 : i18n.changedTheChatDescriptionTo(
221 4 : event.senderFromMemoryOrFallback.calcDisplayname(i18n: i18n),
222 4 : event.content.tryGet<String>('topic') ?? '',
223 : ),
224 4 : EventTypes.RoomAvatar: (event, i18n, body) => i18n.changedTheChatAvatar(
225 4 : event.senderFromMemoryOrFallback.calcDisplayname(i18n: i18n),
226 : ),
227 0 : EventTypes.GuestAccess: (event, i18n, body) {
228 0 : final guestAccess = GuestAccess.values.firstWhereOrNull(
229 0 : (r) =>
230 0 : r.toString().replaceAll('GuestAccess.', '') ==
231 0 : event.content['guest_access'],
232 : );
233 : if (guestAccess == null) {
234 0 : return i18n.changedTheGuestAccessRules(
235 0 : event.senderFromMemoryOrFallback.calcDisplayname(i18n: i18n),
236 : );
237 : } else {
238 0 : return i18n.changedTheGuestAccessRulesTo(
239 0 : event.senderFromMemoryOrFallback.calcDisplayname(i18n: i18n),
240 0 : guestAccess.getLocalizedString(i18n),
241 : );
242 : }
243 : },
244 2 : EventTypes.HistoryVisibility: (event, i18n, body) {
245 2 : final historyVisibility = HistoryVisibility.values.firstWhereOrNull(
246 2 : (r) =>
247 6 : r.toString().replaceAll('HistoryVisibility.', '') ==
248 4 : event.content['history_visibility'],
249 : );
250 : if (historyVisibility == null) {
251 0 : return i18n.changedTheHistoryVisibility(
252 0 : event.senderFromMemoryOrFallback.calcDisplayname(i18n: i18n),
253 : );
254 : } else {
255 2 : return i18n.changedTheHistoryVisibilityTo(
256 4 : event.senderFromMemoryOrFallback.calcDisplayname(i18n: i18n),
257 2 : historyVisibility.getLocalizedString(i18n),
258 : );
259 : }
260 : },
261 2 : EventTypes.Encryption: (event, i18n, body) {
262 2 : var localizedBody = i18n.activatedEndToEndEncryption(
263 4 : event.senderFromMemoryOrFallback.calcDisplayname(i18n: i18n),
264 : );
265 8 : if (event.room.client.encryptionEnabled == false) {
266 6 : localizedBody += '. ${i18n.needPantalaimonWarning}';
267 : }
268 : return localizedBody;
269 : },
270 0 : EventTypes.CallAnswer: (event, i18n, body) => i18n.answeredTheCall(
271 0 : event.senderFromMemoryOrFallback.calcDisplayname(i18n: i18n),
272 : ),
273 0 : EventTypes.CallHangup: (event, i18n, body) => i18n.endedTheCall(
274 0 : event.senderFromMemoryOrFallback.calcDisplayname(i18n: i18n),
275 : ),
276 0 : EventTypes.CallInvite: (event, i18n, body) => i18n.startedACall(
277 0 : event.senderFromMemoryOrFallback.calcDisplayname(i18n: i18n),
278 : ),
279 0 : EventTypes.CallCandidates: (event, i18n, body) => i18n.sentCallInformations(
280 0 : event.senderFromMemoryOrFallback.calcDisplayname(i18n: i18n),
281 : ),
282 0 : EventTypes.Encrypted: (event, i18n, body) =>
283 0 : _localizedBodyNormalMessage(event, i18n, body),
284 2 : EventTypes.Message: (event, i18n, body) =>
285 2 : _localizedBodyNormalMessage(event, i18n, body),
286 0 : EventTypes.Reaction: (event, i18n, body) => i18n.sentReaction(
287 0 : event.senderFromMemoryOrFallback.calcDisplayname(i18n: i18n),
288 0 : event.content
289 0 : .tryGetMap<String, Object?>('m.relates_to')
290 0 : ?.tryGet<String>('key') ??
291 : body,
292 : ),
293 : };
294 : }
|