1use std::collections::HashMap;
4use std::sync::Arc;
5
6use anyhow::{Result, bail};
7use strum::EnumProperty as EnumPropertyTrait;
8use strum_macros::EnumProperty;
9use tokio::sync::RwLock;
10
11use crate::accounts::Accounts;
12use crate::blob::BlobObject;
13use crate::chat::{self, Chat, ChatId};
14use crate::config::Config;
15use crate::contact::{Contact, ContactId};
16use crate::context::Context;
17use crate::message::{Message, Viewtype};
18use crate::param::Param;
19
20#[derive(Debug, Clone)]
22pub struct StockStrings {
23 translated_stockstrings: Arc<RwLock<HashMap<usize, String>>>,
25}
26
27#[allow(missing_docs)]
36#[derive(Debug, Clone, Copy, PartialEq, Eq, FromPrimitive, ToPrimitive, EnumProperty)]
37#[repr(u32)]
38pub enum StockMessage {
39 #[strum(props(fallback = "No messages."))]
40 NoMessages = 1,
41
42 #[strum(props(fallback = "Me"))]
43 SelfMsg = 2,
44
45 #[strum(props(fallback = "Draft"))]
46 Draft = 3,
47
48 #[strum(props(fallback = "Voice message"))]
49 VoiceMessage = 7,
50
51 #[strum(props(fallback = "Image"))]
52 Image = 9,
53
54 #[strum(props(fallback = "Video"))]
55 Video = 10,
56
57 #[strum(props(fallback = "Audio"))]
58 Audio = 11,
59
60 #[strum(props(fallback = "File"))]
61 File = 12,
62
63 #[strum(props(fallback = "GIF"))]
64 Gif = 23,
65
66 #[strum(props(fallback = "No encryption."))]
67 EncrNone = 28,
68
69 #[strum(props(fallback = "Fingerprints"))]
70 FingerPrints = 30,
71
72 #[strum(props(fallback = "%1$s verified."))]
73 ContactVerified = 35,
74
75 #[strum(props(fallback = "Archived chats"))]
76 ArchivedChats = 40,
77
78 #[strum(props(
79 fallback = "Cannot login as \"%1$s\". Please check if the email address and the password are correct."
80 ))]
81 CannotLogin = 60,
82
83 #[strum(props(fallback = "Location streaming enabled."))]
84 MsgLocationEnabled = 64,
85
86 #[strum(props(fallback = "Location streaming disabled."))]
87 MsgLocationDisabled = 65,
88
89 #[strum(props(fallback = "Location"))]
90 Location = 66,
91
92 #[strum(props(fallback = "Sticker"))]
93 Sticker = 67,
94
95 #[strum(props(fallback = "Device messages"))]
96 DeviceMessages = 68,
97
98 #[strum(props(fallback = "Saved messages"))]
99 SavedMessages = 69,
100
101 #[strum(props(
102 fallback = "Messages in this chat are generated locally by your Delta Chat app. \
103 Its makers use it to inform about app updates and problems during usage."
104 ))]
105 DeviceMessagesHint = 70,
106
107 #[strum(props(fallback = "Get in contact!\n\n\
108 🙌 Tap \"QR code\" on the main screen of both devices. \
109 Choose \"Scan QR Code\" on one device, and point it at the other\n\n\
110 🌍 If not in the same room, \
111 scan via video call or share an invite link from \"Scan QR code\"\n\n\
112 Then: Enjoy your decentralized messenger experience. \
113 In contrast to other popular apps, \
114 without central control or tracking or selling you, \
115 friends, colleagues or family out to large organizations."))]
116 WelcomeMessage = 71,
117
118 #[strum(props(fallback = "Message from %1$s"))]
119 SubjectForNewContact = 73,
120
121 #[strum(props(fallback = "Failed to send message to %1$s."))]
123 FailedSendingTo = 74,
124
125 #[strum(props(fallback = "Error:\n\n“%1$s”"))]
126 ConfigurationFailed = 84,
127
128 #[strum(props(
129 fallback = "⚠️ Date or time of your device seem to be inaccurate (%1$s).\n\n\
130 Adjust your clock ⏰🔧 to ensure your messages are received correctly."
131 ))]
132 BadTimeMsgBody = 85,
133
134 #[strum(props(fallback = "⚠️ Your Delta Chat version might be outdated.\n\n\
135 This may cause problems because your chat partners use newer versions - \
136 and you are missing the latest features 😳\n\
137 Please check https://get.delta.chat or your app store for updates."))]
138 UpdateReminderMsgBody = 86,
139
140 #[strum(props(
141 fallback = "Could not find your mail server.\n\nPlease check your internet connection."
142 ))]
143 ErrorNoNetwork = 87,
144
145 #[strum(props(fallback = "Reply"))]
147 ReplyNoun = 90,
148
149 #[strum(props(fallback = "You deleted the \"Saved messages\" chat.\n\n\
150 To use the \"Saved messages\" feature again, create a new chat with yourself."))]
151 SelfDeletedMsgBody = 91,
152
153 #[strum(props(fallback = "Forwarded"))]
154 Forwarded = 97,
155
156 #[strum(props(
157 fallback = "⚠️ Your provider's storage is about to exceed, already %1$s%% are used.\n\n\
158 You may not be able to receive message when the storage is 100%% used.\n\n\
159 👉 Please check if you can delete old data in the provider's webinterface \
160 and consider to enable \"Settings / Delete Old Messages\". \
161 You can check your current storage usage anytime at \"Settings / Connectivity\"."
162 ))]
163 QuotaExceedingMsgBody = 98,
164
165 #[strum(props(fallback = "Multi Device Synchronization"))]
166 SyncMsgSubject = 101,
167
168 #[strum(props(
169 fallback = "This message is used to synchronize data between your devices.\n\n\
170 👉 If you see this message in Delta Chat, please update your Delta Chat apps on all devices."
171 ))]
172 SyncMsgBody = 102,
173
174 #[strum(props(fallback = "Incoming Messages"))]
175 IncomingMessages = 103,
176
177 #[strum(props(fallback = "Outgoing Messages"))]
178 OutgoingMessages = 104,
179
180 #[strum(props(fallback = "Storage on %1$s"))]
181 StorageOnDomain = 105,
182
183 #[strum(props(fallback = "Connected"))]
184 Connected = 107,
185
186 #[strum(props(fallback = "Connecting…"))]
187 Connecting = 108,
188
189 #[strum(props(fallback = "Updating…"))]
190 Updating = 109,
191
192 #[strum(props(fallback = "Sending…"))]
193 Sending = 110,
194
195 #[strum(props(fallback = "Your last message was sent successfully."))]
196 LastMsgSentSuccessfully = 111,
197
198 #[strum(props(fallback = "Error: %1$s"))]
199 Error = 112,
200
201 #[strum(props(fallback = "Not supported by your provider."))]
202 NotSupportedByProvider = 113,
203
204 #[strum(props(fallback = "Messages"))]
205 Messages = 114,
206
207 #[strum(props(fallback = "%1$s of %2$s used"))]
208 PartOfTotallUsed = 116,
209
210 #[strum(props(fallback = "%1$s invited you to join this group.\n\n\
211 Waiting for the device of %2$s to reply…"))]
212 SecureJoinStarted = 117,
213
214 #[strum(props(fallback = "%1$s replied, waiting for being added to the group…"))]
215 SecureJoinReplies = 118,
216
217 #[strum(props(fallback = "Scan to chat with %1$s"))]
218 SetupContactQRDescription = 119,
219
220 #[strum(props(fallback = "Scan to join group %1$s"))]
221 SecureJoinGroupQRDescription = 120,
222
223 #[strum(props(fallback = "Not connected"))]
224 NotConnected = 121,
225
226 #[strum(props(fallback = "You changed group name from \"%1$s\" to \"%2$s\"."))]
227 MsgYouChangedGrpName = 124,
228
229 #[strum(props(fallback = "Group name changed from \"%1$s\" to \"%2$s\" by %3$s."))]
230 MsgGrpNameChangedBy = 125,
231
232 #[strum(props(fallback = "You changed the group image."))]
233 MsgYouChangedGrpImg = 126,
234
235 #[strum(props(fallback = "Group image changed by %1$s."))]
236 MsgGrpImgChangedBy = 127,
237
238 #[strum(props(fallback = "You added member %1$s."))]
239 MsgYouAddMember = 128,
240
241 #[strum(props(fallback = "Member %1$s added by %2$s."))]
242 MsgAddMemberBy = 129,
243
244 #[strum(props(fallback = "You removed member %1$s."))]
245 MsgYouDelMember = 130,
246
247 #[strum(props(fallback = "Member %1$s removed by %2$s."))]
248 MsgDelMemberBy = 131,
249
250 #[strum(props(fallback = "You left the group."))]
251 MsgYouLeftGroup = 132,
252
253 #[strum(props(fallback = "Group left by %1$s."))]
254 MsgGroupLeftBy = 133,
255
256 #[strum(props(fallback = "You deleted the group image."))]
257 MsgYouDeletedGrpImg = 134,
258
259 #[strum(props(fallback = "Group image deleted by %1$s."))]
260 MsgGrpImgDeletedBy = 135,
261
262 #[strum(props(fallback = "You enabled location streaming."))]
263 MsgYouEnabledLocation = 136,
264
265 #[strum(props(fallback = "Location streaming enabled by %1$s."))]
266 MsgLocationEnabledBy = 137,
267
268 #[strum(props(fallback = "You disabled message deletion timer."))]
269 MsgYouDisabledEphemeralTimer = 138,
270
271 #[strum(props(fallback = "Message deletion timer is disabled by %1$s."))]
272 MsgEphemeralTimerDisabledBy = 139,
273
274 #[strum(props(fallback = "You set message deletion timer to %1$s s."))]
277 MsgYouEnabledEphemeralTimer = 140,
278
279 #[strum(props(fallback = "Message deletion timer is set to %1$s s by %2$s."))]
280 MsgEphemeralTimerEnabledBy = 141,
281
282 #[strum(props(fallback = "You set message deletion timer to 1 hour."))]
283 MsgYouEphemeralTimerHour = 144,
284
285 #[strum(props(fallback = "Message deletion timer is set to 1 hour by %1$s."))]
286 MsgEphemeralTimerHourBy = 145,
287
288 #[strum(props(fallback = "You set message deletion timer to 1 day."))]
289 MsgYouEphemeralTimerDay = 146,
290
291 #[strum(props(fallback = "Message deletion timer is set to 1 day by %1$s."))]
292 MsgEphemeralTimerDayBy = 147,
293
294 #[strum(props(fallback = "You set message deletion timer to 1 week."))]
295 MsgYouEphemeralTimerWeek = 148,
296
297 #[strum(props(fallback = "Message deletion timer is set to 1 week by %1$s."))]
298 MsgEphemeralTimerWeekBy = 149,
299
300 #[strum(props(fallback = "You set message deletion timer to %1$s minutes."))]
301 MsgYouEphemeralTimerMinutes = 150,
302
303 #[strum(props(fallback = "Message deletion timer is set to %1$s minutes by %2$s."))]
304 MsgEphemeralTimerMinutesBy = 151,
305
306 #[strum(props(fallback = "You set message deletion timer to %1$s hours."))]
307 MsgYouEphemeralTimerHours = 152,
308
309 #[strum(props(fallback = "Message deletion timer is set to %1$s hours by %2$s."))]
310 MsgEphemeralTimerHoursBy = 153,
311
312 #[strum(props(fallback = "You set message deletion timer to %1$s days."))]
313 MsgYouEphemeralTimerDays = 154,
314
315 #[strum(props(fallback = "Message deletion timer is set to %1$s days by %2$s."))]
316 MsgEphemeralTimerDaysBy = 155,
317
318 #[strum(props(fallback = "You set message deletion timer to %1$s weeks."))]
319 MsgYouEphemeralTimerWeeks = 156,
320
321 #[strum(props(fallback = "Message deletion timer is set to %1$s weeks by %2$s."))]
322 MsgEphemeralTimerWeeksBy = 157,
323
324 #[strum(props(fallback = "You set message deletion timer to 1 year."))]
325 MsgYouEphemeralTimerYear = 158,
326
327 #[strum(props(fallback = "Message deletion timer is set to 1 year by %1$s."))]
328 MsgEphemeralTimerYearBy = 159,
329
330 #[strum(props(fallback = "Scan to set up second device for %1$s"))]
331 BackupTransferQr = 162,
332
333 #[strum(props(fallback = "ℹ️ Account transferred to your second device."))]
334 BackupTransferMsgBody = 163,
335
336 #[strum(props(fallback = "Messages are end-to-end encrypted."))]
337 ChatProtectionEnabled = 170,
338
339 #[strum(props(fallback = "Others will only see this group after you sent a first message."))]
340 NewGroupSendFirstMessage = 172,
341
342 #[strum(props(fallback = "Member %1$s added."))]
343 MsgAddMember = 173,
344
345 #[strum(props(
346 fallback = "⚠️ Your email provider %1$s requires end-to-end encryption which is not setup yet."
347 ))]
348 InvalidUnencryptedMail = 174,
349
350 #[strum(props(fallback = "You reacted %1$s to \"%2$s\""))]
351 MsgYouReacted = 176,
352
353 #[strum(props(fallback = "%1$s reacted %2$s to \"%3$s\""))]
354 MsgReactedBy = 177,
355
356 #[strum(props(fallback = "Member %1$s removed."))]
357 MsgDelMember = 178,
358
359 #[strum(props(fallback = "Establishing connection, please wait…"))]
360 SecurejoinWait = 190,
361
362 #[strum(props(fallback = "❤️ Seems you're enjoying Delta Chat!
363
364Please consider donating to help that Delta Chat stays free for everyone.
365
366While Delta Chat is free to use and open source, development costs money.
367Help keeping us to keep Delta Chat independent and make it more awesome in the future.
368
369https://delta.chat/donate"))]
370 DonationRequest = 193,
371
372 #[strum(props(fallback = "Outgoing call"))]
373 OutgoingCall = 194,
374
375 #[strum(props(fallback = "Incoming call"))]
376 IncomingCall = 195,
377
378 #[strum(props(fallback = "Declined call"))]
379 DeclinedCall = 196,
380
381 #[strum(props(fallback = "Canceled call"))]
382 CanceledCall = 197,
383
384 #[strum(props(fallback = "Missed call"))]
385 MissedCall = 198,
386
387 #[strum(props(fallback = "You left the channel."))]
388 MsgYouLeftBroadcast = 200,
389
390 #[strum(props(fallback = "Scan to join channel %1$s"))]
391 SecureJoinBrodcastQRDescription = 201,
392
393 #[strum(props(fallback = "You joined the channel."))]
394 MsgYouJoinedBroadcast = 202,
395
396 #[strum(props(fallback = "%1$s invited you to join this channel.\n\n\
397 Waiting for the device of %2$s to reply…"))]
398 SecureJoinBroadcastStarted = 203,
399
400 #[strum(props(
401 fallback = "The attachment contains anonymous usage statistics, which helps us improve Delta Chat. Thank you!"
402 ))]
403 StatsMsgBody = 210,
404
405 #[strum(props(fallback = "Proxy Enabled"))]
406 ProxyEnabled = 220,
407
408 #[strum(props(
409 fallback = "You are using a proxy. If you're having trouble connecting, try a different proxy."
410 ))]
411 ProxyEnabledDescription = 221,
412
413 #[strum(props(fallback = "Messages in this chat use classic email and are not encrypted."))]
414 ChatUnencryptedExplanation = 230,
415}
416
417impl StockMessage {
418 fn fallback(self) -> &'static str {
422 self.get_str("fallback").unwrap_or_default()
423 }
424}
425
426impl Default for StockStrings {
427 fn default() -> Self {
428 StockStrings::new()
429 }
430}
431
432impl StockStrings {
433 pub fn new() -> Self {
435 Self {
436 translated_stockstrings: Arc::new(RwLock::new(Default::default())),
437 }
438 }
439
440 async fn translated(&self, id: StockMessage) -> String {
441 self.translated_stockstrings
442 .read()
443 .await
444 .get(&(id as usize))
445 .map(AsRef::as_ref)
446 .unwrap_or_else(|| id.fallback())
447 .to_string()
448 }
449
450 async fn set_stock_translation(&self, id: StockMessage, stockstring: String) -> Result<()> {
451 if stockstring.contains("%1") && !id.fallback().contains("%1") {
452 bail!(
453 "translation {} contains invalid %1 placeholder, default is {}",
454 stockstring,
455 id.fallback()
456 );
457 }
458 if stockstring.contains("%2") && !id.fallback().contains("%2") {
459 bail!(
460 "translation {} contains invalid %2 placeholder, default is {}",
461 stockstring,
462 id.fallback()
463 );
464 }
465 self.translated_stockstrings
466 .write()
467 .await
468 .insert(id as usize, stockstring);
469 Ok(())
470 }
471}
472
473async fn translated(context: &Context, id: StockMessage) -> String {
474 context.translated_stockstrings.translated(id).await
475}
476
477trait StockStringMods: AsRef<str> + Sized {
479 fn replace1(&self, replacement: &str) -> String {
481 self.as_ref()
482 .replacen("%1$s", replacement, 1)
483 .replacen("%1$d", replacement, 1)
484 .replacen("%1$@", replacement, 1)
485 }
486
487 fn replace2(&self, replacement: &str) -> String {
492 self.as_ref()
493 .replacen("%2$s", replacement, 1)
494 .replacen("%2$d", replacement, 1)
495 .replacen("%2$@", replacement, 1)
496 }
497
498 fn replace3(&self, replacement: &str) -> String {
503 self.as_ref()
504 .replacen("%3$s", replacement, 1)
505 .replacen("%3$d", replacement, 1)
506 .replacen("%3$@", replacement, 1)
507 }
508}
509
510impl ContactId {
511 async fn get_stock_name(self, context: &Context) -> String {
513 Contact::get_by_id(context, self)
514 .await
515 .map(|contact| contact.get_display_name().to_string())
516 .unwrap_or_else(|_| self.to_string())
517 }
518}
519
520impl StockStringMods for String {}
521
522pub(crate) async fn no_messages(context: &Context) -> String {
524 translated(context, StockMessage::NoMessages).await
525}
526
527pub(crate) async fn self_msg(context: &Context) -> String {
529 translated(context, StockMessage::SelfMsg).await
530}
531
532pub(crate) async fn draft(context: &Context) -> String {
534 translated(context, StockMessage::Draft).await
535}
536
537pub(crate) async fn voice_message(context: &Context) -> String {
539 translated(context, StockMessage::VoiceMessage).await
540}
541
542pub(crate) async fn image(context: &Context) -> String {
544 translated(context, StockMessage::Image).await
545}
546
547pub(crate) async fn video(context: &Context) -> String {
549 translated(context, StockMessage::Video).await
550}
551
552pub(crate) async fn audio(context: &Context) -> String {
554 translated(context, StockMessage::Audio).await
555}
556
557pub(crate) async fn file(context: &Context) -> String {
559 translated(context, StockMessage::File).await
560}
561
562pub(crate) async fn msg_grp_name(
564 context: &Context,
565 from_group: &str,
566 to_group: &str,
567 by_contact: ContactId,
568) -> String {
569 if by_contact == ContactId::SELF {
570 translated(context, StockMessage::MsgYouChangedGrpName)
571 .await
572 .replace1(from_group)
573 .replace2(to_group)
574 } else {
575 translated(context, StockMessage::MsgGrpNameChangedBy)
576 .await
577 .replace1(from_group)
578 .replace2(to_group)
579 .replace3(&by_contact.get_stock_name(context).await)
580 }
581}
582
583pub(crate) async fn msg_grp_img_changed(context: &Context, by_contact: ContactId) -> String {
584 if by_contact == ContactId::SELF {
585 translated(context, StockMessage::MsgYouChangedGrpImg).await
586 } else {
587 translated(context, StockMessage::MsgGrpImgChangedBy)
588 .await
589 .replace1(&by_contact.get_stock_name(context).await)
590 }
591}
592
593pub(crate) async fn msg_add_member_local(
598 context: &Context,
599 added_member: ContactId,
600 by_contact: ContactId,
601) -> String {
602 let whom = added_member.get_stock_name(context).await;
603 if by_contact == ContactId::UNDEFINED {
604 translated(context, StockMessage::MsgAddMember)
605 .await
606 .replace1(&whom)
607 } else if by_contact == ContactId::SELF {
608 translated(context, StockMessage::MsgYouAddMember)
609 .await
610 .replace1(&whom)
611 } else {
612 translated(context, StockMessage::MsgAddMemberBy)
613 .await
614 .replace1(&whom)
615 .replace2(&by_contact.get_stock_name(context).await)
616 }
617}
618
619pub(crate) async fn msg_del_member_local(
624 context: &Context,
625 removed_member: ContactId,
626 by_contact: ContactId,
627) -> String {
628 let whom = removed_member.get_stock_name(context).await;
629 if by_contact == ContactId::UNDEFINED {
630 translated(context, StockMessage::MsgDelMember)
631 .await
632 .replace1(&whom)
633 } else if by_contact == ContactId::SELF {
634 translated(context, StockMessage::MsgYouDelMember)
635 .await
636 .replace1(&whom)
637 } else {
638 translated(context, StockMessage::MsgDelMemberBy)
639 .await
640 .replace1(&whom)
641 .replace2(&by_contact.get_stock_name(context).await)
642 }
643}
644
645pub(crate) async fn msg_group_left_local(context: &Context, by_contact: ContactId) -> String {
647 if by_contact == ContactId::SELF {
648 translated(context, StockMessage::MsgYouLeftGroup).await
649 } else {
650 translated(context, StockMessage::MsgGroupLeftBy)
651 .await
652 .replace1(&by_contact.get_stock_name(context).await)
653 }
654}
655
656pub(crate) async fn msg_you_left_broadcast(context: &Context) -> String {
658 translated(context, StockMessage::MsgYouLeftBroadcast).await
659}
660
661pub(crate) async fn msg_you_joined_broadcast(context: &Context) -> String {
663 translated(context, StockMessage::MsgYouJoinedBroadcast).await
664}
665
666pub(crate) async fn secure_join_broadcast_started(
668 context: &Context,
669 inviter_contact_id: ContactId,
670) -> String {
671 if let Ok(contact) = Contact::get_by_id(context, inviter_contact_id).await {
672 translated(context, StockMessage::SecureJoinBroadcastStarted)
673 .await
674 .replace1(contact.get_display_name())
675 .replace2(contact.get_display_name())
676 } else {
677 format!("secure_join_started: unknown contact {inviter_contact_id}")
678 }
679}
680
681pub(crate) async fn msg_reacted(
683 context: &Context,
684 by_contact: ContactId,
685 reaction: &str,
686 summary: &str,
687) -> String {
688 if by_contact == ContactId::SELF {
689 translated(context, StockMessage::MsgYouReacted)
690 .await
691 .replace1(reaction)
692 .replace2(summary)
693 } else {
694 translated(context, StockMessage::MsgReactedBy)
695 .await
696 .replace1(&by_contact.get_stock_name(context).await)
697 .replace2(reaction)
698 .replace3(summary)
699 }
700}
701
702pub(crate) async fn gif(context: &Context) -> String {
704 translated(context, StockMessage::Gif).await
705}
706
707pub(crate) async fn encr_none(context: &Context) -> String {
709 translated(context, StockMessage::EncrNone).await
710}
711
712pub(crate) async fn finger_prints(context: &Context) -> String {
714 translated(context, StockMessage::FingerPrints).await
715}
716
717pub(crate) async fn msg_grp_img_deleted(context: &Context, by_contact: ContactId) -> String {
719 if by_contact == ContactId::SELF {
720 translated(context, StockMessage::MsgYouDeletedGrpImg).await
721 } else {
722 translated(context, StockMessage::MsgGrpImgDeletedBy)
723 .await
724 .replace1(&by_contact.get_stock_name(context).await)
725 }
726}
727
728pub(crate) async fn secure_join_started(
730 context: &Context,
731 inviter_contact_id: ContactId,
732) -> String {
733 if let Ok(contact) = Contact::get_by_id(context, inviter_contact_id).await {
734 translated(context, StockMessage::SecureJoinStarted)
735 .await
736 .replace1(contact.get_display_name())
737 .replace2(contact.get_display_name())
738 } else {
739 format!("secure_join_started: unknown contact {inviter_contact_id}")
740 }
741}
742
743pub(crate) async fn secure_join_replies(context: &Context, contact_id: ContactId) -> String {
745 translated(context, StockMessage::SecureJoinReplies)
746 .await
747 .replace1(&contact_id.get_stock_name(context).await)
748}
749
750pub(crate) async fn securejoin_wait(context: &Context) -> String {
752 translated(context, StockMessage::SecurejoinWait).await
753}
754
755pub(crate) async fn donation_request(context: &Context) -> String {
757 translated(context, StockMessage::DonationRequest).await
758}
759
760pub(crate) async fn outgoing_call(context: &Context) -> String {
762 translated(context, StockMessage::OutgoingCall).await
763}
764
765pub(crate) async fn incoming_call(context: &Context) -> String {
767 translated(context, StockMessage::IncomingCall).await
768}
769
770pub(crate) async fn declined_call(context: &Context) -> String {
772 translated(context, StockMessage::DeclinedCall).await
773}
774
775pub(crate) async fn canceled_call(context: &Context) -> String {
777 translated(context, StockMessage::CanceledCall).await
778}
779
780pub(crate) async fn missed_call(context: &Context) -> String {
782 translated(context, StockMessage::MissedCall).await
783}
784
785pub(crate) async fn setup_contact_qr_description(
787 context: &Context,
788 display_name: &str,
789 addr: &str,
790) -> String {
791 let name = if display_name.is_empty() {
792 addr.to_owned()
793 } else {
794 display_name.to_owned()
795 };
796 translated(context, StockMessage::SetupContactQRDescription)
797 .await
798 .replace1(&name)
799}
800
801pub(crate) async fn secure_join_group_qr_description(context: &Context, chat: &Chat) -> String {
803 translated(context, StockMessage::SecureJoinGroupQRDescription)
804 .await
805 .replace1(chat.get_name())
806}
807
808pub(crate) async fn secure_join_broadcast_qr_description(context: &Context, chat: &Chat) -> String {
810 translated(context, StockMessage::SecureJoinBrodcastQRDescription)
811 .await
812 .replace1(chat.get_name())
813}
814
815#[allow(dead_code)]
817pub(crate) async fn contact_verified(context: &Context, contact: &Contact) -> String {
818 let addr = contact.get_display_name();
819 translated(context, StockMessage::ContactVerified)
820 .await
821 .replace1(addr)
822}
823
824pub(crate) async fn archived_chats(context: &Context) -> String {
826 translated(context, StockMessage::ArchivedChats).await
827}
828
829pub(crate) async fn sync_msg_subject(context: &Context) -> String {
831 translated(context, StockMessage::SyncMsgSubject).await
832}
833
834pub(crate) async fn sync_msg_body(context: &Context) -> String {
836 translated(context, StockMessage::SyncMsgBody).await
837}
838
839pub(crate) async fn cannot_login(context: &Context, user: &str) -> String {
841 translated(context, StockMessage::CannotLogin)
842 .await
843 .replace1(user)
844}
845
846pub(crate) async fn msg_location_enabled(context: &Context) -> String {
848 translated(context, StockMessage::MsgLocationEnabled).await
849}
850
851pub(crate) async fn msg_location_enabled_by(context: &Context, contact: ContactId) -> String {
853 if contact == ContactId::SELF {
854 translated(context, StockMessage::MsgYouEnabledLocation).await
855 } else {
856 translated(context, StockMessage::MsgLocationEnabledBy)
857 .await
858 .replace1(&contact.get_stock_name(context).await)
859 }
860}
861
862pub(crate) async fn msg_location_disabled(context: &Context) -> String {
864 translated(context, StockMessage::MsgLocationDisabled).await
865}
866
867pub(crate) async fn location(context: &Context) -> String {
869 translated(context, StockMessage::Location).await
870}
871
872pub(crate) async fn sticker(context: &Context) -> String {
874 translated(context, StockMessage::Sticker).await
875}
876
877pub(crate) async fn device_messages(context: &Context) -> String {
879 translated(context, StockMessage::DeviceMessages).await
880}
881
882pub(crate) async fn saved_messages(context: &Context) -> String {
884 translated(context, StockMessage::SavedMessages).await
885}
886
887pub(crate) async fn device_messages_hint(context: &Context) -> String {
889 translated(context, StockMessage::DeviceMessagesHint).await
890}
891
892pub(crate) async fn welcome_message(context: &Context) -> String {
894 translated(context, StockMessage::WelcomeMessage).await
895}
896
897pub(crate) async fn subject_for_new_contact(context: &Context, self_name: &str) -> String {
900 translated(context, StockMessage::SubjectForNewContact)
901 .await
902 .replace1(self_name)
903}
904
905pub(crate) async fn msg_ephemeral_timer_disabled(
907 context: &Context,
908 by_contact: ContactId,
909) -> String {
910 if by_contact == ContactId::SELF {
911 translated(context, StockMessage::MsgYouDisabledEphemeralTimer).await
912 } else {
913 translated(context, StockMessage::MsgEphemeralTimerDisabledBy)
914 .await
915 .replace1(&by_contact.get_stock_name(context).await)
916 }
917}
918
919pub(crate) async fn msg_ephemeral_timer_enabled(
921 context: &Context,
922 timer: &str,
923 by_contact: ContactId,
924) -> String {
925 if by_contact == ContactId::SELF {
926 translated(context, StockMessage::MsgYouEnabledEphemeralTimer)
927 .await
928 .replace1(timer)
929 } else {
930 translated(context, StockMessage::MsgEphemeralTimerEnabledBy)
931 .await
932 .replace1(timer)
933 .replace2(&by_contact.get_stock_name(context).await)
934 }
935}
936
937pub(crate) async fn msg_ephemeral_timer_hour(context: &Context, by_contact: ContactId) -> String {
939 if by_contact == ContactId::SELF {
940 translated(context, StockMessage::MsgYouEphemeralTimerHour).await
941 } else {
942 translated(context, StockMessage::MsgEphemeralTimerHourBy)
943 .await
944 .replace1(&by_contact.get_stock_name(context).await)
945 }
946}
947
948pub(crate) async fn msg_ephemeral_timer_day(context: &Context, by_contact: ContactId) -> String {
950 if by_contact == ContactId::SELF {
951 translated(context, StockMessage::MsgYouEphemeralTimerDay).await
952 } else {
953 translated(context, StockMessage::MsgEphemeralTimerDayBy)
954 .await
955 .replace1(&by_contact.get_stock_name(context).await)
956 }
957}
958
959pub(crate) async fn msg_ephemeral_timer_week(context: &Context, by_contact: ContactId) -> String {
961 if by_contact == ContactId::SELF {
962 translated(context, StockMessage::MsgYouEphemeralTimerWeek).await
963 } else {
964 translated(context, StockMessage::MsgEphemeralTimerWeekBy)
965 .await
966 .replace1(&by_contact.get_stock_name(context).await)
967 }
968}
969
970pub(crate) async fn msg_ephemeral_timer_year(context: &Context, by_contact: ContactId) -> String {
972 if by_contact == ContactId::SELF {
973 translated(context, StockMessage::MsgYouEphemeralTimerYear).await
974 } else {
975 translated(context, StockMessage::MsgEphemeralTimerYearBy)
976 .await
977 .replace1(&by_contact.get_stock_name(context).await)
978 }
979}
980
981pub(crate) async fn configuration_failed(context: &Context, details: &str) -> String {
983 translated(context, StockMessage::ConfigurationFailed)
984 .await
985 .replace1(details)
986}
987
988pub(crate) async fn bad_time_msg_body(context: &Context, now: &str) -> String {
991 translated(context, StockMessage::BadTimeMsgBody)
992 .await
993 .replace1(now)
994}
995
996pub(crate) async fn update_reminder_msg_body(context: &Context) -> String {
998 translated(context, StockMessage::UpdateReminderMsgBody).await
999}
1000
1001pub(crate) async fn error_no_network(context: &Context) -> String {
1003 translated(context, StockMessage::ErrorNoNetwork).await
1004}
1005
1006pub(crate) async fn messages_e2e_encrypted(context: &Context) -> String {
1008 translated(context, StockMessage::ChatProtectionEnabled).await
1009}
1010
1011pub(crate) async fn reply_noun(context: &Context) -> String {
1013 translated(context, StockMessage::ReplyNoun).await
1014}
1015
1016pub(crate) async fn self_deleted_msg_body(context: &Context) -> String {
1018 translated(context, StockMessage::SelfDeletedMsgBody).await
1019}
1020
1021pub(crate) async fn msg_ephemeral_timer_minutes(
1023 context: &Context,
1024 minutes: &str,
1025 by_contact: ContactId,
1026) -> String {
1027 if by_contact == ContactId::SELF {
1028 translated(context, StockMessage::MsgYouEphemeralTimerMinutes)
1029 .await
1030 .replace1(minutes)
1031 } else {
1032 translated(context, StockMessage::MsgEphemeralTimerMinutesBy)
1033 .await
1034 .replace1(minutes)
1035 .replace2(&by_contact.get_stock_name(context).await)
1036 }
1037}
1038
1039pub(crate) async fn msg_ephemeral_timer_hours(
1041 context: &Context,
1042 hours: &str,
1043 by_contact: ContactId,
1044) -> String {
1045 if by_contact == ContactId::SELF {
1046 translated(context, StockMessage::MsgYouEphemeralTimerHours)
1047 .await
1048 .replace1(hours)
1049 } else {
1050 translated(context, StockMessage::MsgEphemeralTimerHoursBy)
1051 .await
1052 .replace1(hours)
1053 .replace2(&by_contact.get_stock_name(context).await)
1054 }
1055}
1056
1057pub(crate) async fn msg_ephemeral_timer_days(
1059 context: &Context,
1060 days: &str,
1061 by_contact: ContactId,
1062) -> String {
1063 if by_contact == ContactId::SELF {
1064 translated(context, StockMessage::MsgYouEphemeralTimerDays)
1065 .await
1066 .replace1(days)
1067 } else {
1068 translated(context, StockMessage::MsgEphemeralTimerDaysBy)
1069 .await
1070 .replace1(days)
1071 .replace2(&by_contact.get_stock_name(context).await)
1072 }
1073}
1074
1075pub(crate) async fn msg_ephemeral_timer_weeks(
1077 context: &Context,
1078 weeks: &str,
1079 by_contact: ContactId,
1080) -> String {
1081 if by_contact == ContactId::SELF {
1082 translated(context, StockMessage::MsgYouEphemeralTimerWeeks)
1083 .await
1084 .replace1(weeks)
1085 } else {
1086 translated(context, StockMessage::MsgEphemeralTimerWeeksBy)
1087 .await
1088 .replace1(weeks)
1089 .replace2(&by_contact.get_stock_name(context).await)
1090 }
1091}
1092
1093pub(crate) async fn forwarded(context: &Context) -> String {
1095 translated(context, StockMessage::Forwarded).await
1096}
1097
1098pub(crate) async fn quota_exceeding(context: &Context, highest_usage: u64) -> String {
1100 translated(context, StockMessage::QuotaExceedingMsgBody)
1101 .await
1102 .replace1(&format!("{highest_usage}"))
1103 .replace("%%", "%")
1104}
1105
1106pub(crate) async fn incoming_messages(context: &Context) -> String {
1108 translated(context, StockMessage::IncomingMessages).await
1109}
1110
1111pub(crate) async fn outgoing_messages(context: &Context) -> String {
1113 translated(context, StockMessage::OutgoingMessages).await
1114}
1115
1116pub(crate) async fn not_connected(context: &Context) -> String {
1118 translated(context, StockMessage::NotConnected).await
1119}
1120
1121pub(crate) async fn connected(context: &Context) -> String {
1123 translated(context, StockMessage::Connected).await
1124}
1125
1126pub(crate) async fn connecting(context: &Context) -> String {
1128 translated(context, StockMessage::Connecting).await
1129}
1130
1131pub(crate) async fn updating(context: &Context) -> String {
1133 translated(context, StockMessage::Updating).await
1134}
1135
1136pub(crate) async fn sending(context: &Context) -> String {
1138 translated(context, StockMessage::Sending).await
1139}
1140
1141pub(crate) async fn last_msg_sent_successfully(context: &Context) -> String {
1143 translated(context, StockMessage::LastMsgSentSuccessfully).await
1144}
1145
1146pub(crate) async fn error(context: &Context, error: &str) -> String {
1149 translated(context, StockMessage::Error)
1150 .await
1151 .replace1(error)
1152}
1153
1154pub(crate) async fn not_supported_by_provider(context: &Context) -> String {
1156 translated(context, StockMessage::NotSupportedByProvider).await
1157}
1158
1159pub(crate) async fn messages(context: &Context) -> String {
1162 translated(context, StockMessage::Messages).await
1163}
1164
1165pub(crate) async fn part_of_total_used(context: &Context, part: &str, total: &str) -> String {
1167 translated(context, StockMessage::PartOfTotallUsed)
1168 .await
1169 .replace1(part)
1170 .replace2(total)
1171}
1172
1173pub(crate) async fn unencrypted_email(context: &Context, provider: &str) -> String {
1175 translated(context, StockMessage::InvalidUnencryptedMail)
1176 .await
1177 .replace1(provider)
1178}
1179
1180pub(crate) async fn stats_msg_body(context: &Context) -> String {
1182 translated(context, StockMessage::StatsMsgBody).await
1183}
1184
1185pub(crate) async fn new_group_send_first_message(context: &Context) -> String {
1187 translated(context, StockMessage::NewGroupSendFirstMessage).await
1188}
1189
1190pub(crate) async fn backup_transfer_qr(context: &Context) -> Result<String> {
1197 let name = if let Some(name) = context.get_config(Config::Displayname).await? {
1198 name
1199 } else {
1200 context.get_primary_self_addr().await?
1201 };
1202 Ok(translated(context, StockMessage::BackupTransferQr)
1203 .await
1204 .replace1(&name))
1205}
1206
1207pub(crate) async fn backup_transfer_msg_body(context: &Context) -> String {
1208 translated(context, StockMessage::BackupTransferMsgBody).await
1209}
1210
1211pub(crate) async fn proxy_enabled(context: &Context) -> String {
1213 translated(context, StockMessage::ProxyEnabled).await
1214}
1215
1216pub(crate) async fn proxy_description(context: &Context) -> String {
1218 translated(context, StockMessage::ProxyEnabledDescription).await
1219}
1220
1221pub(crate) async fn chat_unencrypted_explanation(context: &Context) -> String {
1223 translated(context, StockMessage::ChatUnencryptedExplanation).await
1224}
1225
1226impl Viewtype {
1227 pub async fn to_locale_string(&self, context: &Context) -> String {
1229 match self {
1230 Viewtype::Image => image(context).await,
1231 Viewtype::Gif => gif(context).await,
1232 Viewtype::Sticker => sticker(context).await,
1233 Viewtype::Audio => audio(context).await,
1234 Viewtype::Voice => voice_message(context).await,
1235 Viewtype::Video => video(context).await,
1236 Viewtype::File => file(context).await,
1237 Viewtype::Webxdc => "Mini App".to_owned(),
1238 Viewtype::Vcard => "👤".to_string(),
1239 Viewtype::Unknown | Viewtype::Text | Viewtype::Call => self.to_string(),
1241 }
1242 }
1243}
1244
1245impl Context {
1246 pub async fn set_stock_translation(&self, id: StockMessage, stockstring: String) -> Result<()> {
1249 self.translated_stockstrings
1250 .set_stock_translation(id, stockstring)
1251 .await?;
1252 Ok(())
1253 }
1254
1255 pub(crate) async fn update_device_chats(&self) -> Result<()> {
1256 if self.get_config_bool(Config::Bot).await? {
1257 return Ok(());
1258 }
1259
1260 if !self.sql.get_raw_config_bool("self-chat-added").await? {
1263 self.sql
1264 .set_raw_config_bool("self-chat-added", true)
1265 .await?;
1266 ChatId::create_for_contact(self, ContactId::SELF).await?;
1267 }
1268
1269 let image = include_bytes!("../assets/welcome-image.jpg");
1272 let blob = BlobObject::create_and_deduplicate_from_bytes(self, image, "welcome.jpg")?;
1273 let mut msg = Message::new(Viewtype::Image);
1274 msg.param.set(Param::File, blob.as_name());
1275 msg.param.set(Param::Filename, "welcome-image.jpg");
1276 chat::add_device_msg(self, Some("core-welcome-image"), Some(&mut msg)).await?;
1277
1278 let mut msg = Message::new_text(welcome_message(self).await);
1279 chat::add_device_msg(self, Some("core-welcome"), Some(&mut msg)).await?;
1280 Ok(())
1281 }
1282}
1283
1284impl Accounts {
1285 pub async fn set_stock_translation(&self, id: StockMessage, stockstring: String) -> Result<()> {
1288 self.stockstrings
1289 .set_stock_translation(id, stockstring)
1290 .await?;
1291 Ok(())
1292 }
1293}
1294
1295#[cfg(test)]
1296mod stock_str_tests;