diff --git a/apps/ios/SimpleX Localizations/bg.xcloc/Localized Contents/bg.xliff b/apps/ios/SimpleX Localizations/bg.xcloc/Localized Contents/bg.xliff
index 2b8613f929..77133907fb 100644
--- a/apps/ios/SimpleX Localizations/bg.xcloc/Localized Contents/bg.xliff
+++ b/apps/ios/SimpleX Localizations/bg.xcloc/Localized Contents/bg.xliff
@@ -1966,6 +1966,14 @@ This cannot be undone!
Криптирано съобщение: неочаквана грешка
notification
+
+ Encryption re-negotiation error
+ message decrypt error item
+
+
+ Encryption re-negotiation failed.
+ No comment provided by engineer.
+
Enter Passcode
Въведете kодa за достъп
diff --git a/apps/ios/SimpleX Localizations/cs.xcloc/Localized Contents/cs.xliff b/apps/ios/SimpleX Localizations/cs.xcloc/Localized Contents/cs.xliff
index 663dcb022a..fe78e2da82 100644
--- a/apps/ios/SimpleX Localizations/cs.xcloc/Localized Contents/cs.xliff
+++ b/apps/ios/SimpleX Localizations/cs.xcloc/Localized Contents/cs.xliff
@@ -1966,6 +1966,14 @@ This cannot be undone!
Šifrovaná zpráva: neočekávaná chyba
notification
+
+ Encryption re-negotiation error
+ message decrypt error item
+
+
+ Encryption re-negotiation failed.
+ No comment provided by engineer.
+
Enter Passcode
Zadat heslo
diff --git a/apps/ios/SimpleX Localizations/de.xcloc/Localized Contents/de.xliff b/apps/ios/SimpleX Localizations/de.xcloc/Localized Contents/de.xliff
index 00cef659cf..259d7c12f2 100644
--- a/apps/ios/SimpleX Localizations/de.xcloc/Localized Contents/de.xliff
+++ b/apps/ios/SimpleX Localizations/de.xcloc/Localized Contents/de.xliff
@@ -89,11 +89,12 @@
%@ and %@
+ %@ und %@
No comment provided by engineer.
%@ and %@ connected
- %@ und %@ wurden verbunden
+ %@ und %@ wurden mit Ihnen verbunden
No comment provided by engineer.
@@ -103,6 +104,7 @@
%@ connected
+ %@ wurde mit Ihnen verbunden
No comment provided by engineer.
@@ -132,11 +134,12 @@
%@, %@ and %lld members
+ %@, %@ und %lld Mitglieder
No comment provided by engineer.
%@, %@ and %lld other members connected
- %@, %@ und %lld weitere Mitglieder wurden verbunden
+ %@, %@ und %lld weitere Mitglieder wurden mit Ihnen verbunden
No comment provided by engineer.
@@ -201,6 +204,7 @@
%lld group events
+ %lld Gruppenereignisse
No comment provided by engineer.
@@ -210,14 +214,17 @@
%lld messages blocked
+ %lld Nachrichten blockiert
No comment provided by engineer.
%lld messages marked deleted
+ %lld Nachrichten als gelöscht markiert
No comment provided by engineer.
%lld messages moderated by %@
+ %lld Nachrichten von %@ moderiert
No comment provided by engineer.
@@ -394,6 +401,7 @@
0 sec
+ 0 sek
time to disappear
@@ -623,6 +631,7 @@
All new messages from %@ will be hidden!
+ Alle neuen Nachrichten von %@ werden verborgen!
No comment provided by engineer.
@@ -732,10 +741,12 @@
Already connecting!
+ Bereits verbunden!
No comment provided by engineer.
Already joining the group!
+ Sie sind bereits Mitglied der Gruppe!
No comment provided by engineer.
@@ -875,14 +886,17 @@
Block
+ Blockieren
No comment provided by engineer.
Block member
+ Mitglied blockieren
No comment provided by engineer.
Block member?
+ Mitglied blockieren?
No comment provided by engineer.
@@ -1153,20 +1167,26 @@
Connect to yourself?
+ Mit Ihnen selbst verbinden?
No comment provided by engineer.
Connect to yourself?
This is your own SimpleX address!
+ Mit Ihnen selbst verbinden?
+Das ist Ihre eigene SimpleX-Adresse!
No comment provided by engineer.
Connect to yourself?
This is your own one-time link!
+ Mit Ihnen selbst verbinden?
+Das ist Ihr eigener Einmal-Link!
No comment provided by engineer.
Connect via contact address
+ Über die Kontakt-Adresse verbinden
No comment provided by engineer.
@@ -1186,6 +1206,7 @@ This is your own one-time link!
Connect with %@
+ Mit %@ verbinden
No comment provided by engineer.
@@ -1285,6 +1306,7 @@ This is your own one-time link!
Correct name to %@?
+ Richtiger Name für %@?
No comment provided by engineer.
@@ -1309,6 +1331,7 @@ This is your own one-time link!
Create group
+ Gruppe erstellen
No comment provided by engineer.
@@ -1333,6 +1356,7 @@ This is your own one-time link!
Create profile
+ Profil erstellen
No comment provided by engineer.
@@ -1495,6 +1519,7 @@ This is your own one-time link!
Delete %lld messages?
+ %lld Nachrichten löschen?
No comment provided by engineer.
@@ -1524,6 +1549,7 @@ This is your own one-time link!
Delete and notify contact
+ Kontakt löschen und benachrichtigen
No comment provided by engineer.
@@ -1559,6 +1585,8 @@ This is your own one-time link!
Delete contact?
This cannot be undone!
+ Kontakt löschen?
+Das kann nicht rückgängig gemacht werden!
No comment provided by engineer.
@@ -1966,6 +1994,14 @@ This cannot be undone!
Verschlüsselte Nachricht: Unerwarteter Fehler
notification
+
+ Encryption re-negotiation error
+ message decrypt error item
+
+
+ Encryption re-negotiation failed.
+ No comment provided by engineer.
+
Enter Passcode
Zugangscode eingeben
@@ -1978,6 +2014,7 @@ This cannot be undone!
Enter group name…
+ Geben Sie den Gruppennamen ein…
No comment provided by engineer.
@@ -2007,6 +2044,7 @@ This cannot be undone!
Enter your name…
+ Geben Sie Ihren Namen ein…
No comment provided by engineer.
@@ -2286,6 +2324,7 @@ This cannot be undone!
Expand
+ Erweitern
chat item action
@@ -2435,6 +2474,7 @@ This cannot be undone!
Fully decentralized – visible only to members.
+ Vollständig dezentralisiert – nur für Mitglieder sichtbar.
No comment provided by engineer.
@@ -2459,10 +2499,12 @@ This cannot be undone!
Group already exists
+ Die Gruppe besteht bereits
No comment provided by engineer.
Group already exists!
+ Die Gruppe besteht bereits!
No comment provided by engineer.
@@ -2814,6 +2856,7 @@ This cannot be undone!
Invalid name!
+ Ungültiger Name!
No comment provided by engineer.
@@ -2884,7 +2927,7 @@ This cannot be undone!
It seems like you are already connected via this link. If it is not the case, there was an error (%@).
- Es sieht so aus, dass Sie bereits über diesen Link verbunden sind. Wenn das nicht der Fall, gab es einen Fehler (%@).
+ Es sieht so aus, als ob Sie bereits über diesen Link verbunden sind. Wenn das nicht der Fall ist, gab es einen Fehler (%@).
No comment provided by engineer.
@@ -2909,6 +2952,7 @@ This cannot be undone!
Join group?
+ Der Gruppe beitreten?
No comment provided by engineer.
@@ -2918,11 +2962,14 @@ This cannot be undone!
Join with current profile
+ Mit dem aktuellen Profil beitreten
No comment provided by engineer.
Join your group?
This is your link for group %@!
+ Ihrer Gruppe beitreten?
+Das ist Ihr Link für die Gruppe %@!
No comment provided by engineer.
@@ -3142,6 +3189,7 @@ This is your link for group %@!
Messages from %@ will be shown!
+ Die Nachrichten von %@ werden angezeigt!
No comment provided by engineer.
@@ -3495,6 +3543,7 @@ This is your link for group %@!
Open group
+ Gruppe öffnen
No comment provided by engineer.
@@ -3704,10 +3753,12 @@ This is your link for group %@!
Profile name
+ Profilname
No comment provided by engineer.
Profile name:
+ Profilname:
No comment provided by engineer.
@@ -3957,10 +4008,12 @@ This is your link for group %@!
Repeat connection request?
+ Verbindungsanfrage wiederholen?
No comment provided by engineer.
Repeat join request?
+ Verbindungsanfrage wiederholen?
No comment provided by engineer.
@@ -4680,6 +4733,7 @@ This is your link for group %@!
Tap to Connect
+ Zum Verbinden antippen
No comment provided by engineer.
@@ -4876,10 +4930,12 @@ Dies kann passieren, wenn es einen Fehler gegeben hat oder die Verbindung kompro
This is your own SimpleX address!
+ Das ist Ihre eigene SimpleX-Adresse!
No comment provided by engineer.
This is your own one-time link!
+ Das ist Ihr eigener Einmal-Link!
No comment provided by engineer.
@@ -4926,7 +4982,7 @@ Sie werden aufgefordert, die Authentifizierung abzuschließen, bevor diese Funkt
To reveal your hidden profile, enter a full password into a search field in **Your chat profiles** page.
- Geben Sie ein vollständiges Passwort in das Suchfeld auf der Seite **Meine Chat-Profile** ein, um Ihr verborgenes Profil zu sehen.
+ Geben Sie ein vollständiges Passwort in das Suchfeld auf der Seite **Ihre Chat-Profile** ein, um Ihr verborgenes Profil zu sehen.
No comment provided by engineer.
@@ -4981,14 +5037,17 @@ Sie werden aufgefordert, die Authentifizierung abzuschließen, bevor diese Funkt
Unblock
+ Freigeben
No comment provided by engineer.
Unblock member
+ Mitglied freigeben
No comment provided by engineer.
Unblock member?
+ Mitglied freigeben?
No comment provided by engineer.
@@ -5315,7 +5374,7 @@ Bitten Sie Ihren Kontakt darum einen weiteren Verbindungs-Link zu erzeugen, um s
You
- Meine Daten
+ Ihre Daten
No comment provided by engineer.
@@ -5340,31 +5399,39 @@ Bitten Sie Ihren Kontakt darum einen weiteren Verbindungs-Link zu erzeugen, um s
You are already connecting to %@.
+ Sie sind bereits mit %@ verbunden.
No comment provided by engineer.
You are already connecting via this one-time link!
+ Sie sind bereits über diesen Einmal-Link verbunden!
No comment provided by engineer.
You are already in group %@.
+ Sie sind bereits Mitglied der Gruppe %@.
No comment provided by engineer.
You are already joining the group %@.
+ Sie sind bereits Mitglied der Gruppe %@.
No comment provided by engineer.
You are already joining the group via this link!
+ Sie sind über diesen Link bereits Mitglied der Gruppe!
No comment provided by engineer.
You are already joining the group via this link.
+ Sie sind über diesen Link bereits Mitglied der Gruppe.
No comment provided by engineer.
You are already joining the group!
Repeat join request?
+ Sie sind bereits Mitglied dieser Gruppe!
+Verbindungsanfrage wiederholen?
No comment provided by engineer.
@@ -5464,11 +5531,14 @@ Repeat join request?
You have already requested connection via this address!
+ Sie haben über diese Adresse bereits eine Verbindung beantragt!
No comment provided by engineer.
You have already requested connection!
Repeat connection request?
+ Sie haben bereits ein Verbindungsanfrage beantragt!
+Verbindungsanfrage wiederholen?
No comment provided by engineer.
@@ -5523,6 +5593,7 @@ Repeat connection request?
You will be connected when group link host's device is online, please wait or check later!
+ Sie werden verbunden, sobald das Endgerät des Gruppenlink-Hosts online ist. Bitte warten oder schauen Sie später nochmal nach!
No comment provided by engineer.
@@ -5542,6 +5613,7 @@ Repeat connection request?
You will connect to all group members.
+ Sie werden mit allen Gruppenmitgliedern verbunden.
No comment provided by engineer.
@@ -5586,7 +5658,7 @@ Repeat connection request?
Your SimpleX address
- Meine SimpleX-Adresse
+ Ihre SimpleX-Adresse
No comment provided by engineer.
@@ -5611,7 +5683,7 @@ Repeat connection request?
Your chat profiles
- Meine Chat-Profile
+ Ihre Chat-Profile
No comment provided by engineer.
@@ -5655,16 +5727,17 @@ Sie können es in den Einstellungen ändern.
Your preferences
- Meine Präferenzen
+ Ihre Präferenzen
No comment provided by engineer.
Your privacy
- Meine Privatsphäre
+ Ihre Privatsphäre
No comment provided by engineer.
Your profile
+ Mein Profil
No comment provided by engineer.
@@ -5701,7 +5774,7 @@ SimpleX-Server können Ihr Profil nicht einsehen.
Your settings
- Meine Einstellungen
+ Ihre Einstellungen
No comment provided by engineer.
@@ -5761,6 +5834,7 @@ SimpleX-Server können Ihr Profil nicht einsehen.
and %lld other events
+ und %lld weitere Ereignisse
No comment provided by engineer.
@@ -5780,6 +5854,7 @@ SimpleX-Server können Ihr Profil nicht einsehen.
blocked
+ blockiert
No comment provided by engineer.
@@ -5954,6 +6029,7 @@ SimpleX-Server können Ihr Profil nicht einsehen.
deleted contact
+ Gelöschter Kontakt
rcv direct event chat item
diff --git a/apps/ios/SimpleX Localizations/en.xcloc/Localized Contents/en.xliff b/apps/ios/SimpleX Localizations/en.xcloc/Localized Contents/en.xliff
index 696465adcf..aa128d59d9 100644
--- a/apps/ios/SimpleX Localizations/en.xcloc/Localized Contents/en.xliff
+++ b/apps/ios/SimpleX Localizations/en.xcloc/Localized Contents/en.xliff
@@ -1994,6 +1994,16 @@ This cannot be undone!
Encrypted message: unexpected error
notification
+
+ Encryption re-negotiation error
+ Encryption re-negotiation error
+ message decrypt error item
+
+
+ Encryption re-negotiation failed.
+ Encryption re-negotiation failed.
+ No comment provided by engineer.
+
Enter Passcode
Enter Passcode
diff --git a/apps/ios/SimpleX Localizations/es.xcloc/Localized Contents/es.xliff b/apps/ios/SimpleX Localizations/es.xcloc/Localized Contents/es.xliff
index a27d02aa68..4a76e3ddb4 100644
--- a/apps/ios/SimpleX Localizations/es.xcloc/Localized Contents/es.xliff
+++ b/apps/ios/SimpleX Localizations/es.xcloc/Localized Contents/es.xliff
@@ -1966,6 +1966,14 @@ This cannot be undone!
Mensaje cifrado: error inesperado
notification
+
+ Encryption re-negotiation error
+ message decrypt error item
+
+
+ Encryption re-negotiation failed.
+ No comment provided by engineer.
+
Enter Passcode
Introduce Código
diff --git a/apps/ios/SimpleX Localizations/fi.xcloc/Localized Contents/fi.xliff b/apps/ios/SimpleX Localizations/fi.xcloc/Localized Contents/fi.xliff
index 10e249cfdf..2661dc10a1 100644
--- a/apps/ios/SimpleX Localizations/fi.xcloc/Localized Contents/fi.xliff
+++ b/apps/ios/SimpleX Localizations/fi.xcloc/Localized Contents/fi.xliff
@@ -1960,6 +1960,14 @@ This cannot be undone!
Salattu viesti: odottamaton virhe
notification
+
+ Encryption re-negotiation error
+ message decrypt error item
+
+
+ Encryption re-negotiation failed.
+ No comment provided by engineer.
+
Enter Passcode
Syötä pääsykoodi
diff --git a/apps/ios/SimpleX Localizations/fr.xcloc/Localized Contents/fr.xliff b/apps/ios/SimpleX Localizations/fr.xcloc/Localized Contents/fr.xliff
index 381a50fe8f..6a5f397557 100644
--- a/apps/ios/SimpleX Localizations/fr.xcloc/Localized Contents/fr.xliff
+++ b/apps/ios/SimpleX Localizations/fr.xcloc/Localized Contents/fr.xliff
@@ -89,6 +89,7 @@
%@ and %@
+ %@ et %@
No comment provided by engineer.
@@ -103,6 +104,7 @@
%@ connected
+ %@ connecté(e)
No comment provided by engineer.
@@ -132,6 +134,7 @@
%@, %@ and %lld members
+ %@, %@ et %lld membres
No comment provided by engineer.
@@ -201,6 +204,7 @@
%lld group events
+ %lld événements de groupe
No comment provided by engineer.
@@ -210,14 +214,17 @@
%lld messages blocked
+ %lld messages bloqués
No comment provided by engineer.
%lld messages marked deleted
+ %lld messages marqués comme supprimés
No comment provided by engineer.
%lld messages moderated by %@
+ %lld messages modérés par %@
No comment provided by engineer.
@@ -394,6 +401,7 @@
0 sec
+ 0 sec
time to disappear
@@ -623,6 +631,7 @@
All new messages from %@ will be hidden!
+ Tous les nouveaux messages de %@ seront cachés !
No comment provided by engineer.
@@ -732,10 +741,12 @@
Already connecting!
+ Déjà en connexion !
No comment provided by engineer.
Already joining the group!
+ Groupe déjà rejoint !
No comment provided by engineer.
@@ -875,14 +886,17 @@
Block
+ Bloquer
No comment provided by engineer.
Block member
+ Bloquer ce membre
No comment provided by engineer.
Block member?
+ Bloquer ce membre ?
No comment provided by engineer.
@@ -1153,20 +1167,26 @@
Connect to yourself?
+ Se connecter à soi-même ?
No comment provided by engineer.
Connect to yourself?
This is your own SimpleX address!
+ Se connecter à soi-même ?
+C'est votre propre adresse SimpleX !
No comment provided by engineer.
Connect to yourself?
This is your own one-time link!
+ Se connecter à soi-même ?
+Il s'agit de votre propre lien unique !
No comment provided by engineer.
Connect via contact address
+ Se connecter via l'adresse de contact
No comment provided by engineer.
@@ -1186,6 +1206,7 @@ This is your own one-time link!
Connect with %@
+ Se connecter avec %@
No comment provided by engineer.
@@ -1285,6 +1306,7 @@ This is your own one-time link!
Correct name to %@?
+ Corriger le nom pour %@ ?
No comment provided by engineer.
@@ -1309,6 +1331,7 @@ This is your own one-time link!
Create group
+ Créer un groupe
No comment provided by engineer.
@@ -1333,6 +1356,7 @@ This is your own one-time link!
Create profile
+ Créer le profil
No comment provided by engineer.
@@ -1495,6 +1519,7 @@ This is your own one-time link!
Delete %lld messages?
+ Supprimer %lld messages ?
No comment provided by engineer.
@@ -1524,6 +1549,7 @@ This is your own one-time link!
Delete and notify contact
+ Supprimer et en informer le contact
No comment provided by engineer.
@@ -1559,6 +1585,8 @@ This is your own one-time link!
Delete contact?
This cannot be undone!
+ Supprimer le contact ?
+Cette opération ne peut être annulée !
No comment provided by engineer.
@@ -1966,6 +1994,14 @@ This cannot be undone!
Message chiffrée : erreur inattendue
notification
+
+ Encryption re-negotiation error
+ message decrypt error item
+
+
+ Encryption re-negotiation failed.
+ No comment provided by engineer.
+
Enter Passcode
Entrer le code d'accès
@@ -1978,6 +2014,7 @@ This cannot be undone!
Enter group name…
+ Entrer un nom de groupe…
No comment provided by engineer.
@@ -2007,6 +2044,7 @@ This cannot be undone!
Enter your name…
+ Entrez votre nom…
No comment provided by engineer.
@@ -2286,6 +2324,7 @@ This cannot be undone!
Expand
+ Développer
chat item action
@@ -2435,6 +2474,7 @@ This cannot be undone!
Fully decentralized – visible only to members.
+ Entièrement décentralisé – visible que par ses membres.
No comment provided by engineer.
@@ -2459,10 +2499,12 @@ This cannot be undone!
Group already exists
+ Le groupe existe déjà
No comment provided by engineer.
Group already exists!
+ Ce groupe existe déjà !
No comment provided by engineer.
@@ -2814,6 +2856,7 @@ This cannot be undone!
Invalid name!
+ Nom invalide !
No comment provided by engineer.
@@ -2909,6 +2952,7 @@ This cannot be undone!
Join group?
+ Rejoindre le groupe ?
No comment provided by engineer.
@@ -2918,11 +2962,14 @@ This cannot be undone!
Join with current profile
+ Rejoindre avec le profil actuel
No comment provided by engineer.
Join your group?
This is your link for group %@!
+ Rejoindre votre groupe ?
+Voici votre lien pour le groupe %@ !
No comment provided by engineer.
@@ -3142,6 +3189,7 @@ This is your link for group %@!
Messages from %@ will be shown!
+ Les messages de %@ seront affichés !
No comment provided by engineer.
@@ -3495,6 +3543,7 @@ This is your link for group %@!
Open group
+ Ouvrir le groupe
No comment provided by engineer.
@@ -3704,10 +3753,12 @@ This is your link for group %@!
Profile name
+ Nom du profil
No comment provided by engineer.
Profile name:
+ Nom du profil :
No comment provided by engineer.
@@ -3957,10 +4008,12 @@ This is your link for group %@!
Repeat connection request?
+ Répéter la demande de connexion ?
No comment provided by engineer.
Repeat join request?
+ Répéter la requête d'adhésion ?
No comment provided by engineer.
@@ -4680,6 +4733,7 @@ This is your link for group %@!
Tap to Connect
+ Tapez pour vous connecter
No comment provided by engineer.
@@ -4876,10 +4930,12 @@ Cela peut se produire en raison d'un bug ou lorsque la connexion est compromise.
This is your own SimpleX address!
+ Voici votre propre adresse SimpleX !
No comment provided by engineer.
This is your own one-time link!
+ Voici votre propre lien unique !
No comment provided by engineer.
@@ -4981,14 +5037,17 @@ Vous serez invité à confirmer l'authentification avant que cette fonction ne s
Unblock
+ Débloquer
No comment provided by engineer.
Unblock member
+ Débloquer ce membre
No comment provided by engineer.
Unblock member?
+ Débloquer ce membre ?
No comment provided by engineer.
@@ -5340,31 +5399,39 @@ Pour vous connecter, veuillez demander à votre contact de créer un autre lien
You are already connecting to %@.
+ Vous êtes déjà en train de vous connecter à %@.
No comment provided by engineer.
You are already connecting via this one-time link!
+ Vous êtes déjà connecté(e) via ce lien unique !
No comment provided by engineer.
You are already in group %@.
+ Vous êtes déjà dans le groupe %@.
No comment provided by engineer.
You are already joining the group %@.
+ Vous êtes déjà en train de rejoindre le groupe %@.
No comment provided by engineer.
You are already joining the group via this link!
+ Vous êtes déjà en train de rejoindre le groupe via ce lien !
No comment provided by engineer.
You are already joining the group via this link.
+ Vous êtes déjà en train de rejoindre le groupe via ce lien.
No comment provided by engineer.
You are already joining the group!
Repeat join request?
+ Vous êtes déjà membre de ce groupe !
+Répéter la demande d'adhésion ?
No comment provided by engineer.
@@ -5464,11 +5531,14 @@ Repeat join request?
You have already requested connection via this address!
+ Vous avez déjà demandé une connexion via cette adresse !
No comment provided by engineer.
You have already requested connection!
Repeat connection request?
+ Vous avez déjà demandé une connexion !
+Répéter la demande de connexion ?
No comment provided by engineer.
@@ -5523,6 +5593,7 @@ Repeat connection request?
You will be connected when group link host's device is online, please wait or check later!
+ Vous serez connecté(e) lorsque l'appareil de l'hôte du lien de groupe sera en ligne, veuillez patienter ou vérifier plus tard !
No comment provided by engineer.
@@ -5542,6 +5613,7 @@ Repeat connection request?
You will connect to all group members.
+ Vous vous connecterez à tous les membres du groupe.
No comment provided by engineer.
@@ -5665,6 +5737,7 @@ Vous pouvez modifier ce choix dans les Paramètres.
Your profile
+ Votre profil
No comment provided by engineer.
@@ -5761,6 +5834,7 @@ Les serveurs SimpleX ne peuvent pas voir votre profil.
and %lld other events
+ et %lld autres événements
No comment provided by engineer.
@@ -5780,6 +5854,7 @@ Les serveurs SimpleX ne peuvent pas voir votre profil.
blocked
+ blocké
No comment provided by engineer.
@@ -5954,6 +6029,7 @@ Les serveurs SimpleX ne peuvent pas voir votre profil.
deleted contact
+ contact supprimé
rcv direct event chat item
diff --git a/apps/ios/SimpleX Localizations/it.xcloc/Localized Contents/it.xliff b/apps/ios/SimpleX Localizations/it.xcloc/Localized Contents/it.xliff
index 1faabca974..72cda836ac 100644
--- a/apps/ios/SimpleX Localizations/it.xcloc/Localized Contents/it.xliff
+++ b/apps/ios/SimpleX Localizations/it.xcloc/Localized Contents/it.xliff
@@ -89,6 +89,7 @@
%@ and %@
+ %@ e %@
No comment provided by engineer.
@@ -103,6 +104,7 @@
%@ connected
+ %@ si è connesso/a
No comment provided by engineer.
@@ -132,6 +134,7 @@
%@, %@ and %lld members
+ %@, %@ e %lld membri
No comment provided by engineer.
@@ -201,6 +204,7 @@
%lld group events
+ %lld eventi del gruppo
No comment provided by engineer.
@@ -210,14 +214,17 @@
%lld messages blocked
+ %lld messaggi bloccati
No comment provided by engineer.
%lld messages marked deleted
+ %lld messaggi contrassegnati eliminati
No comment provided by engineer.
%lld messages moderated by %@
+ %lld messaggi moderati da %@
No comment provided by engineer.
@@ -394,6 +401,7 @@
0 sec
+ 0 sec
time to disappear
@@ -623,6 +631,7 @@
All new messages from %@ will be hidden!
+ Tutti i nuovi messaggi da %@ verrranno nascosti!
No comment provided by engineer.
@@ -732,10 +741,12 @@
Already connecting!
+ Già in connessione!
No comment provided by engineer.
Already joining the group!
+ Già in ingresso nel gruppo!
No comment provided by engineer.
@@ -875,14 +886,17 @@
Block
+ Blocca
No comment provided by engineer.
Block member
+ Blocca membro
No comment provided by engineer.
Block member?
+ Bloccare il membro?
No comment provided by engineer.
@@ -1153,20 +1167,26 @@
Connect to yourself?
+ Connettersi a te stesso?
No comment provided by engineer.
Connect to yourself?
This is your own SimpleX address!
+ Connettersi a te stesso?
+Questo è il tuo indirizzo SimpleX!
No comment provided by engineer.
Connect to yourself?
This is your own one-time link!
+ Connettersi a te stesso?
+Questo è il tuo link una tantum!
No comment provided by engineer.
Connect via contact address
+ Connettere via indirizzo del contatto
No comment provided by engineer.
@@ -1186,6 +1206,7 @@ This is your own one-time link!
Connect with %@
+ Connettersi con %@
No comment provided by engineer.
@@ -1285,6 +1306,7 @@ This is your own one-time link!
Correct name to %@?
+ Correggere il nome a %@?
No comment provided by engineer.
@@ -1309,6 +1331,7 @@ This is your own one-time link!
Create group
+ Crea gruppo
No comment provided by engineer.
@@ -1333,6 +1356,7 @@ This is your own one-time link!
Create profile
+ Crea profilo
No comment provided by engineer.
@@ -1495,6 +1519,7 @@ This is your own one-time link!
Delete %lld messages?
+ Eliminare %lld messaggi?
No comment provided by engineer.
@@ -1524,6 +1549,7 @@ This is your own one-time link!
Delete and notify contact
+ Elimina e avvisa il contatto
No comment provided by engineer.
@@ -1559,6 +1585,8 @@ This is your own one-time link!
Delete contact?
This cannot be undone!
+ Eliminare il contatto?
+Non è reversibile!
No comment provided by engineer.
@@ -1966,6 +1994,14 @@ This cannot be undone!
Messaggio crittografato: errore imprevisto
notification
+
+ Encryption re-negotiation error
+ message decrypt error item
+
+
+ Encryption re-negotiation failed.
+ No comment provided by engineer.
+
Enter Passcode
Inserisci il codice di accesso
@@ -1978,6 +2014,7 @@ This cannot be undone!
Enter group name…
+ Inserisci il nome del gruppo…
No comment provided by engineer.
@@ -2007,6 +2044,7 @@ This cannot be undone!
Enter your name…
+ Inserisci il tuo nome…
No comment provided by engineer.
@@ -2286,6 +2324,7 @@ This cannot be undone!
Expand
+ Espandi
chat item action
@@ -2435,6 +2474,7 @@ This cannot be undone!
Fully decentralized – visible only to members.
+ Completamente decentralizzato: visibile solo ai membri.
No comment provided by engineer.
@@ -2459,10 +2499,12 @@ This cannot be undone!
Group already exists
+ Il gruppo esiste già
No comment provided by engineer.
Group already exists!
+ Il gruppo esiste già!
No comment provided by engineer.
@@ -2814,6 +2856,7 @@ This cannot be undone!
Invalid name!
+ Nome non valido!
No comment provided by engineer.
@@ -2909,6 +2952,7 @@ This cannot be undone!
Join group?
+ Entrare nel gruppo?
No comment provided by engineer.
@@ -2918,11 +2962,14 @@ This cannot be undone!
Join with current profile
+ Entra con il profilo attuale
No comment provided by engineer.
Join your group?
This is your link for group %@!
+ Entrare nel tuo gruppo?
+Questo è il tuo link per il gruppo %@!
No comment provided by engineer.
@@ -3142,6 +3189,7 @@ This is your link for group %@!
Messages from %@ will be shown!
+ I messaggi da %@ verranno mostrati!
No comment provided by engineer.
@@ -3495,6 +3543,7 @@ This is your link for group %@!
Open group
+ Apri gruppo
No comment provided by engineer.
@@ -3704,10 +3753,12 @@ This is your link for group %@!
Profile name
+ Nome del profilo
No comment provided by engineer.
Profile name:
+ Nome del profilo:
No comment provided by engineer.
@@ -3957,10 +4008,12 @@ This is your link for group %@!
Repeat connection request?
+ Ripetere la richiesta di connessione?
No comment provided by engineer.
Repeat join request?
+ Ripetere la richiesta di ingresso?
No comment provided by engineer.
@@ -4680,6 +4733,7 @@ This is your link for group %@!
Tap to Connect
+ Tocca per connettere
No comment provided by engineer.
@@ -4876,10 +4930,12 @@ Può accadere a causa di qualche bug o quando la connessione è compromessa.
This is your own SimpleX address!
+ Questo è il tuo indirizzo SimpleX!
No comment provided by engineer.
This is your own one-time link!
+ Questo è il tuo link una tantum!
No comment provided by engineer.
@@ -4981,14 +5037,17 @@ Ti verrà chiesto di completare l'autenticazione prima di attivare questa funzio
Unblock
+ Sblocca
No comment provided by engineer.
Unblock member
+ Sblocca membro
No comment provided by engineer.
Unblock member?
+ Sbloccare il membro?
No comment provided by engineer.
@@ -5340,31 +5399,39 @@ Per connetterti, chiedi al tuo contatto di creare un altro link di connessione e
You are already connecting to %@.
+ Ti stai già connettendo a %@.
No comment provided by engineer.
You are already connecting via this one-time link!
+ Ti stai già connettendo tramite questo link una tantum!
No comment provided by engineer.
You are already in group %@.
+ Sei già nel gruppo %@.
No comment provided by engineer.
You are already joining the group %@.
+ Stai già entrando nel gruppo %@.
No comment provided by engineer.
You are already joining the group via this link!
+ Stai già entrando nel gruppo tramite questo link!
No comment provided by engineer.
You are already joining the group via this link.
+ Stai già entrando nel gruppo tramite questo link.
No comment provided by engineer.
You are already joining the group!
Repeat join request?
+ Stai già entrando nel gruppo!
+Ripetere la richiesta di ingresso?
No comment provided by engineer.
@@ -5464,11 +5531,14 @@ Repeat join request?
You have already requested connection via this address!
+ Hai già richiesto la connessione tramite questo indirizzo!
No comment provided by engineer.
You have already requested connection!
Repeat connection request?
+ Hai già richiesto la connessione!
+Ripetere la richiesta di connessione?
No comment provided by engineer.
@@ -5523,6 +5593,7 @@ Repeat connection request?
You will be connected when group link host's device is online, please wait or check later!
+ Verrai connesso/a quando il dispositivo dell'host del gruppo sarà in linea, attendi o controlla più tardi!
No comment provided by engineer.
@@ -5542,6 +5613,7 @@ Repeat connection request?
You will connect to all group members.
+ Ti connetterai a tutti i membri del gruppo.
No comment provided by engineer.
@@ -5665,6 +5737,7 @@ Puoi modificarlo nelle impostazioni.
Your profile
+ Il tuo profilo
No comment provided by engineer.
@@ -5761,6 +5834,7 @@ I server di SimpleX non possono vedere il tuo profilo.
and %lld other events
+ e altri %lld eventi
No comment provided by engineer.
@@ -5780,6 +5854,7 @@ I server di SimpleX non possono vedere il tuo profilo.
blocked
+ bloccato
No comment provided by engineer.
@@ -5814,7 +5889,7 @@ I server di SimpleX non possono vedere il tuo profilo.
changed role of %1$@ to %2$@
- cambiato il ruolo di %1$@ in %2$@
+ ha cambiato il ruolo di %1$@ in %2$@
rcv group event chat item
@@ -5954,6 +6029,7 @@ I server di SimpleX non possono vedere il tuo profilo.
deleted contact
+ contatto eliminato
rcv direct event chat item
diff --git a/apps/ios/SimpleX Localizations/ja.xcloc/Localized Contents/ja.xliff b/apps/ios/SimpleX Localizations/ja.xcloc/Localized Contents/ja.xliff
index 9f688d1ff5..b574faecb1 100644
--- a/apps/ios/SimpleX Localizations/ja.xcloc/Localized Contents/ja.xliff
+++ b/apps/ios/SimpleX Localizations/ja.xcloc/Localized Contents/ja.xliff
@@ -1963,6 +1963,14 @@ This cannot be undone!
暗号化されたメッセージ : 予期しないエラー
notification
+
+ Encryption re-negotiation error
+ message decrypt error item
+
+
+ Encryption re-negotiation failed.
+ No comment provided by engineer.
+
Enter Passcode
パスコードを入力
diff --git a/apps/ios/SimpleX Localizations/nl.xcloc/Localized Contents/nl.xliff b/apps/ios/SimpleX Localizations/nl.xcloc/Localized Contents/nl.xliff
index 257b2ff0ce..92cb85456f 100644
--- a/apps/ios/SimpleX Localizations/nl.xcloc/Localized Contents/nl.xliff
+++ b/apps/ios/SimpleX Localizations/nl.xcloc/Localized Contents/nl.xliff
@@ -89,6 +89,7 @@
%@ and %@
+ %@ en %@
No comment provided by engineer.
@@ -103,6 +104,7 @@
%@ connected
+ %@ verbonden
No comment provided by engineer.
@@ -132,6 +134,7 @@
%@, %@ and %lld members
+ %@, %@ en %lld leden
No comment provided by engineer.
@@ -201,6 +204,7 @@
%lld group events
+ %lld groep gebeurtenissen
No comment provided by engineer.
@@ -210,14 +214,17 @@
%lld messages blocked
+ %lld berichten geblokkeerd
No comment provided by engineer.
%lld messages marked deleted
+ %lld berichten gemarkeerd als verwijderd
No comment provided by engineer.
%lld messages moderated by %@
+ %lld berichten gemodereerd door %@
No comment provided by engineer.
@@ -394,6 +401,7 @@
0 sec
+ 0 sec
time to disappear
@@ -623,6 +631,7 @@
All new messages from %@ will be hidden!
+ Alle nieuwe berichten van %@ worden verborgen!
No comment provided by engineer.
@@ -732,10 +741,12 @@
Already connecting!
+ Al bezig met verbinden!
No comment provided by engineer.
Already joining the group!
+ Al lid van de groep!
No comment provided by engineer.
@@ -875,14 +886,17 @@
Block
+ Blokkeren
No comment provided by engineer.
Block member
+ Lid blokkeren
No comment provided by engineer.
Block member?
+ Lid blokkeren?
No comment provided by engineer.
@@ -977,7 +991,7 @@
Change member role?
- Rol van gebruiker wijzigen?
+ Rol van lid wijzigen?
No comment provided by engineer.
@@ -1153,20 +1167,26 @@
Connect to yourself?
+ Verbinding maken met jezelf?
No comment provided by engineer.
Connect to yourself?
This is your own SimpleX address!
+ Verbinding maken met jezelf?
+Dit is uw eigen SimpleX adres!
No comment provided by engineer.
Connect to yourself?
This is your own one-time link!
+ Verbinding maken met jezelf?
+Dit is uw eigen eenmalige link!
No comment provided by engineer.
Connect via contact address
+ Verbinding maken via contactadres
No comment provided by engineer.
@@ -1186,6 +1206,7 @@ This is your own one-time link!
Connect with %@
+ Verbonden met %@
No comment provided by engineer.
@@ -1285,6 +1306,7 @@ This is your own one-time link!
Correct name to %@?
+ Juiste naam voor %@?
No comment provided by engineer.
@@ -1309,6 +1331,7 @@ This is your own one-time link!
Create group
+ Groep aanmaken
No comment provided by engineer.
@@ -1333,6 +1356,7 @@ This is your own one-time link!
Create profile
+ Maak een profiel aan
No comment provided by engineer.
@@ -1495,6 +1519,7 @@ This is your own one-time link!
Delete %lld messages?
+ %lld berichten verwijderen?
No comment provided by engineer.
@@ -1524,6 +1549,7 @@ This is your own one-time link!
Delete and notify contact
+ Contact verwijderen en op de hoogte stellen
No comment provided by engineer.
@@ -1559,6 +1585,8 @@ This is your own one-time link!
Delete contact?
This cannot be undone!
+ Verwijder contact?
+Dit kan niet ongedaan gemaakt worden!
No comment provided by engineer.
@@ -1966,6 +1994,14 @@ This cannot be undone!
Versleuteld bericht: onverwachte fout
notification
+
+ Encryption re-negotiation error
+ message decrypt error item
+
+
+ Encryption re-negotiation failed.
+ No comment provided by engineer.
+
Enter Passcode
Voer toegangscode in
@@ -1978,6 +2014,7 @@ This cannot be undone!
Enter group name…
+ Groep naam invoeren…
No comment provided by engineer.
@@ -2007,6 +2044,7 @@ This cannot be undone!
Enter your name…
+ Vul uw naam in…
No comment provided by engineer.
@@ -2031,7 +2069,7 @@ This cannot be undone!
Error adding member(s)
- Fout bij het toevoegen van gebruiker(s)
+ Fout bij het toevoegen van leden
No comment provided by engineer.
@@ -2161,7 +2199,7 @@ This cannot be undone!
Error removing member
- Fout bij verwijderen van gebruiker
+ Fout bij verwijderen van lid
No comment provided by engineer.
@@ -2286,6 +2324,7 @@ This cannot be undone!
Expand
+ Uitbreiden
chat item action
@@ -2435,6 +2474,7 @@ This cannot be undone!
Fully decentralized – visible only to members.
+ Volledig gedecentraliseerd – alleen zichtbaar voor leden.
No comment provided by engineer.
@@ -2459,10 +2499,12 @@ This cannot be undone!
Group already exists
+ Groep bestaat al
No comment provided by engineer.
Group already exists!
+ Groep bestaat al!
No comment provided by engineer.
@@ -2814,6 +2856,7 @@ This cannot be undone!
Invalid name!
+ Ongeldige naam!
No comment provided by engineer.
@@ -2909,6 +2952,7 @@ This cannot be undone!
Join group?
+ Deelnemen aan groep?
No comment provided by engineer.
@@ -2918,11 +2962,14 @@ This cannot be undone!
Join with current profile
+ Word lid met huidig profiel
No comment provided by engineer.
Join your group?
This is your link for group %@!
+ Sluit u aan bij uw groep?
+Dit is jouw link voor groep %@!
No comment provided by engineer.
@@ -3077,22 +3124,22 @@ This is your link for group %@!
Member
- Gebruiker
+ Lid
No comment provided by engineer.
Member role will be changed to "%@". All group members will be notified.
- De rol van gebruiker wordt gewijzigd in "%@". Alle groepsleden worden op de hoogte gebracht.
+ De rol van lid wordt gewijzigd in "%@". Alle groepsleden worden op de hoogte gebracht.
No comment provided by engineer.
Member role will be changed to "%@". The member will receive a new invitation.
- De rol van gebruiker wordt gewijzigd in "%@". Het lid ontvangt een nieuwe uitnodiging.
+ De rol van lid wordt gewijzigd in "%@". Het lid ontvangt een nieuwe uitnodiging.
No comment provided by engineer.
Member will be removed from group - this cannot be undone!
- Gebruiker wordt uit de groep verwijderd, dit kan niet ongedaan worden gemaakt!
+ Lid wordt uit de groep verwijderd, dit kan niet ongedaan worden gemaakt!
No comment provided by engineer.
@@ -3142,6 +3189,7 @@ This is your link for group %@!
Messages from %@ will be shown!
+ Berichten van %@ worden getoond!
No comment provided by engineer.
@@ -3415,7 +3463,7 @@ This is your link for group %@!
Only group owners can enable files and media.
- Alleen groepseigenaren kunnen bestanden en media inschakelen.
+ Alleen groep eigenaren kunnen bestanden en media inschakelen.
No comment provided by engineer.
@@ -3495,6 +3543,7 @@ This is your link for group %@!
Open group
+ Open groep
No comment provided by engineer.
@@ -3704,10 +3753,12 @@ This is your link for group %@!
Profile name
+ Profielnaam
No comment provided by engineer.
Profile name:
+ Profielnaam:
No comment provided by engineer.
@@ -3927,12 +3978,12 @@ This is your link for group %@!
Remove member
- Gebruiker verwijderen
+ Lid verwijderen
No comment provided by engineer.
Remove member?
- Gebruiker verwijderen?
+ Lid verwijderen?
No comment provided by engineer.
@@ -3957,10 +4008,12 @@ This is your link for group %@!
Repeat connection request?
+ Verbindingsverzoek herhalen?
No comment provided by engineer.
Repeat join request?
+ Deelnameverzoek herhalen?
No comment provided by engineer.
@@ -4075,7 +4128,7 @@ This is your link for group %@!
Save and notify group members
- Opslaan en Groep leden melden
+ Opslaan en groep leden melden
No comment provided by engineer.
@@ -4680,6 +4733,7 @@ This is your link for group %@!
Tap to Connect
+ Tik om verbinding te maken
No comment provided by engineer.
@@ -4689,12 +4743,12 @@ This is your link for group %@!
Tap to join
- Tik om mee te doen
+ Tik om lid te worden
No comment provided by engineer.
Tap to join incognito
- Tik om incognito deel te nemen
+ Tik om incognito lid te worden
No comment provided by engineer.
@@ -4876,10 +4930,12 @@ Het kan gebeuren vanwege een bug of wanneer de verbinding is aangetast.
This is your own SimpleX address!
+ Dit is uw eigen SimpleX adres!
No comment provided by engineer.
This is your own one-time link!
+ Dit is uw eigen eenmalige link!
No comment provided by engineer.
@@ -4981,14 +5037,17 @@ U wordt gevraagd de authenticatie te voltooien voordat deze functie wordt ingesc
Unblock
+ Deblokkeren
No comment provided by engineer.
Unblock member
+ Lid deblokkeren
No comment provided by engineer.
Unblock member?
+ Lid deblokkeren?
No comment provided by engineer.
@@ -5340,31 +5399,39 @@ Om verbinding te maken, vraagt u uw contact om een andere verbinding link te mak
You are already connecting to %@.
+ U maakt al verbinding met %@.
No comment provided by engineer.
You are already connecting via this one-time link!
+ Je maakt al verbinding via deze eenmalige link!
No comment provided by engineer.
You are already in group %@.
+ Je zit al in groep %@.
No comment provided by engineer.
You are already joining the group %@.
+ Je bent al lid van de groep %@.
No comment provided by engineer.
You are already joining the group via this link!
+ Je wordt al lid van de groep via deze link!
No comment provided by engineer.
You are already joining the group via this link.
+ Je wordt al lid van de groep via deze link.
No comment provided by engineer.
You are already joining the group!
Repeat join request?
+ Je sluit je al aan bij de groep!
+Deelnameverzoek herhalen?
No comment provided by engineer.
@@ -5464,11 +5531,14 @@ Repeat join request?
You have already requested connection via this address!
+ U heeft al een verbinding aangevraagd via dit adres!
No comment provided by engineer.
You have already requested connection!
Repeat connection request?
+ Je hebt al verbinding aangevraagd!
+Verbindingsverzoek herhalen?
No comment provided by engineer.
@@ -5523,6 +5593,7 @@ Repeat connection request?
You will be connected when group link host's device is online, please wait or check later!
+ U wordt verbonden wanneer het apparaat van de groep link host online is. Wacht even of controleer het later opnieuw!
No comment provided by engineer.
@@ -5542,6 +5613,7 @@ Repeat connection request?
You will connect to all group members.
+ Je maakt verbinding met alle leden.
No comment provided by engineer.
@@ -5665,6 +5737,7 @@ U kunt dit wijzigen in Instellingen.
Your profile
+ Jouw profiel
No comment provided by engineer.
@@ -5761,6 +5834,7 @@ SimpleX servers kunnen uw profiel niet zien.
and %lld other events
+ en %lld andere gebeurtenissen
No comment provided by engineer.
@@ -5780,6 +5854,7 @@ SimpleX servers kunnen uw profiel niet zien.
blocked
+ geblokkeerd
No comment provided by engineer.
@@ -5954,6 +6029,7 @@ SimpleX servers kunnen uw profiel niet zien.
deleted contact
+ verwijderd contact
rcv direct event chat item
@@ -6168,7 +6244,7 @@ SimpleX servers kunnen uw profiel niet zien.
member
- gebruiker
+ lid
member role
diff --git a/apps/ios/SimpleX Localizations/pl.xcloc/Localized Contents/pl.xliff b/apps/ios/SimpleX Localizations/pl.xcloc/Localized Contents/pl.xliff
index 03ac0250a0..5ec6d3ee36 100644
--- a/apps/ios/SimpleX Localizations/pl.xcloc/Localized Contents/pl.xliff
+++ b/apps/ios/SimpleX Localizations/pl.xcloc/Localized Contents/pl.xliff
@@ -89,6 +89,7 @@
%@ and %@
+ %@ i %@
No comment provided by engineer.
@@ -103,6 +104,7 @@
%@ connected
+ %@ połączony
No comment provided by engineer.
@@ -132,6 +134,7 @@
%@, %@ and %lld members
+ %@, %@ i %lld członków
No comment provided by engineer.
@@ -201,6 +204,7 @@
%lld group events
+ %lld wydarzeń grupy
No comment provided by engineer.
@@ -210,14 +214,17 @@
%lld messages blocked
+ %lld wiadomości zablokowanych
No comment provided by engineer.
%lld messages marked deleted
+ %lld wiadomości oznaczonych do usunięcia
No comment provided by engineer.
%lld messages moderated by %@
+ %lld wiadomości zmoderowanych przez %@
No comment provided by engineer.
@@ -394,6 +401,7 @@
0 sec
+ 0 sek
time to disappear
@@ -623,6 +631,7 @@
All new messages from %@ will be hidden!
+ Wszystkie nowe wiadomości z %@ zostaną ukryte!
No comment provided by engineer.
@@ -732,10 +741,12 @@
Already connecting!
+ Już połączony!
No comment provided by engineer.
Already joining the group!
+ Już dołączono do grupy!
No comment provided by engineer.
@@ -875,14 +886,17 @@
Block
+ Zablokuj
No comment provided by engineer.
Block member
+ Zablokuj członka
No comment provided by engineer.
Block member?
+ Zablokować członka?
No comment provided by engineer.
@@ -1153,20 +1167,26 @@
Connect to yourself?
+ Połączyć się ze sobą?
No comment provided by engineer.
Connect to yourself?
This is your own SimpleX address!
+ Połączyć się ze sobą?
+To jest twój własny adres SimpleX!
No comment provided by engineer.
Connect to yourself?
This is your own one-time link!
+ Połączyć się ze sobą?
+To jest twój jednorazowy link!
No comment provided by engineer.
Connect via contact address
+ Połącz przez adres kontaktowy
No comment provided by engineer.
@@ -1186,6 +1206,7 @@ This is your own one-time link!
Connect with %@
+ Połącz z %@
No comment provided by engineer.
@@ -1285,6 +1306,7 @@ This is your own one-time link!
Correct name to %@?
+ Poprawić imię na %@?
No comment provided by engineer.
@@ -1309,6 +1331,7 @@ This is your own one-time link!
Create group
+ Utwórz grupę
No comment provided by engineer.
@@ -1333,6 +1356,7 @@ This is your own one-time link!
Create profile
+ Utwórz profil
No comment provided by engineer.
@@ -1495,6 +1519,7 @@ This is your own one-time link!
Delete %lld messages?
+ Usunąć %lld wiadomości?
No comment provided by engineer.
@@ -1524,6 +1549,7 @@ This is your own one-time link!
Delete and notify contact
+ Usuń i powiadom kontakt
No comment provided by engineer.
@@ -1559,6 +1585,8 @@ This is your own one-time link!
Delete contact?
This cannot be undone!
+ Usunąć kontakt?
+To nie może być cofnięte!
No comment provided by engineer.
@@ -1966,6 +1994,14 @@ This cannot be undone!
Zaszyfrowana wiadomość: nieoczekiwany błąd
notification
+
+ Encryption re-negotiation error
+ message decrypt error item
+
+
+ Encryption re-negotiation failed.
+ No comment provided by engineer.
+
Enter Passcode
Wprowadź Pin
@@ -1978,6 +2014,7 @@ This cannot be undone!
Enter group name…
+ Wpisz nazwę grupy…
No comment provided by engineer.
@@ -2007,6 +2044,7 @@ This cannot be undone!
Enter your name…
+ Wpisz swoją nazwę…
No comment provided by engineer.
@@ -2286,6 +2324,7 @@ This cannot be undone!
Expand
+ Rozszerz
chat item action
@@ -2435,6 +2474,7 @@ This cannot be undone!
Fully decentralized – visible only to members.
+ W pełni zdecentralizowana – widoczna tylko dla członków.
No comment provided by engineer.
@@ -2459,10 +2499,12 @@ This cannot be undone!
Group already exists
+ Grupa już istnieje
No comment provided by engineer.
Group already exists!
+ Grupa już istnieje!
No comment provided by engineer.
@@ -2814,6 +2856,7 @@ This cannot be undone!
Invalid name!
+ Nieprawidłowa nazwa!
No comment provided by engineer.
@@ -2909,6 +2952,7 @@ This cannot be undone!
Join group?
+ Dołączyć do grupy?
No comment provided by engineer.
@@ -2918,11 +2962,14 @@ This cannot be undone!
Join with current profile
+ Dołącz z obecnym profilem
No comment provided by engineer.
Join your group?
This is your link for group %@!
+ Dołączyć do twojej grupy?
+To jest twój link do grupy %@!
No comment provided by engineer.
@@ -3142,6 +3189,7 @@ This is your link for group %@!
Messages from %@ will be shown!
+ Wiadomości od %@ zostaną pokazane!
No comment provided by engineer.
@@ -3495,6 +3543,7 @@ This is your link for group %@!
Open group
+ Grupa otwarta
No comment provided by engineer.
@@ -3704,10 +3753,12 @@ This is your link for group %@!
Profile name
+ Nazwa profilu
No comment provided by engineer.
Profile name:
+ Nazwa profilu:
No comment provided by engineer.
@@ -3957,10 +4008,12 @@ This is your link for group %@!
Repeat connection request?
+ Powtórzyć prośbę połączenia?
No comment provided by engineer.
Repeat join request?
+ Powtórzyć prośbę dołączenia?
No comment provided by engineer.
@@ -4680,6 +4733,7 @@ This is your link for group %@!
Tap to Connect
+ Dotknij aby połączyć
No comment provided by engineer.
@@ -4876,10 +4930,12 @@ Może się to zdarzyć z powodu jakiegoś błędu lub gdy połączenie jest skom
This is your own SimpleX address!
+ To jest twój własny adres SimpleX!
No comment provided by engineer.
This is your own one-time link!
+ To jest twój jednorazowy link!
No comment provided by engineer.
@@ -4981,14 +5037,17 @@ Przed włączeniem tej funkcji zostanie wyświetlony monit uwierzytelniania.
Unblock
+ Odblokuj
No comment provided by engineer.
Unblock member
+ Odblokuj członka
No comment provided by engineer.
Unblock member?
+ Odblokować członka?
No comment provided by engineer.
@@ -5340,31 +5399,39 @@ Aby się połączyć, poproś Twój kontakt o utworzenie kolejnego linku połąc
You are already connecting to %@.
+ Już się łączysz z %@.
No comment provided by engineer.
You are already connecting via this one-time link!
+ Już jesteś połączony z tym jednorazowym linkiem!
No comment provided by engineer.
You are already in group %@.
+ Już jesteś w grupie %@.
No comment provided by engineer.
You are already joining the group %@.
+ Już dołączasz do grupy %@.
No comment provided by engineer.
You are already joining the group via this link!
+ Już dołączasz do grupy przez ten link!
No comment provided by engineer.
You are already joining the group via this link.
+ Już dołączasz do grupy przez ten link.
No comment provided by engineer.
You are already joining the group!
Repeat join request?
+ Już dołączasz do grupy!
+Powtórzyć prośbę dołączenia?
No comment provided by engineer.
@@ -5464,11 +5531,14 @@ Repeat join request?
You have already requested connection via this address!
+ Już prosiłeś o połączenie na ten adres!
No comment provided by engineer.
You have already requested connection!
Repeat connection request?
+ Już prosiłeś o połączenie!
+Powtórzyć prośbę połączenia?
No comment provided by engineer.
@@ -5523,6 +5593,7 @@ Repeat connection request?
You will be connected when group link host's device is online, please wait or check later!
+ Zostaniesz połączony, gdy urządzenie hosta grupy będzie online, proszę czekać lub sprawdzić później!
No comment provided by engineer.
@@ -5542,6 +5613,7 @@ Repeat connection request?
You will connect to all group members.
+ Zostaniesz połączony ze wszystkimi członkami grupy.
No comment provided by engineer.
@@ -5665,6 +5737,7 @@ Możesz to zmienić w Ustawieniach.
Your profile
+ Twój profil
No comment provided by engineer.
@@ -5761,6 +5834,7 @@ Serwery SimpleX nie mogą zobaczyć Twojego profilu.
and %lld other events
+ i %lld innych wydarzeń
No comment provided by engineer.
@@ -5780,6 +5854,7 @@ Serwery SimpleX nie mogą zobaczyć Twojego profilu.
blocked
+ zablokowany
No comment provided by engineer.
@@ -5954,6 +6029,7 @@ Serwery SimpleX nie mogą zobaczyć Twojego profilu.
deleted contact
+ usunięto kontakt
rcv direct event chat item
diff --git a/apps/ios/SimpleX Localizations/ru.xcloc/Localized Contents/ru.xliff b/apps/ios/SimpleX Localizations/ru.xcloc/Localized Contents/ru.xliff
index 672a9071c3..402fdc9658 100644
--- a/apps/ios/SimpleX Localizations/ru.xcloc/Localized Contents/ru.xliff
+++ b/apps/ios/SimpleX Localizations/ru.xcloc/Localized Contents/ru.xliff
@@ -1966,6 +1966,14 @@ This cannot be undone!
Зашифрованное сообщение: неожиданная ошибка
notification
+
+ Encryption re-negotiation error
+ message decrypt error item
+
+
+ Encryption re-negotiation failed.
+ No comment provided by engineer.
+
Enter Passcode
Введите Код
diff --git a/apps/ios/SimpleX Localizations/th.xcloc/Localized Contents/th.xliff b/apps/ios/SimpleX Localizations/th.xcloc/Localized Contents/th.xliff
index 44342f0885..e1451378a5 100644
--- a/apps/ios/SimpleX Localizations/th.xcloc/Localized Contents/th.xliff
+++ b/apps/ios/SimpleX Localizations/th.xcloc/Localized Contents/th.xliff
@@ -1946,6 +1946,14 @@ This cannot be undone!
ข้อความที่ encrypt: ข้อผิดพลาดที่ไม่คาดคิด
notification
+
+ Encryption re-negotiation error
+ message decrypt error item
+
+
+ Encryption re-negotiation failed.
+ No comment provided by engineer.
+
Enter Passcode
ใส่รหัสผ่าน
diff --git a/apps/ios/SimpleX Localizations/uk.xcloc/Localized Contents/uk.xliff b/apps/ios/SimpleX Localizations/uk.xcloc/Localized Contents/uk.xliff
index b4ce70f7df..b3b5e9d39a 100644
--- a/apps/ios/SimpleX Localizations/uk.xcloc/Localized Contents/uk.xliff
+++ b/apps/ios/SimpleX Localizations/uk.xcloc/Localized Contents/uk.xliff
@@ -1956,6 +1956,14 @@ This cannot be undone!
Зашифроване повідомлення: несподівана помилка
notification
+
+ Encryption re-negotiation error
+ message decrypt error item
+
+
+ Encryption re-negotiation failed.
+ No comment provided by engineer.
+
Enter Passcode
Введіть пароль
diff --git a/apps/ios/SimpleX Localizations/zh-Hans.xcloc/Localized Contents/zh-Hans.xliff b/apps/ios/SimpleX Localizations/zh-Hans.xcloc/Localized Contents/zh-Hans.xliff
index fba15d68cf..929b54a631 100644
--- a/apps/ios/SimpleX Localizations/zh-Hans.xcloc/Localized Contents/zh-Hans.xliff
+++ b/apps/ios/SimpleX Localizations/zh-Hans.xcloc/Localized Contents/zh-Hans.xliff
@@ -1966,6 +1966,14 @@ This cannot be undone!
加密消息:意外错误
notification
+
+ Encryption re-negotiation error
+ message decrypt error item
+
+
+ Encryption re-negotiation failed.
+ No comment provided by engineer.
+
Enter Passcode
输入密码
diff --git a/apps/ios/de.lproj/Localizable.strings b/apps/ios/de.lproj/Localizable.strings
index 2fe8d87b69..00489be829 100644
--- a/apps/ios/de.lproj/Localizable.strings
+++ b/apps/ios/de.lproj/Localizable.strings
@@ -119,11 +119,17 @@
"%@ %@" = "%@ %@";
/* No comment provided by engineer. */
-"%@ and %@ connected" = "%@ und %@ wurden verbunden";
+"%@ and %@" = "%@ und %@";
+
+/* No comment provided by engineer. */
+"%@ and %@ connected" = "%@ und %@ wurden mit Ihnen verbunden";
/* copied message info, at */
"%@ at %@:" = "%1$@ an %2$@:";
+/* No comment provided by engineer. */
+"%@ connected" = "%@ wurde mit Ihnen verbunden";
+
/* notification title */
"%@ is connected!" = "%@ ist mit Ihnen verbunden!";
@@ -140,7 +146,10 @@
"%@ wants to connect!" = "%@ will sich mit Ihnen verbinden!";
/* No comment provided by engineer. */
-"%@, %@ and %lld other members connected" = "%@, %@ und %lld weitere Mitglieder wurden verbunden";
+"%@, %@ and %lld members" = "%@, %@ und %lld Mitglieder";
+
+/* No comment provided by engineer. */
+"%@, %@ and %lld other members connected" = "%@, %@ und %lld weitere Mitglieder wurden mit Ihnen verbunden";
/* copied message info */
"%@:" = "%@:";
@@ -178,9 +187,21 @@
/* No comment provided by engineer. */
"%lld file(s) with total size of %@" = "%lld Datei(en) mit einem Gesamtspeicherverbrauch von %@";
+/* No comment provided by engineer. */
+"%lld group events" = "%lld Gruppenereignisse";
+
/* No comment provided by engineer. */
"%lld members" = "%lld Mitglieder";
+/* No comment provided by engineer. */
+"%lld messages blocked" = "%lld Nachrichten blockiert";
+
+/* No comment provided by engineer. */
+"%lld messages marked deleted" = "%lld Nachrichten als gelöscht markiert";
+
+/* No comment provided by engineer. */
+"%lld messages moderated by %@" = "%lld Nachrichten von %@ moderiert";
+
/* No comment provided by engineer. */
"%lld minutes" = "%lld Minuten";
@@ -229,6 +250,9 @@
/* No comment provided by engineer. */
"~strike~" = "\\~durchstreichen~";
+/* time to disappear */
+"0 sec" = "0 sek";
+
/* No comment provided by engineer. */
"0s" = "0s";
@@ -371,6 +395,9 @@
/* No comment provided by engineer. */
"All messages will be deleted - this cannot be undone! The messages will be deleted ONLY for you." = "Alle Nachrichten werden gelöscht - dies kann nicht rückgängig gemacht werden! Die Nachrichten werden NUR bei Ihnen gelöscht.";
+/* No comment provided by engineer. */
+"All new messages from %@ will be hidden!" = "Alle neuen Nachrichten von %@ werden verborgen!";
+
/* No comment provided by engineer. */
"All your contacts will remain connected." = "Alle Ihre Kontakte bleiben verbunden.";
@@ -434,6 +461,12 @@
/* No comment provided by engineer. */
"Already connected?" = "Sind Sie bereits verbunden?";
+/* No comment provided by engineer. */
+"Already connecting!" = "Bereits verbunden!";
+
+/* No comment provided by engineer. */
+"Already joining the group!" = "Sie sind bereits Mitglied der Gruppe!";
+
/* pref value */
"always" = "Immer";
@@ -443,6 +476,9 @@
/* No comment provided by engineer. */
"An empty chat profile with the provided name is created, and the app opens as usual." = "Es wurde ein leeres Chat-Profil mit dem eingegebenen Namen erstellt und die App öffnet wie gewohnt.";
+/* No comment provided by engineer. */
+"and %lld other events" = "und %lld weitere Ereignisse";
+
/* No comment provided by engineer. */
"Answer call" = "Anruf annehmen";
@@ -527,6 +563,18 @@
/* No comment provided by engineer. */
"Better messages" = "Verbesserungen bei Nachrichten";
+/* No comment provided by engineer. */
+"Block" = "Blockieren";
+
+/* No comment provided by engineer. */
+"Block member" = "Mitglied blockieren";
+
+/* No comment provided by engineer. */
+"Block member?" = "Mitglied blockieren?";
+
+/* No comment provided by engineer. */
+"blocked" = "blockiert";
+
/* No comment provided by engineer. */
"bold" = "fett";
@@ -726,6 +774,18 @@
/* No comment provided by engineer. */
"connect to SimpleX Chat developers." = "Mit den SimpleX Chat-Entwicklern verbinden.";
+/* No comment provided by engineer. */
+"Connect to yourself?" = "Mit Ihnen selbst verbinden?";
+
+/* No comment provided by engineer. */
+"Connect to yourself?\nThis is your own one-time link!" = "Mit Ihnen selbst verbinden?\nDas ist Ihr eigener Einmal-Link!";
+
+/* No comment provided by engineer. */
+"Connect to yourself?\nThis is your own SimpleX address!" = "Mit Ihnen selbst verbinden?\nDas ist Ihre eigene SimpleX-Adresse!";
+
+/* No comment provided by engineer. */
+"Connect via contact address" = "Über die Kontakt-Adresse verbinden";
+
/* No comment provided by engineer. */
"Connect via link" = "Über einen Link verbinden";
@@ -735,6 +795,9 @@
/* No comment provided by engineer. */
"Connect via one-time link" = "Über einen Einmal-Link verbinden";
+/* No comment provided by engineer. */
+"Connect with %@" = "Mit %@ verbinden";
+
/* No comment provided by engineer. */
"connected" = "Verbunden";
@@ -831,6 +894,9 @@
/* No comment provided by engineer. */
"Core version: v%@" = "Core Version: v%@";
+/* No comment provided by engineer. */
+"Correct name to %@?" = "Richtiger Name für %@?";
+
/* No comment provided by engineer. */
"Create" = "Erstellen";
@@ -840,6 +906,9 @@
/* server test step */
"Create file" = "Datei erstellen";
+/* No comment provided by engineer. */
+"Create group" = "Gruppe erstellen";
+
/* No comment provided by engineer. */
"Create group link" = "Gruppenlink erstellen";
@@ -852,6 +921,9 @@
/* No comment provided by engineer. */
"Create one-time invitation link" = "Einmal-Einladungslink erstellen";
+/* No comment provided by engineer. */
+"Create profile" = "Profil erstellen";
+
/* server test step */
"Create queue" = "Erzeuge Warteschlange";
@@ -966,6 +1038,9 @@
/* chat item action */
"Delete" = "Löschen";
+/* No comment provided by engineer. */
+"Delete %lld messages?" = "%lld Nachrichten löschen?";
+
/* No comment provided by engineer. */
"Delete address" = "Adresse löschen";
@@ -978,6 +1053,9 @@
/* No comment provided by engineer. */
"Delete all files" = "Alle Dateien löschen";
+/* No comment provided by engineer. */
+"Delete and notify contact" = "Kontakt löschen und benachrichtigen";
+
/* No comment provided by engineer. */
"Delete archive" = "Archiv löschen";
@@ -999,6 +1077,9 @@
/* No comment provided by engineer. */
"Delete Contact" = "Kontakt löschen";
+/* No comment provided by engineer. */
+"Delete contact?\nThis cannot be undone!" = "Kontakt löschen?\nDas kann nicht rückgängig gemacht werden!";
+
/* No comment provided by engineer. */
"Delete database" = "Datenbank löschen";
@@ -1074,6 +1155,9 @@
/* copied message info */
"Deleted at: %@" = "Gelöscht um: %@";
+/* rcv direct event chat item */
+"deleted contact" = "Gelöschter Kontakt";
+
/* rcv group event chat item */
"deleted group" = "Gruppe gelöscht";
@@ -1305,6 +1389,9 @@
/* No comment provided by engineer. */
"Enter correct passphrase." = "Geben Sie das korrekte Passwort ein.";
+/* No comment provided by engineer. */
+"Enter group name…" = "Geben Sie den Gruppennamen ein…";
+
/* No comment provided by engineer. */
"Enter Passcode" = "Zugangscode eingeben";
@@ -1323,6 +1410,9 @@
/* placeholder */
"Enter welcome message… (optional)" = "Geben Sie eine Begrüßungsmeldung ein … (optional)";
+/* No comment provided by engineer. */
+"Enter your name…" = "Geben Sie Ihren Namen ein…";
+
/* No comment provided by engineer. */
"error" = "Fehler";
@@ -1494,6 +1584,9 @@
/* No comment provided by engineer. */
"Exit without saving" = "Beenden ohne Speichern";
+/* chat item action */
+"Expand" = "Erweitern";
+
/* No comment provided by engineer. */
"Export database" = "Datenbank exportieren";
@@ -1581,6 +1674,9 @@
/* No comment provided by engineer. */
"Full name:" = "Vollständiger Name:";
+/* No comment provided by engineer. */
+"Fully decentralized – visible only to members." = "Vollständig dezentralisiert – nur für Mitglieder sichtbar.";
+
/* No comment provided by engineer. */
"Fully re-implemented - work in background!" = "Komplett neu umgesetzt - arbeitet nun im Hintergrund!";
@@ -1593,6 +1689,12 @@
/* No comment provided by engineer. */
"Group" = "Gruppe";
+/* No comment provided by engineer. */
+"Group already exists" = "Die Gruppe besteht bereits";
+
+/* No comment provided by engineer. */
+"Group already exists!" = "Die Gruppe besteht bereits!";
+
/* No comment provided by engineer. */
"group deleted" = "Gruppe gelöscht";
@@ -1830,6 +1932,9 @@
/* invalid chat item */
"invalid data" = "Ungültige Daten";
+/* No comment provided by engineer. */
+"Invalid name!" = "Ungültiger Name!";
+
/* No comment provided by engineer. */
"Invalid server address!" = "Ungültige Serveradresse!";
@@ -1888,7 +1993,7 @@
"It can happen when:\n1. The messages expired in the sending client after 2 days or on the server after 30 days.\n2. Message decryption failed, because you or your contact used old database backup.\n3. The connection was compromised." = "Dies kann unter folgenden Umständen passieren:\n1. Die Nachrichten verfallen auf dem sendenden Client-System nach 2 Tagen oder auf dem Server nach 30 Tagen.\n2. Die Nachrichten-Entschlüsselung ist fehlgeschlagen, da von Ihnen oder Ihrem Kontakt ein altes Datenbank-Backup genutzt wurde.\n3. Die Verbindung wurde kompromittiert.";
/* No comment provided by engineer. */
-"It seems like you are already connected via this link. If it is not the case, there was an error (%@)." = "Es sieht so aus, dass Sie bereits über diesen Link verbunden sind. Wenn das nicht der Fall, gab es einen Fehler (%@).";
+"It seems like you are already connected via this link. If it is not the case, there was an error (%@)." = "Es sieht so aus, als ob Sie bereits über diesen Link verbunden sind. Wenn das nicht der Fall ist, gab es einen Fehler (%@).";
/* No comment provided by engineer. */
"Italian interface" = "Italienische Bedienoberfläche";
@@ -1908,9 +2013,18 @@
/* No comment provided by engineer. */
"Join group" = "Treten Sie der Gruppe bei";
+/* No comment provided by engineer. */
+"Join group?" = "Der Gruppe beitreten?";
+
/* No comment provided by engineer. */
"Join incognito" = "Inkognito beitreten";
+/* No comment provided by engineer. */
+"Join with current profile" = "Mit dem aktuellen Profil beitreten";
+
+/* No comment provided by engineer. */
+"Join your group?\nThis is your link for group %@!" = "Ihrer Gruppe beitreten?\nDas ist Ihr Link für die Gruppe %@!";
+
/* No comment provided by engineer. */
"Joining group" = "Der Gruppe beitreten";
@@ -2055,6 +2169,9 @@
/* No comment provided by engineer. */
"Messages & files" = "Nachrichten";
+/* No comment provided by engineer. */
+"Messages from %@ will be shown!" = "Die Nachrichten von %@ werden angezeigt!";
+
/* No comment provided by engineer. */
"Migrating database archive…" = "Datenbank-Archiv wird migriert…";
@@ -2306,6 +2423,9 @@
/* authentication reason */
"Open chat console" = "Chat-Konsole öffnen";
+/* No comment provided by engineer. */
+"Open group" = "Gruppe öffnen";
+
/* No comment provided by engineer. */
"Open Settings" = "Geräte-Einstellungen öffnen";
@@ -2438,6 +2558,12 @@
/* No comment provided by engineer. */
"Profile image" = "Profilbild";
+/* No comment provided by engineer. */
+"Profile name" = "Profilname";
+
+/* No comment provided by engineer. */
+"Profile name:" = "Profilname:";
+
/* No comment provided by engineer. */
"Profile password" = "Passwort für Profil";
@@ -2603,6 +2729,12 @@
/* No comment provided by engineer. */
"Renegotiate encryption?" = "Verschlüsselung neu aushandeln?";
+/* No comment provided by engineer. */
+"Repeat connection request?" = "Verbindungsanfrage wiederholen?";
+
+/* No comment provided by engineer. */
+"Repeat join request?" = "Verbindungsanfrage wiederholen?";
+
/* chat item action */
"Reply" = "Antwort";
@@ -3044,6 +3176,9 @@
/* No comment provided by engineer. */
"Tap to activate profile." = "Tippen Sie auf das Profil um es zu aktivieren.";
+/* No comment provided by engineer. */
+"Tap to Connect" = "Zum Verbinden antippen";
+
/* No comment provided by engineer. */
"Tap to join" = "Zum Beitreten tippen";
@@ -3170,6 +3305,12 @@
/* No comment provided by engineer. */
"This group no longer exists." = "Diese Gruppe existiert nicht mehr.";
+/* No comment provided by engineer. */
+"This is your own one-time link!" = "Das ist Ihr eigener Einmal-Link!";
+
+/* No comment provided by engineer. */
+"This is your own SimpleX address!" = "Das ist Ihre eigene SimpleX-Adresse!";
+
/* No comment provided by engineer. */
"This setting applies to messages in your current chat profile **%@**." = "Diese Einstellung gilt für Nachrichten in Ihrem aktuellen Chat-Profil **%@**.";
@@ -3195,7 +3336,7 @@
"To record voice message please grant permission to use Microphone." = "Bitte erlauben Sie die Nutzung des Mikrofons, um Sprachnachrichten aufnehmen zu können.";
/* No comment provided by engineer. */
-"To reveal your hidden profile, enter a full password into a search field in **Your chat profiles** page." = "Geben Sie ein vollständiges Passwort in das Suchfeld auf der Seite **Meine Chat-Profile** ein, um Ihr verborgenes Profil zu sehen.";
+"To reveal your hidden profile, enter a full password into a search field in **Your chat profiles** page." = "Geben Sie ein vollständiges Passwort in das Suchfeld auf der Seite **Ihre Chat-Profile** ein, um Ihr verborgenes Profil zu sehen.";
/* No comment provided by engineer. */
"To support instant push notifications the chat database has to be migrated." = "Um sofortige Push-Benachrichtigungen zu unterstützen, muss die Chat-Datenbank migriert werden.";
@@ -3227,6 +3368,15 @@
/* No comment provided by engineer. */
"Unable to record voice message" = "Die Aufnahme einer Sprachnachricht ist nicht möglich";
+/* No comment provided by engineer. */
+"Unblock" = "Freigeben";
+
+/* No comment provided by engineer. */
+"Unblock member" = "Mitglied freigeben";
+
+/* No comment provided by engineer. */
+"Unblock member?" = "Mitglied freigeben?";
+
/* item status description */
"Unexpected error: %@" = "Unerwarteter Fehler: %@";
@@ -3459,7 +3609,7 @@
"yes" = "Ja";
/* No comment provided by engineer. */
-"You" = "Meine Daten";
+"You" = "Ihre Daten";
/* No comment provided by engineer. */
"You accepted connection" = "Sie haben die Verbindung akzeptiert";
@@ -3473,6 +3623,27 @@
/* No comment provided by engineer. */
"You are already connected to %@." = "Sie sind bereits mit %@ verbunden.";
+/* No comment provided by engineer. */
+"You are already connecting to %@." = "Sie sind bereits mit %@ verbunden.";
+
+/* No comment provided by engineer. */
+"You are already connecting via this one-time link!" = "Sie sind bereits über diesen Einmal-Link verbunden!";
+
+/* No comment provided by engineer. */
+"You are already in group %@." = "Sie sind bereits Mitglied der Gruppe %@.";
+
+/* No comment provided by engineer. */
+"You are already joining the group %@." = "Sie sind bereits Mitglied der Gruppe %@.";
+
+/* No comment provided by engineer. */
+"You are already joining the group via this link!" = "Sie sind über diesen Link bereits Mitglied der Gruppe!";
+
+/* No comment provided by engineer. */
+"You are already joining the group via this link." = "Sie sind über diesen Link bereits Mitglied der Gruppe.";
+
+/* No comment provided by engineer. */
+"You are already joining the group!\nRepeat join request?" = "Sie sind bereits Mitglied dieser Gruppe!\nVerbindungsanfrage wiederholen?";
+
/* No comment provided by engineer. */
"You are connected to the server used to receive messages from this contact." = "Sie sind mit dem Server verbunden, der für den Empfang von Nachrichten mit diesem Kontakt genutzt wird.";
@@ -3548,6 +3719,12 @@
/* No comment provided by engineer. */
"You could not be verified; please try again." = "Sie konnten nicht überprüft werden; bitte versuchen Sie es erneut.";
+/* No comment provided by engineer. */
+"You have already requested connection via this address!" = "Sie haben über diese Adresse bereits eine Verbindung beantragt!";
+
+/* No comment provided by engineer. */
+"You have already requested connection!\nRepeat connection request?" = "Sie haben bereits ein Verbindungsanfrage beantragt!\nVerbindungsanfrage wiederholen?";
+
/* No comment provided by engineer. */
"You have no chats" = "Sie haben keine Chats";
@@ -3590,6 +3767,9 @@
/* No comment provided by engineer. */
"You will be connected to group when the group host's device is online, please wait or check later!" = "Sie werden mit der Gruppe verbunden, sobald das Endgerät des Gruppen-Hosts online ist. Bitte warten oder schauen Sie später nochmal nach!";
+/* No comment provided by engineer. */
+"You will be connected when group link host's device is online, please wait or check later!" = "Sie werden verbunden, sobald das Endgerät des Gruppenlink-Hosts online ist. Bitte warten oder schauen Sie später nochmal nach!";
+
/* No comment provided by engineer. */
"You will be connected when your connection request is accepted, please wait or check later!" = "Sie werden verbunden, sobald Ihre Verbindungsanfrage akzeptiert wird. Bitte warten oder schauen Sie später nochmal nach!";
@@ -3599,6 +3779,9 @@
/* No comment provided by engineer. */
"You will be required to authenticate when you start or resume the app after 30 seconds in background." = "Sie müssen sich authentifizieren, wenn Sie die im Hintergrund befindliche App nach 30 Sekunden starten oder fortsetzen.";
+/* No comment provided by engineer. */
+"You will connect to all group members." = "Sie werden mit allen Gruppenmitgliedern verbunden.";
+
/* No comment provided by engineer. */
"You will still receive calls and notifications from muted profiles when they are active." = "Sie können Anrufe und Benachrichtigungen auch von stummgeschalteten Profilen empfangen, solange diese aktiv sind.";
@@ -3630,7 +3813,7 @@
"Your chat database is not encrypted - set passphrase to encrypt it." = "Ihre Chat-Datenbank ist nicht verschlüsselt. Bitte legen Sie ein Passwort fest, um sie zu schützen.";
/* No comment provided by engineer. */
-"Your chat profiles" = "Meine Chat-Profile";
+"Your chat profiles" = "Ihre Chat-Profile";
/* No comment provided by engineer. */
"Your contact needs to be online for the connection to complete.\nYou can cancel this connection and remove the contact (and try later with a new link)." = "Damit die Verbindung hergestellt werden kann, muss Ihr Kontakt online sein.\nSie können diese Verbindung abbrechen und den Kontakt entfernen (und es später nochmals mit einem neuen Link versuchen).";
@@ -3657,10 +3840,13 @@
"Your ICE servers" = "Ihre ICE-Server";
/* No comment provided by engineer. */
-"Your preferences" = "Meine Präferenzen";
+"Your preferences" = "Ihre Präferenzen";
/* No comment provided by engineer. */
-"Your privacy" = "Meine Privatsphäre";
+"Your privacy" = "Ihre Privatsphäre";
+
+/* No comment provided by engineer. */
+"Your profile" = "Mein Profil";
/* No comment provided by engineer. */
"Your profile **%@** will be shared." = "Ihr Profil **%@** wird geteilt.";
@@ -3681,10 +3867,10 @@
"Your server address" = "Ihre Serveradresse";
/* No comment provided by engineer. */
-"Your settings" = "Meine Einstellungen";
+"Your settings" = "Ihre Einstellungen";
/* No comment provided by engineer. */
-"Your SimpleX address" = "Meine SimpleX-Adresse";
+"Your SimpleX address" = "Ihre SimpleX-Adresse";
/* No comment provided by engineer. */
"Your SMP servers" = "Ihre SMP-Server";
diff --git a/apps/ios/fr.lproj/Localizable.strings b/apps/ios/fr.lproj/Localizable.strings
index 52c114a9de..33f571b513 100644
--- a/apps/ios/fr.lproj/Localizable.strings
+++ b/apps/ios/fr.lproj/Localizable.strings
@@ -118,12 +118,18 @@
/* No comment provided by engineer. */
"%@ %@" = "%@ %@";
+/* No comment provided by engineer. */
+"%@ and %@" = "%@ et %@";
+
/* No comment provided by engineer. */
"%@ and %@ connected" = "%@ et %@ sont connecté.es";
/* copied message info, at */
"%@ at %@:" = "%1$@ à %2$@ :";
+/* No comment provided by engineer. */
+"%@ connected" = "%@ connecté(e)";
+
/* notification title */
"%@ is connected!" = "%@ est connecté·e !";
@@ -139,6 +145,9 @@
/* notification title */
"%@ wants to connect!" = "%@ veut se connecter !";
+/* No comment provided by engineer. */
+"%@, %@ and %lld members" = "%@, %@ et %lld membres";
+
/* No comment provided by engineer. */
"%@, %@ and %lld other members connected" = "%@, %@ et %lld autres membres sont connectés";
@@ -178,9 +187,21 @@
/* No comment provided by engineer. */
"%lld file(s) with total size of %@" = "%lld fichier·s pour une taille totale de %@";
+/* No comment provided by engineer. */
+"%lld group events" = "%lld événements de groupe";
+
/* No comment provided by engineer. */
"%lld members" = "%lld membres";
+/* No comment provided by engineer. */
+"%lld messages blocked" = "%lld messages bloqués";
+
+/* No comment provided by engineer. */
+"%lld messages marked deleted" = "%lld messages marqués comme supprimés";
+
+/* No comment provided by engineer. */
+"%lld messages moderated by %@" = "%lld messages modérés par %@";
+
/* No comment provided by engineer. */
"%lld minutes" = "%lld minutes";
@@ -229,6 +250,9 @@
/* No comment provided by engineer. */
"~strike~" = "\\~barré~";
+/* time to disappear */
+"0 sec" = "0 sec";
+
/* No comment provided by engineer. */
"0s" = "0s";
@@ -371,6 +395,9 @@
/* No comment provided by engineer. */
"All messages will be deleted - this cannot be undone! The messages will be deleted ONLY for you." = "Tous les messages seront supprimés - impossible de revenir en arrière ! Les messages seront supprimés UNIQUEMENT pour vous.";
+/* No comment provided by engineer. */
+"All new messages from %@ will be hidden!" = "Tous les nouveaux messages de %@ seront cachés !";
+
/* No comment provided by engineer. */
"All your contacts will remain connected." = "Tous vos contacts resteront connectés.";
@@ -434,6 +461,12 @@
/* No comment provided by engineer. */
"Already connected?" = "Déjà connecté ?";
+/* No comment provided by engineer. */
+"Already connecting!" = "Déjà en connexion !";
+
+/* No comment provided by engineer. */
+"Already joining the group!" = "Groupe déjà rejoint !";
+
/* pref value */
"always" = "toujours";
@@ -443,6 +476,9 @@
/* No comment provided by engineer. */
"An empty chat profile with the provided name is created, and the app opens as usual." = "Un profil de chat vierge portant le nom fourni est créé et l'application s'ouvre normalement.";
+/* No comment provided by engineer. */
+"and %lld other events" = "et %lld autres événements";
+
/* No comment provided by engineer. */
"Answer call" = "Répondre à l'appel";
@@ -527,6 +563,18 @@
/* No comment provided by engineer. */
"Better messages" = "Meilleurs messages";
+/* No comment provided by engineer. */
+"Block" = "Bloquer";
+
+/* No comment provided by engineer. */
+"Block member" = "Bloquer ce membre";
+
+/* No comment provided by engineer. */
+"Block member?" = "Bloquer ce membre ?";
+
+/* No comment provided by engineer. */
+"blocked" = "blocké";
+
/* No comment provided by engineer. */
"bold" = "gras";
@@ -726,6 +774,18 @@
/* No comment provided by engineer. */
"connect to SimpleX Chat developers." = "se connecter aux developpeurs de SimpleX Chat.";
+/* No comment provided by engineer. */
+"Connect to yourself?" = "Se connecter à soi-même ?";
+
+/* No comment provided by engineer. */
+"Connect to yourself?\nThis is your own one-time link!" = "Se connecter à soi-même ?\nIl s'agit de votre propre lien unique !";
+
+/* No comment provided by engineer. */
+"Connect to yourself?\nThis is your own SimpleX address!" = "Se connecter à soi-même ?\nC'est votre propre adresse SimpleX !";
+
+/* No comment provided by engineer. */
+"Connect via contact address" = "Se connecter via l'adresse de contact";
+
/* No comment provided by engineer. */
"Connect via link" = "Se connecter via un lien";
@@ -735,6 +795,9 @@
/* No comment provided by engineer. */
"Connect via one-time link" = "Se connecter via un lien unique";
+/* No comment provided by engineer. */
+"Connect with %@" = "Se connecter avec %@";
+
/* No comment provided by engineer. */
"connected" = "connecté";
@@ -831,6 +894,9 @@
/* No comment provided by engineer. */
"Core version: v%@" = "Version du cœur : v%@";
+/* No comment provided by engineer. */
+"Correct name to %@?" = "Corriger le nom pour %@ ?";
+
/* No comment provided by engineer. */
"Create" = "Créer";
@@ -840,6 +906,9 @@
/* server test step */
"Create file" = "Créer un fichier";
+/* No comment provided by engineer. */
+"Create group" = "Créer un groupe";
+
/* No comment provided by engineer. */
"Create group link" = "Créer un lien de groupe";
@@ -852,6 +921,9 @@
/* No comment provided by engineer. */
"Create one-time invitation link" = "Créer un lien d'invitation unique";
+/* No comment provided by engineer. */
+"Create profile" = "Créer le profil";
+
/* server test step */
"Create queue" = "Créer une file d'attente";
@@ -966,6 +1038,9 @@
/* chat item action */
"Delete" = "Supprimer";
+/* No comment provided by engineer. */
+"Delete %lld messages?" = "Supprimer %lld messages ?";
+
/* No comment provided by engineer. */
"Delete address" = "Supprimer l'adresse";
@@ -978,6 +1053,9 @@
/* No comment provided by engineer. */
"Delete all files" = "Effacer tous les fichiers";
+/* No comment provided by engineer. */
+"Delete and notify contact" = "Supprimer et en informer le contact";
+
/* No comment provided by engineer. */
"Delete archive" = "Supprimer l'archive";
@@ -999,6 +1077,9 @@
/* No comment provided by engineer. */
"Delete Contact" = "Supprimer le contact";
+/* No comment provided by engineer. */
+"Delete contact?\nThis cannot be undone!" = "Supprimer le contact ?\nCette opération ne peut être annulée !";
+
/* No comment provided by engineer. */
"Delete database" = "Supprimer la base de données";
@@ -1074,6 +1155,9 @@
/* copied message info */
"Deleted at: %@" = "Supprimé à : %@";
+/* rcv direct event chat item */
+"deleted contact" = "contact supprimé";
+
/* rcv group event chat item */
"deleted group" = "groupe supprimé";
@@ -1305,6 +1389,9 @@
/* No comment provided by engineer. */
"Enter correct passphrase." = "Entrez la phrase secrète correcte.";
+/* No comment provided by engineer. */
+"Enter group name…" = "Entrer un nom de groupe…";
+
/* No comment provided by engineer. */
"Enter Passcode" = "Entrer le code d'accès";
@@ -1323,6 +1410,9 @@
/* placeholder */
"Enter welcome message… (optional)" = "Entrez un message de bienvenue… (facultatif)";
+/* No comment provided by engineer. */
+"Enter your name…" = "Entrez votre nom…";
+
/* No comment provided by engineer. */
"error" = "erreur";
@@ -1494,6 +1584,9 @@
/* No comment provided by engineer. */
"Exit without saving" = "Quitter sans sauvegarder";
+/* chat item action */
+"Expand" = "Développer";
+
/* No comment provided by engineer. */
"Export database" = "Exporter la base de données";
@@ -1581,6 +1674,9 @@
/* No comment provided by engineer. */
"Full name:" = "Nom complet :";
+/* No comment provided by engineer. */
+"Fully decentralized – visible only to members." = "Entièrement décentralisé – visible que par ses membres.";
+
/* No comment provided by engineer. */
"Fully re-implemented - work in background!" = "Entièrement réimplémenté - fonctionne en arrière-plan !";
@@ -1593,6 +1689,12 @@
/* No comment provided by engineer. */
"Group" = "Groupe";
+/* No comment provided by engineer. */
+"Group already exists" = "Le groupe existe déjà";
+
+/* No comment provided by engineer. */
+"Group already exists!" = "Ce groupe existe déjà !";
+
/* No comment provided by engineer. */
"group deleted" = "groupe supprimé";
@@ -1830,6 +1932,9 @@
/* invalid chat item */
"invalid data" = "données invalides";
+/* No comment provided by engineer. */
+"Invalid name!" = "Nom invalide !";
+
/* No comment provided by engineer. */
"Invalid server address!" = "Adresse de serveur invalide !";
@@ -1908,9 +2013,18 @@
/* No comment provided by engineer. */
"Join group" = "Rejoindre le groupe";
+/* No comment provided by engineer. */
+"Join group?" = "Rejoindre le groupe ?";
+
/* No comment provided by engineer. */
"Join incognito" = "Rejoindre en incognito";
+/* No comment provided by engineer. */
+"Join with current profile" = "Rejoindre avec le profil actuel";
+
+/* No comment provided by engineer. */
+"Join your group?\nThis is your link for group %@!" = "Rejoindre votre groupe ?\nVoici votre lien pour le groupe %@ !";
+
/* No comment provided by engineer. */
"Joining group" = "Entrain de rejoindre le groupe";
@@ -2055,6 +2169,9 @@
/* No comment provided by engineer. */
"Messages & files" = "Messages";
+/* No comment provided by engineer. */
+"Messages from %@ will be shown!" = "Les messages de %@ seront affichés !";
+
/* No comment provided by engineer. */
"Migrating database archive…" = "Migration de l'archive de la base de données…";
@@ -2306,6 +2423,9 @@
/* authentication reason */
"Open chat console" = "Ouvrir la console du chat";
+/* No comment provided by engineer. */
+"Open group" = "Ouvrir le groupe";
+
/* No comment provided by engineer. */
"Open Settings" = "Ouvrir les Paramètres";
@@ -2438,6 +2558,12 @@
/* No comment provided by engineer. */
"Profile image" = "Image de profil";
+/* No comment provided by engineer. */
+"Profile name" = "Nom du profil";
+
+/* No comment provided by engineer. */
+"Profile name:" = "Nom du profil :";
+
/* No comment provided by engineer. */
"Profile password" = "Mot de passe de profil";
@@ -2603,6 +2729,12 @@
/* No comment provided by engineer. */
"Renegotiate encryption?" = "Renégocier le chiffrement?";
+/* No comment provided by engineer. */
+"Repeat connection request?" = "Répéter la demande de connexion ?";
+
+/* No comment provided by engineer. */
+"Repeat join request?" = "Répéter la requête d'adhésion ?";
+
/* chat item action */
"Reply" = "Répondre";
@@ -3044,6 +3176,9 @@
/* No comment provided by engineer. */
"Tap to activate profile." = "Appuyez pour activer un profil.";
+/* No comment provided by engineer. */
+"Tap to Connect" = "Tapez pour vous connecter";
+
/* No comment provided by engineer. */
"Tap to join" = "Appuyez pour rejoindre";
@@ -3170,6 +3305,12 @@
/* No comment provided by engineer. */
"This group no longer exists." = "Ce groupe n'existe plus.";
+/* No comment provided by engineer. */
+"This is your own one-time link!" = "Voici votre propre lien unique !";
+
+/* No comment provided by engineer. */
+"This is your own SimpleX address!" = "Voici votre propre adresse SimpleX !";
+
/* No comment provided by engineer. */
"This setting applies to messages in your current chat profile **%@**." = "Ce paramètre s'applique aux messages de votre profil de chat actuel **%@**.";
@@ -3227,6 +3368,15 @@
/* No comment provided by engineer. */
"Unable to record voice message" = "Impossible d'enregistrer un message vocal";
+/* No comment provided by engineer. */
+"Unblock" = "Débloquer";
+
+/* No comment provided by engineer. */
+"Unblock member" = "Débloquer ce membre";
+
+/* No comment provided by engineer. */
+"Unblock member?" = "Débloquer ce membre ?";
+
/* item status description */
"Unexpected error: %@" = "Erreur inattendue : %@";
@@ -3473,6 +3623,27 @@
/* No comment provided by engineer. */
"You are already connected to %@." = "Vous êtes déjà connecté·e à %@ via ce lien.";
+/* No comment provided by engineer. */
+"You are already connecting to %@." = "Vous êtes déjà en train de vous connecter à %@.";
+
+/* No comment provided by engineer. */
+"You are already connecting via this one-time link!" = "Vous êtes déjà connecté(e) via ce lien unique !";
+
+/* No comment provided by engineer. */
+"You are already in group %@." = "Vous êtes déjà dans le groupe %@.";
+
+/* No comment provided by engineer. */
+"You are already joining the group %@." = "Vous êtes déjà en train de rejoindre le groupe %@.";
+
+/* No comment provided by engineer. */
+"You are already joining the group via this link!" = "Vous êtes déjà en train de rejoindre le groupe via ce lien !";
+
+/* No comment provided by engineer. */
+"You are already joining the group via this link." = "Vous êtes déjà en train de rejoindre le groupe via ce lien.";
+
+/* No comment provided by engineer. */
+"You are already joining the group!\nRepeat join request?" = "Vous êtes déjà membre de ce groupe !\nRépéter la demande d'adhésion ?";
+
/* No comment provided by engineer. */
"You are connected to the server used to receive messages from this contact." = "Vous êtes connecté·e au serveur utilisé pour recevoir les messages de ce contact.";
@@ -3548,6 +3719,12 @@
/* No comment provided by engineer. */
"You could not be verified; please try again." = "Vous n'avez pas pu être vérifié·e ; veuillez réessayer.";
+/* No comment provided by engineer. */
+"You have already requested connection via this address!" = "Vous avez déjà demandé une connexion via cette adresse !";
+
+/* No comment provided by engineer. */
+"You have already requested connection!\nRepeat connection request?" = "Vous avez déjà demandé une connexion !\nRépéter la demande de connexion ?";
+
/* No comment provided by engineer. */
"You have no chats" = "Vous n'avez aucune discussion";
@@ -3590,6 +3767,9 @@
/* No comment provided by engineer. */
"You will be connected to group when the group host's device is online, please wait or check later!" = "Vous serez connecté·e au groupe lorsque l'appareil de l'hôte sera en ligne, veuillez attendre ou vérifier plus tard !";
+/* No comment provided by engineer. */
+"You will be connected when group link host's device is online, please wait or check later!" = "Vous serez connecté(e) lorsque l'appareil de l'hôte du lien de groupe sera en ligne, veuillez patienter ou vérifier plus tard !";
+
/* No comment provided by engineer. */
"You will be connected when your connection request is accepted, please wait or check later!" = "Vous serez connecté·e lorsque votre demande de connexion sera acceptée, veuillez attendre ou vérifier plus tard !";
@@ -3599,6 +3779,9 @@
/* No comment provided by engineer. */
"You will be required to authenticate when you start or resume the app after 30 seconds in background." = "Il vous sera demandé de vous authentifier lorsque vous démarrez ou reprenez l'application après 30 secondes en arrière-plan.";
+/* No comment provided by engineer. */
+"You will connect to all group members." = "Vous vous connecterez à tous les membres du groupe.";
+
/* No comment provided by engineer. */
"You will still receive calls and notifications from muted profiles when they are active." = "Vous continuerez à recevoir des appels et des notifications des profils mis en sourdine lorsqu'ils sont actifs.";
@@ -3662,6 +3845,9 @@
/* No comment provided by engineer. */
"Your privacy" = "Votre vie privée";
+/* No comment provided by engineer. */
+"Your profile" = "Votre profil";
+
/* No comment provided by engineer. */
"Your profile **%@** will be shared." = "Votre profil **%@** sera partagé.";
diff --git a/apps/ios/it.lproj/Localizable.strings b/apps/ios/it.lproj/Localizable.strings
index ac2cfc4964..256a64a663 100644
--- a/apps/ios/it.lproj/Localizable.strings
+++ b/apps/ios/it.lproj/Localizable.strings
@@ -118,12 +118,18 @@
/* No comment provided by engineer. */
"%@ %@" = "%@ %@";
+/* No comment provided by engineer. */
+"%@ and %@" = "%@ e %@";
+
/* No comment provided by engineer. */
"%@ and %@ connected" = "%@ e %@ sono connessi/e";
/* copied message info, at */
"%@ at %@:" = "%1$@ alle %2$@:";
+/* No comment provided by engineer. */
+"%@ connected" = "%@ si è connesso/a";
+
/* notification title */
"%@ is connected!" = "%@ è connesso/a!";
@@ -139,6 +145,9 @@
/* notification title */
"%@ wants to connect!" = "%@ si vuole connettere!";
+/* No comment provided by engineer. */
+"%@, %@ and %lld members" = "%@, %@ e %lld membri";
+
/* No comment provided by engineer. */
"%@, %@ and %lld other members connected" = "%@, %@ e altri %lld membri sono connessi";
@@ -178,9 +187,21 @@
/* No comment provided by engineer. */
"%lld file(s) with total size of %@" = "%lld file con dimensione totale di %@";
+/* No comment provided by engineer. */
+"%lld group events" = "%lld eventi del gruppo";
+
/* No comment provided by engineer. */
"%lld members" = "%lld membri";
+/* No comment provided by engineer. */
+"%lld messages blocked" = "%lld messaggi bloccati";
+
+/* No comment provided by engineer. */
+"%lld messages marked deleted" = "%lld messaggi contrassegnati eliminati";
+
+/* No comment provided by engineer. */
+"%lld messages moderated by %@" = "%lld messaggi moderati da %@";
+
/* No comment provided by engineer. */
"%lld minutes" = "%lld minuti";
@@ -229,6 +250,9 @@
/* No comment provided by engineer. */
"~strike~" = "\\~barrato~";
+/* time to disappear */
+"0 sec" = "0 sec";
+
/* No comment provided by engineer. */
"0s" = "0s";
@@ -371,6 +395,9 @@
/* No comment provided by engineer. */
"All messages will be deleted - this cannot be undone! The messages will be deleted ONLY for you." = "Tutti i messaggi verranno eliminati, non è reversibile! I messaggi verranno eliminati SOLO per te.";
+/* No comment provided by engineer. */
+"All new messages from %@ will be hidden!" = "Tutti i nuovi messaggi da %@ verrranno nascosti!";
+
/* No comment provided by engineer. */
"All your contacts will remain connected." = "Tutti i tuoi contatti resteranno connessi.";
@@ -434,6 +461,12 @@
/* No comment provided by engineer. */
"Already connected?" = "Già connesso/a?";
+/* No comment provided by engineer. */
+"Already connecting!" = "Già in connessione!";
+
+/* No comment provided by engineer. */
+"Already joining the group!" = "Già in ingresso nel gruppo!";
+
/* pref value */
"always" = "sempre";
@@ -443,6 +476,9 @@
/* No comment provided by engineer. */
"An empty chat profile with the provided name is created, and the app opens as usual." = "Viene creato un profilo di chat vuoto con il nome scelto e l'app si apre come al solito.";
+/* No comment provided by engineer. */
+"and %lld other events" = "e altri %lld eventi";
+
/* No comment provided by engineer. */
"Answer call" = "Rispondi alla chiamata";
@@ -527,6 +563,18 @@
/* No comment provided by engineer. */
"Better messages" = "Messaggi migliorati";
+/* No comment provided by engineer. */
+"Block" = "Blocca";
+
+/* No comment provided by engineer. */
+"Block member" = "Blocca membro";
+
+/* No comment provided by engineer. */
+"Block member?" = "Bloccare il membro?";
+
+/* No comment provided by engineer. */
+"blocked" = "bloccato";
+
/* No comment provided by engineer. */
"bold" = "grassetto";
@@ -622,7 +670,7 @@
"changed address for you" = "indirizzo cambiato per te";
/* rcv group event chat item */
-"changed role of %@ to %@" = "cambiato il ruolo di %1$@ in %2$@";
+"changed role of %@ to %@" = "ha cambiato il ruolo di %1$@ in %2$@";
/* rcv group event chat item */
"changed your role to %@" = "cambiato il tuo ruolo in %@";
@@ -726,6 +774,18 @@
/* No comment provided by engineer. */
"connect to SimpleX Chat developers." = "connettiti agli sviluppatori di SimpleX Chat.";
+/* No comment provided by engineer. */
+"Connect to yourself?" = "Connettersi a te stesso?";
+
+/* No comment provided by engineer. */
+"Connect to yourself?\nThis is your own one-time link!" = "Connettersi a te stesso?\nQuesto è il tuo link una tantum!";
+
+/* No comment provided by engineer. */
+"Connect to yourself?\nThis is your own SimpleX address!" = "Connettersi a te stesso?\nQuesto è il tuo indirizzo SimpleX!";
+
+/* No comment provided by engineer. */
+"Connect via contact address" = "Connettere via indirizzo del contatto";
+
/* No comment provided by engineer. */
"Connect via link" = "Connetti via link";
@@ -735,6 +795,9 @@
/* No comment provided by engineer. */
"Connect via one-time link" = "Connetti via link una tantum";
+/* No comment provided by engineer. */
+"Connect with %@" = "Connettersi con %@";
+
/* No comment provided by engineer. */
"connected" = "connesso/a";
@@ -831,6 +894,9 @@
/* No comment provided by engineer. */
"Core version: v%@" = "Versione core: v%@";
+/* No comment provided by engineer. */
+"Correct name to %@?" = "Correggere il nome a %@?";
+
/* No comment provided by engineer. */
"Create" = "Crea";
@@ -840,6 +906,9 @@
/* server test step */
"Create file" = "Crea file";
+/* No comment provided by engineer. */
+"Create group" = "Crea gruppo";
+
/* No comment provided by engineer. */
"Create group link" = "Crea link del gruppo";
@@ -852,6 +921,9 @@
/* No comment provided by engineer. */
"Create one-time invitation link" = "Crea link di invito una tantum";
+/* No comment provided by engineer. */
+"Create profile" = "Crea profilo";
+
/* server test step */
"Create queue" = "Crea coda";
@@ -966,6 +1038,9 @@
/* chat item action */
"Delete" = "Elimina";
+/* No comment provided by engineer. */
+"Delete %lld messages?" = "Eliminare %lld messaggi?";
+
/* No comment provided by engineer. */
"Delete address" = "Elimina indirizzo";
@@ -978,6 +1053,9 @@
/* No comment provided by engineer. */
"Delete all files" = "Elimina tutti i file";
+/* No comment provided by engineer. */
+"Delete and notify contact" = "Elimina e avvisa il contatto";
+
/* No comment provided by engineer. */
"Delete archive" = "Elimina archivio";
@@ -999,6 +1077,9 @@
/* No comment provided by engineer. */
"Delete Contact" = "Elimina contatto";
+/* No comment provided by engineer. */
+"Delete contact?\nThis cannot be undone!" = "Eliminare il contatto?\nNon è reversibile!";
+
/* No comment provided by engineer. */
"Delete database" = "Elimina database";
@@ -1074,6 +1155,9 @@
/* copied message info */
"Deleted at: %@" = "Eliminato il: %@";
+/* rcv direct event chat item */
+"deleted contact" = "contatto eliminato";
+
/* rcv group event chat item */
"deleted group" = "gruppo eliminato";
@@ -1305,6 +1389,9 @@
/* No comment provided by engineer. */
"Enter correct passphrase." = "Inserisci la password giusta.";
+/* No comment provided by engineer. */
+"Enter group name…" = "Inserisci il nome del gruppo…";
+
/* No comment provided by engineer. */
"Enter Passcode" = "Inserisci il codice di accesso";
@@ -1323,6 +1410,9 @@
/* placeholder */
"Enter welcome message… (optional)" = "Inserisci il messaggio di benvenuto… (facoltativo)";
+/* No comment provided by engineer. */
+"Enter your name…" = "Inserisci il tuo nome…";
+
/* No comment provided by engineer. */
"error" = "errore";
@@ -1494,6 +1584,9 @@
/* No comment provided by engineer. */
"Exit without saving" = "Esci senza salvare";
+/* chat item action */
+"Expand" = "Espandi";
+
/* No comment provided by engineer. */
"Export database" = "Esporta database";
@@ -1581,6 +1674,9 @@
/* No comment provided by engineer. */
"Full name:" = "Nome completo:";
+/* No comment provided by engineer. */
+"Fully decentralized – visible only to members." = "Completamente decentralizzato: visibile solo ai membri.";
+
/* No comment provided by engineer. */
"Fully re-implemented - work in background!" = "Completamente reimplementato - funziona in secondo piano!";
@@ -1593,6 +1689,12 @@
/* No comment provided by engineer. */
"Group" = "Gruppo";
+/* No comment provided by engineer. */
+"Group already exists" = "Il gruppo esiste già";
+
+/* No comment provided by engineer. */
+"Group already exists!" = "Il gruppo esiste già!";
+
/* No comment provided by engineer. */
"group deleted" = "gruppo eliminato";
@@ -1830,6 +1932,9 @@
/* invalid chat item */
"invalid data" = "dati non validi";
+/* No comment provided by engineer. */
+"Invalid name!" = "Nome non valido!";
+
/* No comment provided by engineer. */
"Invalid server address!" = "Indirizzo del server non valido!";
@@ -1908,9 +2013,18 @@
/* No comment provided by engineer. */
"Join group" = "Entra nel gruppo";
+/* No comment provided by engineer. */
+"Join group?" = "Entrare nel gruppo?";
+
/* No comment provided by engineer. */
"Join incognito" = "Entra in incognito";
+/* No comment provided by engineer. */
+"Join with current profile" = "Entra con il profilo attuale";
+
+/* No comment provided by engineer. */
+"Join your group?\nThis is your link for group %@!" = "Entrare nel tuo gruppo?\nQuesto è il tuo link per il gruppo %@!";
+
/* No comment provided by engineer. */
"Joining group" = "Ingresso nel gruppo";
@@ -2055,6 +2169,9 @@
/* No comment provided by engineer. */
"Messages & files" = "Messaggi";
+/* No comment provided by engineer. */
+"Messages from %@ will be shown!" = "I messaggi da %@ verranno mostrati!";
+
/* No comment provided by engineer. */
"Migrating database archive…" = "Migrazione archivio del database…";
@@ -2306,6 +2423,9 @@
/* authentication reason */
"Open chat console" = "Apri la console della chat";
+/* No comment provided by engineer. */
+"Open group" = "Apri gruppo";
+
/* No comment provided by engineer. */
"Open Settings" = "Apri le impostazioni";
@@ -2438,6 +2558,12 @@
/* No comment provided by engineer. */
"Profile image" = "Immagine del profilo";
+/* No comment provided by engineer. */
+"Profile name" = "Nome del profilo";
+
+/* No comment provided by engineer. */
+"Profile name:" = "Nome del profilo:";
+
/* No comment provided by engineer. */
"Profile password" = "Password del profilo";
@@ -2603,6 +2729,12 @@
/* No comment provided by engineer. */
"Renegotiate encryption?" = "Rinegoziare la crittografia?";
+/* No comment provided by engineer. */
+"Repeat connection request?" = "Ripetere la richiesta di connessione?";
+
+/* No comment provided by engineer. */
+"Repeat join request?" = "Ripetere la richiesta di ingresso?";
+
/* chat item action */
"Reply" = "Rispondi";
@@ -3044,6 +3176,9 @@
/* No comment provided by engineer. */
"Tap to activate profile." = "Tocca per attivare il profilo.";
+/* No comment provided by engineer. */
+"Tap to Connect" = "Tocca per connettere";
+
/* No comment provided by engineer. */
"Tap to join" = "Tocca per entrare";
@@ -3170,6 +3305,12 @@
/* No comment provided by engineer. */
"This group no longer exists." = "Questo gruppo non esiste più.";
+/* No comment provided by engineer. */
+"This is your own one-time link!" = "Questo è il tuo link una tantum!";
+
+/* No comment provided by engineer. */
+"This is your own SimpleX address!" = "Questo è il tuo indirizzo SimpleX!";
+
/* No comment provided by engineer. */
"This setting applies to messages in your current chat profile **%@**." = "Questa impostazione si applica ai messaggi del profilo di chat attuale **%@**.";
@@ -3227,6 +3368,15 @@
/* No comment provided by engineer. */
"Unable to record voice message" = "Impossibile registrare il messaggio vocale";
+/* No comment provided by engineer. */
+"Unblock" = "Sblocca";
+
+/* No comment provided by engineer. */
+"Unblock member" = "Sblocca membro";
+
+/* No comment provided by engineer. */
+"Unblock member?" = "Sbloccare il membro?";
+
/* item status description */
"Unexpected error: %@" = "Errore imprevisto: % @";
@@ -3473,6 +3623,27 @@
/* No comment provided by engineer. */
"You are already connected to %@." = "Sei già connesso/a a %@.";
+/* No comment provided by engineer. */
+"You are already connecting to %@." = "Ti stai già connettendo a %@.";
+
+/* No comment provided by engineer. */
+"You are already connecting via this one-time link!" = "Ti stai già connettendo tramite questo link una tantum!";
+
+/* No comment provided by engineer. */
+"You are already in group %@." = "Sei già nel gruppo %@.";
+
+/* No comment provided by engineer. */
+"You are already joining the group %@." = "Stai già entrando nel gruppo %@.";
+
+/* No comment provided by engineer. */
+"You are already joining the group via this link!" = "Stai già entrando nel gruppo tramite questo link!";
+
+/* No comment provided by engineer. */
+"You are already joining the group via this link." = "Stai già entrando nel gruppo tramite questo link.";
+
+/* No comment provided by engineer. */
+"You are already joining the group!\nRepeat join request?" = "Stai già entrando nel gruppo!\nRipetere la richiesta di ingresso?";
+
/* No comment provided by engineer. */
"You are connected to the server used to receive messages from this contact." = "Sei connesso/a al server usato per ricevere messaggi da questo contatto.";
@@ -3548,6 +3719,12 @@
/* No comment provided by engineer. */
"You could not be verified; please try again." = "Non è stato possibile verificarti, riprova.";
+/* No comment provided by engineer. */
+"You have already requested connection via this address!" = "Hai già richiesto la connessione tramite questo indirizzo!";
+
+/* No comment provided by engineer. */
+"You have already requested connection!\nRepeat connection request?" = "Hai già richiesto la connessione!\nRipetere la richiesta di connessione?";
+
/* No comment provided by engineer. */
"You have no chats" = "Non hai chat";
@@ -3590,6 +3767,9 @@
/* No comment provided by engineer. */
"You will be connected to group when the group host's device is online, please wait or check later!" = "Verrai connesso/a al gruppo quando il dispositivo dell'host del gruppo sarà in linea, attendi o controlla più tardi!";
+/* No comment provided by engineer. */
+"You will be connected when group link host's device is online, please wait or check later!" = "Verrai connesso/a quando il dispositivo dell'host del gruppo sarà in linea, attendi o controlla più tardi!";
+
/* No comment provided by engineer. */
"You will be connected when your connection request is accepted, please wait or check later!" = "Verrai connesso/a quando la tua richiesta di connessione verrà accettata, attendi o controlla più tardi!";
@@ -3599,6 +3779,9 @@
/* No comment provided by engineer. */
"You will be required to authenticate when you start or resume the app after 30 seconds in background." = "Dovrai autenticarti quando avvii o riapri l'app dopo 30 secondi in secondo piano.";
+/* No comment provided by engineer. */
+"You will connect to all group members." = "Ti connetterai a tutti i membri del gruppo.";
+
/* No comment provided by engineer. */
"You will still receive calls and notifications from muted profiles when they are active." = "Continuerai a ricevere chiamate e notifiche da profili silenziati quando sono attivi.";
@@ -3662,6 +3845,9 @@
/* No comment provided by engineer. */
"Your privacy" = "La tua privacy";
+/* No comment provided by engineer. */
+"Your profile" = "Il tuo profilo";
+
/* No comment provided by engineer. */
"Your profile **%@** will be shared." = "Il tuo profilo **%@** verrà condiviso.";
diff --git a/apps/ios/nl.lproj/Localizable.strings b/apps/ios/nl.lproj/Localizable.strings
index 5c53c0c34e..302d7f74e9 100644
--- a/apps/ios/nl.lproj/Localizable.strings
+++ b/apps/ios/nl.lproj/Localizable.strings
@@ -118,12 +118,18 @@
/* No comment provided by engineer. */
"%@ %@" = "%@ %@";
+/* No comment provided by engineer. */
+"%@ and %@" = "%@ en %@";
+
/* No comment provided by engineer. */
"%@ and %@ connected" = "%@ en %@ verbonden";
/* copied message info, at */
"%@ at %@:" = "%1$@ bij %2$@:";
+/* No comment provided by engineer. */
+"%@ connected" = "%@ verbonden";
+
/* notification title */
"%@ is connected!" = "%@ is verbonden!";
@@ -139,6 +145,9 @@
/* notification title */
"%@ wants to connect!" = "%@ wil verbinding maken!";
+/* No comment provided by engineer. */
+"%@, %@ and %lld members" = "%@, %@ en %lld leden";
+
/* No comment provided by engineer. */
"%@, %@ and %lld other members connected" = "%@, %@ en %lld andere leden hebben verbinding gemaakt";
@@ -178,9 +187,21 @@
/* No comment provided by engineer. */
"%lld file(s) with total size of %@" = "%lld bestand(en) met een totale grootte van %@";
+/* No comment provided by engineer. */
+"%lld group events" = "%lld groep gebeurtenissen";
+
/* No comment provided by engineer. */
"%lld members" = "%lld leden";
+/* No comment provided by engineer. */
+"%lld messages blocked" = "%lld berichten geblokkeerd";
+
+/* No comment provided by engineer. */
+"%lld messages marked deleted" = "%lld berichten gemarkeerd als verwijderd";
+
+/* No comment provided by engineer. */
+"%lld messages moderated by %@" = "%lld berichten gemodereerd door %@";
+
/* No comment provided by engineer. */
"%lld minutes" = "%lld minuten";
@@ -229,6 +250,9 @@
/* No comment provided by engineer. */
"~strike~" = "\\~staking~";
+/* time to disappear */
+"0 sec" = "0 sec";
+
/* No comment provided by engineer. */
"0s" = "0s";
@@ -371,6 +395,9 @@
/* No comment provided by engineer. */
"All messages will be deleted - this cannot be undone! The messages will be deleted ONLY for you." = "Alle berichten worden verwijderd, dit kan niet ongedaan worden gemaakt! De berichten worden ALLEEN voor jou verwijderd.";
+/* No comment provided by engineer. */
+"All new messages from %@ will be hidden!" = "Alle nieuwe berichten van %@ worden verborgen!";
+
/* No comment provided by engineer. */
"All your contacts will remain connected." = "Al uw contacten blijven verbonden.";
@@ -434,6 +461,12 @@
/* No comment provided by engineer. */
"Already connected?" = "Al verbonden?";
+/* No comment provided by engineer. */
+"Already connecting!" = "Al bezig met verbinden!";
+
+/* No comment provided by engineer. */
+"Already joining the group!" = "Al lid van de groep!";
+
/* pref value */
"always" = "altijd";
@@ -443,6 +476,9 @@
/* No comment provided by engineer. */
"An empty chat profile with the provided name is created, and the app opens as usual." = "Er wordt een leeg chatprofiel met de opgegeven naam gemaakt en de app wordt zoals gewoonlijk geopend.";
+/* No comment provided by engineer. */
+"and %lld other events" = "en %lld andere gebeurtenissen";
+
/* No comment provided by engineer. */
"Answer call" = "Beantwoord oproep";
@@ -527,6 +563,18 @@
/* No comment provided by engineer. */
"Better messages" = "Betere berichten";
+/* No comment provided by engineer. */
+"Block" = "Blokkeren";
+
+/* No comment provided by engineer. */
+"Block member" = "Lid blokkeren";
+
+/* No comment provided by engineer. */
+"Block member?" = "Lid blokkeren?";
+
+/* No comment provided by engineer. */
+"blocked" = "geblokkeerd";
+
/* No comment provided by engineer. */
"bold" = "vetgedrukt";
@@ -597,7 +645,7 @@
"Change lock mode" = "Wijzig de vergrendelings modus";
/* No comment provided by engineer. */
-"Change member role?" = "Rol van gebruiker wijzigen?";
+"Change member role?" = "Rol van lid wijzigen?";
/* authentication reason */
"Change passcode" = "Toegangscode wijzigen";
@@ -726,6 +774,18 @@
/* No comment provided by engineer. */
"connect to SimpleX Chat developers." = "maak verbinding met SimpleX Chat-ontwikkelaars.";
+/* No comment provided by engineer. */
+"Connect to yourself?" = "Verbinding maken met jezelf?";
+
+/* No comment provided by engineer. */
+"Connect to yourself?\nThis is your own one-time link!" = "Verbinding maken met jezelf?\nDit is uw eigen eenmalige link!";
+
+/* No comment provided by engineer. */
+"Connect to yourself?\nThis is your own SimpleX address!" = "Verbinding maken met jezelf?\nDit is uw eigen SimpleX adres!";
+
+/* No comment provided by engineer. */
+"Connect via contact address" = "Verbinding maken via contactadres";
+
/* No comment provided by engineer. */
"Connect via link" = "Maak verbinding via link";
@@ -735,6 +795,9 @@
/* No comment provided by engineer. */
"Connect via one-time link" = "Verbinden via een eenmalige link?";
+/* No comment provided by engineer. */
+"Connect with %@" = "Verbonden met %@";
+
/* No comment provided by engineer. */
"connected" = "verbonden";
@@ -831,6 +894,9 @@
/* No comment provided by engineer. */
"Core version: v%@" = "Core versie: v% @";
+/* No comment provided by engineer. */
+"Correct name to %@?" = "Juiste naam voor %@?";
+
/* No comment provided by engineer. */
"Create" = "Maak";
@@ -840,6 +906,9 @@
/* server test step */
"Create file" = "Bestand maken";
+/* No comment provided by engineer. */
+"Create group" = "Groep aanmaken";
+
/* No comment provided by engineer. */
"Create group link" = "Groep link maken";
@@ -852,6 +921,9 @@
/* No comment provided by engineer. */
"Create one-time invitation link" = "Maak een eenmalige uitnodiging link";
+/* No comment provided by engineer. */
+"Create profile" = "Maak een profiel aan";
+
/* server test step */
"Create queue" = "Maak een wachtrij";
@@ -966,6 +1038,9 @@
/* chat item action */
"Delete" = "Verwijderen";
+/* No comment provided by engineer. */
+"Delete %lld messages?" = "%lld berichten verwijderen?";
+
/* No comment provided by engineer. */
"Delete address" = "Adres verwijderen";
@@ -978,6 +1053,9 @@
/* No comment provided by engineer. */
"Delete all files" = "Verwijder alle bestanden";
+/* No comment provided by engineer. */
+"Delete and notify contact" = "Contact verwijderen en op de hoogte stellen";
+
/* No comment provided by engineer. */
"Delete archive" = "Archief verwijderen";
@@ -999,6 +1077,9 @@
/* No comment provided by engineer. */
"Delete Contact" = "Verwijder contact";
+/* No comment provided by engineer. */
+"Delete contact?\nThis cannot be undone!" = "Verwijder contact?\nDit kan niet ongedaan gemaakt worden!";
+
/* No comment provided by engineer. */
"Delete database" = "Database verwijderen";
@@ -1074,6 +1155,9 @@
/* copied message info */
"Deleted at: %@" = "Verwijderd om: %@";
+/* rcv direct event chat item */
+"deleted contact" = "verwijderd contact";
+
/* rcv group event chat item */
"deleted group" = "verwijderde groep";
@@ -1305,6 +1389,9 @@
/* No comment provided by engineer. */
"Enter correct passphrase." = "Voer het juiste wachtwoord in.";
+/* No comment provided by engineer. */
+"Enter group name…" = "Groep naam invoeren…";
+
/* No comment provided by engineer. */
"Enter Passcode" = "Voer toegangscode in";
@@ -1323,6 +1410,9 @@
/* placeholder */
"Enter welcome message… (optional)" = "Voer welkomst bericht in... (optioneel)";
+/* No comment provided by engineer. */
+"Enter your name…" = "Vul uw naam in…";
+
/* No comment provided by engineer. */
"error" = "fout";
@@ -1339,7 +1429,7 @@
"Error accessing database file" = "Fout bij toegang tot database bestand";
/* No comment provided by engineer. */
-"Error adding member(s)" = "Fout bij het toevoegen van gebruiker(s)";
+"Error adding member(s)" = "Fout bij het toevoegen van leden";
/* No comment provided by engineer. */
"Error changing address" = "Fout bij wijzigen van adres";
@@ -1417,7 +1507,7 @@
"Error receiving file" = "Fout bij ontvangen van bestand";
/* No comment provided by engineer. */
-"Error removing member" = "Fout bij verwijderen van gebruiker";
+"Error removing member" = "Fout bij verwijderen van lid";
/* No comment provided by engineer. */
"Error saving %@ servers" = "Fout bij opslaan van %@ servers";
@@ -1494,6 +1584,9 @@
/* No comment provided by engineer. */
"Exit without saving" = "Afsluiten zonder opslaan";
+/* chat item action */
+"Expand" = "Uitbreiden";
+
/* No comment provided by engineer. */
"Export database" = "Database exporteren";
@@ -1581,6 +1674,9 @@
/* No comment provided by engineer. */
"Full name:" = "Volledige naam:";
+/* No comment provided by engineer. */
+"Fully decentralized – visible only to members." = "Volledig gedecentraliseerd – alleen zichtbaar voor leden.";
+
/* No comment provided by engineer. */
"Fully re-implemented - work in background!" = "Volledig opnieuw geïmplementeerd - werk op de achtergrond!";
@@ -1593,6 +1689,12 @@
/* No comment provided by engineer. */
"Group" = "Groep";
+/* No comment provided by engineer. */
+"Group already exists" = "Groep bestaat al";
+
+/* No comment provided by engineer. */
+"Group already exists!" = "Groep bestaat al!";
+
/* No comment provided by engineer. */
"group deleted" = "groep verwijderd";
@@ -1830,6 +1932,9 @@
/* invalid chat item */
"invalid data" = "ongeldige gegevens";
+/* No comment provided by engineer. */
+"Invalid name!" = "Ongeldige naam!";
+
/* No comment provided by engineer. */
"Invalid server address!" = "Ongeldig server adres!";
@@ -1908,9 +2013,18 @@
/* No comment provided by engineer. */
"Join group" = "Word lid van groep";
+/* No comment provided by engineer. */
+"Join group?" = "Deelnemen aan groep?";
+
/* No comment provided by engineer. */
"Join incognito" = "Doe incognito mee";
+/* No comment provided by engineer. */
+"Join with current profile" = "Word lid met huidig profiel";
+
+/* No comment provided by engineer. */
+"Join your group?\nThis is your link for group %@!" = "Sluit u aan bij uw groep?\nDit is jouw link voor groep %@!";
+
/* No comment provided by engineer. */
"Joining group" = "Deel nemen aan groep";
@@ -2008,22 +2122,22 @@
"Max 30 seconds, received instantly." = "Max 30 seconden, direct ontvangen.";
/* member role */
-"member" = "gebruiker";
+"member" = "lid";
/* No comment provided by engineer. */
-"Member" = "Gebruiker";
+"Member" = "Lid";
/* rcv group event chat item */
"member connected" = "is toegetreden";
/* No comment provided by engineer. */
-"Member role will be changed to \"%@\". All group members will be notified." = "De rol van gebruiker wordt gewijzigd in \"%@\". Alle groepsleden worden op de hoogte gebracht.";
+"Member role will be changed to \"%@\". All group members will be notified." = "De rol van lid wordt gewijzigd in \"%@\". Alle groepsleden worden op de hoogte gebracht.";
/* No comment provided by engineer. */
-"Member role will be changed to \"%@\". The member will receive a new invitation." = "De rol van gebruiker wordt gewijzigd in \"%@\". Het lid ontvangt een nieuwe uitnodiging.";
+"Member role will be changed to \"%@\". The member will receive a new invitation." = "De rol van lid wordt gewijzigd in \"%@\". Het lid ontvangt een nieuwe uitnodiging.";
/* No comment provided by engineer. */
-"Member will be removed from group - this cannot be undone!" = "Gebruiker wordt uit de groep verwijderd, dit kan niet ongedaan worden gemaakt!";
+"Member will be removed from group - this cannot be undone!" = "Lid wordt uit de groep verwijderd, dit kan niet ongedaan worden gemaakt!";
/* item status text */
"Message delivery error" = "Fout bij bezorging van bericht";
@@ -2055,6 +2169,9 @@
/* No comment provided by engineer. */
"Messages & files" = "Berichten";
+/* No comment provided by engineer. */
+"Messages from %@ will be shown!" = "Berichten van %@ worden getoond!";
+
/* No comment provided by engineer. */
"Migrating database archive…" = "Database archief migreren…";
@@ -2262,7 +2379,7 @@
"Only group owners can change group preferences." = "Alleen groep eigenaren kunnen groep voorkeuren wijzigen.";
/* No comment provided by engineer. */
-"Only group owners can enable files and media." = "Alleen groepseigenaren kunnen bestanden en media inschakelen.";
+"Only group owners can enable files and media." = "Alleen groep eigenaren kunnen bestanden en media inschakelen.";
/* No comment provided by engineer. */
"Only group owners can enable voice messages." = "Alleen groep eigenaren kunnen spraak berichten inschakelen.";
@@ -2306,6 +2423,9 @@
/* authentication reason */
"Open chat console" = "Chat console openen";
+/* No comment provided by engineer. */
+"Open group" = "Open groep";
+
/* No comment provided by engineer. */
"Open Settings" = "Open instellingen";
@@ -2438,6 +2558,12 @@
/* No comment provided by engineer. */
"Profile image" = "profielfoto";
+/* No comment provided by engineer. */
+"Profile name" = "Profielnaam";
+
+/* No comment provided by engineer. */
+"Profile name:" = "Profielnaam:";
+
/* No comment provided by engineer. */
"Profile password" = "Profiel wachtwoord";
@@ -2577,10 +2703,10 @@
"Remove" = "Verwijderen";
/* No comment provided by engineer. */
-"Remove member" = "Gebruiker verwijderen";
+"Remove member" = "Lid verwijderen";
/* No comment provided by engineer. */
-"Remove member?" = "Gebruiker verwijderen?";
+"Remove member?" = "Lid verwijderen?";
/* No comment provided by engineer. */
"Remove passphrase from keychain?" = "Wachtwoord van de keychain verwijderen?";
@@ -2603,6 +2729,12 @@
/* No comment provided by engineer. */
"Renegotiate encryption?" = "Heronderhandelen over versleuteling?";
+/* No comment provided by engineer. */
+"Repeat connection request?" = "Verbindingsverzoek herhalen?";
+
+/* No comment provided by engineer. */
+"Repeat join request?" = "Deelnameverzoek herhalen?";
+
/* chat item action */
"Reply" = "Antwoord";
@@ -2667,7 +2799,7 @@
"Save and notify contact" = "Opslaan en Contact melden";
/* No comment provided by engineer. */
-"Save and notify group members" = "Opslaan en Groep leden melden";
+"Save and notify group members" = "Opslaan en groep leden melden";
/* No comment provided by engineer. */
"Save and update group profile" = "Groep profiel opslaan en bijwerken";
@@ -3045,10 +3177,13 @@
"Tap to activate profile." = "Tik om profiel te activeren.";
/* No comment provided by engineer. */
-"Tap to join" = "Tik om mee te doen";
+"Tap to Connect" = "Tik om verbinding te maken";
/* No comment provided by engineer. */
-"Tap to join incognito" = "Tik om incognito deel te nemen";
+"Tap to join" = "Tik om lid te worden";
+
+/* No comment provided by engineer. */
+"Tap to join incognito" = "Tik om incognito lid te worden";
/* No comment provided by engineer. */
"Tap to start a new chat" = "Tik om een nieuw gesprek te starten";
@@ -3170,6 +3305,12 @@
/* No comment provided by engineer. */
"This group no longer exists." = "Deze groep bestaat niet meer.";
+/* No comment provided by engineer. */
+"This is your own one-time link!" = "Dit is uw eigen eenmalige link!";
+
+/* No comment provided by engineer. */
+"This is your own SimpleX address!" = "Dit is uw eigen SimpleX adres!";
+
/* No comment provided by engineer. */
"This setting applies to messages in your current chat profile **%@**." = "Deze instelling is van toepassing op berichten in je huidige chat profiel **%@**.";
@@ -3227,6 +3368,15 @@
/* No comment provided by engineer. */
"Unable to record voice message" = "Kan spraakbericht niet opnemen";
+/* No comment provided by engineer. */
+"Unblock" = "Deblokkeren";
+
+/* No comment provided by engineer. */
+"Unblock member" = "Lid deblokkeren";
+
+/* No comment provided by engineer. */
+"Unblock member?" = "Lid deblokkeren?";
+
/* item status description */
"Unexpected error: %@" = "Onverwachte fout: %@";
@@ -3473,6 +3623,27 @@
/* No comment provided by engineer. */
"You are already connected to %@." = "U bent al verbonden met %@.";
+/* No comment provided by engineer. */
+"You are already connecting to %@." = "U maakt al verbinding met %@.";
+
+/* No comment provided by engineer. */
+"You are already connecting via this one-time link!" = "Je maakt al verbinding via deze eenmalige link!";
+
+/* No comment provided by engineer. */
+"You are already in group %@." = "Je zit al in groep %@.";
+
+/* No comment provided by engineer. */
+"You are already joining the group %@." = "Je bent al lid van de groep %@.";
+
+/* No comment provided by engineer. */
+"You are already joining the group via this link!" = "Je wordt al lid van de groep via deze link!";
+
+/* No comment provided by engineer. */
+"You are already joining the group via this link." = "Je wordt al lid van de groep via deze link.";
+
+/* No comment provided by engineer. */
+"You are already joining the group!\nRepeat join request?" = "Je sluit je al aan bij de groep!\nDeelnameverzoek herhalen?";
+
/* No comment provided by engineer. */
"You are connected to the server used to receive messages from this contact." = "U bent verbonden met de server die wordt gebruikt om berichten van dit contact te ontvangen.";
@@ -3548,6 +3719,12 @@
/* No comment provided by engineer. */
"You could not be verified; please try again." = "U kon niet worden geverifieerd; probeer het opnieuw.";
+/* No comment provided by engineer. */
+"You have already requested connection via this address!" = "U heeft al een verbinding aangevraagd via dit adres!";
+
+/* No comment provided by engineer. */
+"You have already requested connection!\nRepeat connection request?" = "Je hebt al verbinding aangevraagd!\nVerbindingsverzoek herhalen?";
+
/* No comment provided by engineer. */
"You have no chats" = "Je hebt geen gesprekken";
@@ -3590,6 +3767,9 @@
/* No comment provided by engineer. */
"You will be connected to group when the group host's device is online, please wait or check later!" = "Je wordt verbonden met de groep wanneer het apparaat van de groep host online is, even geduld a.u.b. of controleer het later!";
+/* No comment provided by engineer. */
+"You will be connected when group link host's device is online, please wait or check later!" = "U wordt verbonden wanneer het apparaat van de groep link host online is. Wacht even of controleer het later opnieuw!";
+
/* No comment provided by engineer. */
"You will be connected when your connection request is accepted, please wait or check later!" = "U wordt verbonden wanneer uw verbindingsverzoek wordt geaccepteerd, even geduld a.u.b. of controleer later!";
@@ -3599,6 +3779,9 @@
/* No comment provided by engineer. */
"You will be required to authenticate when you start or resume the app after 30 seconds in background." = "U moet zich authenticeren wanneer u de app na 30 seconden op de achtergrond start of hervat.";
+/* No comment provided by engineer. */
+"You will connect to all group members." = "Je maakt verbinding met alle leden.";
+
/* No comment provided by engineer. */
"You will still receive calls and notifications from muted profiles when they are active." = "U ontvangt nog steeds oproepen en meldingen van gedempte profielen wanneer deze actief zijn.";
@@ -3662,6 +3845,9 @@
/* No comment provided by engineer. */
"Your privacy" = "Uw privacy";
+/* No comment provided by engineer. */
+"Your profile" = "Jouw profiel";
+
/* No comment provided by engineer. */
"Your profile **%@** will be shared." = "Uw profiel **%@** wordt gedeeld.";
diff --git a/apps/ios/pl.lproj/Localizable.strings b/apps/ios/pl.lproj/Localizable.strings
index 1c57568f90..3eb86d2a12 100644
--- a/apps/ios/pl.lproj/Localizable.strings
+++ b/apps/ios/pl.lproj/Localizable.strings
@@ -118,12 +118,18 @@
/* No comment provided by engineer. */
"%@ %@" = "%@ %@";
+/* No comment provided by engineer. */
+"%@ and %@" = "%@ i %@";
+
/* No comment provided by engineer. */
"%@ and %@ connected" = "%@ i %@ połączeni";
/* copied message info, at */
"%@ at %@:" = "%1$@ o %2$@:";
+/* No comment provided by engineer. */
+"%@ connected" = "%@ połączony";
+
/* notification title */
"%@ is connected!" = "%@ jest połączony!";
@@ -139,6 +145,9 @@
/* notification title */
"%@ wants to connect!" = "%@ chce się połączyć!";
+/* No comment provided by engineer. */
+"%@, %@ and %lld members" = "%@, %@ i %lld członków";
+
/* No comment provided by engineer. */
"%@, %@ and %lld other members connected" = "%@, %@ i %lld innych członków połączeni";
@@ -178,9 +187,21 @@
/* No comment provided by engineer. */
"%lld file(s) with total size of %@" = "%lld plik(i) o całkowitym rozmiarze %@";
+/* No comment provided by engineer. */
+"%lld group events" = "%lld wydarzeń grupy";
+
/* No comment provided by engineer. */
"%lld members" = "%lld członków";
+/* No comment provided by engineer. */
+"%lld messages blocked" = "%lld wiadomości zablokowanych";
+
+/* No comment provided by engineer. */
+"%lld messages marked deleted" = "%lld wiadomości oznaczonych do usunięcia";
+
+/* No comment provided by engineer. */
+"%lld messages moderated by %@" = "%lld wiadomości zmoderowanych przez %@";
+
/* No comment provided by engineer. */
"%lld minutes" = "%lld minut";
@@ -229,6 +250,9 @@
/* No comment provided by engineer. */
"~strike~" = "\\~strajk~";
+/* time to disappear */
+"0 sec" = "0 sek";
+
/* No comment provided by engineer. */
"0s" = "0s";
@@ -371,6 +395,9 @@
/* No comment provided by engineer. */
"All messages will be deleted - this cannot be undone! The messages will be deleted ONLY for you." = "Wszystkie wiadomości zostaną usunięte - nie można tego cofnąć! Wiadomości zostaną usunięte TYLKO dla Ciebie.";
+/* No comment provided by engineer. */
+"All new messages from %@ will be hidden!" = "Wszystkie nowe wiadomości z %@ zostaną ukryte!";
+
/* No comment provided by engineer. */
"All your contacts will remain connected." = "Wszystkie Twoje kontakty pozostaną połączone.";
@@ -434,6 +461,12 @@
/* No comment provided by engineer. */
"Already connected?" = "Już połączony?";
+/* No comment provided by engineer. */
+"Already connecting!" = "Już połączony!";
+
+/* No comment provided by engineer. */
+"Already joining the group!" = "Już dołączono do grupy!";
+
/* pref value */
"always" = "zawsze";
@@ -443,6 +476,9 @@
/* No comment provided by engineer. */
"An empty chat profile with the provided name is created, and the app opens as usual." = "Tworzony jest pusty profil czatu o podanej nazwie, a aplikacja otwiera się jak zwykle.";
+/* No comment provided by engineer. */
+"and %lld other events" = "i %lld innych wydarzeń";
+
/* No comment provided by engineer. */
"Answer call" = "Odbierz połączenie";
@@ -527,6 +563,18 @@
/* No comment provided by engineer. */
"Better messages" = "Lepsze wiadomości";
+/* No comment provided by engineer. */
+"Block" = "Zablokuj";
+
+/* No comment provided by engineer. */
+"Block member" = "Zablokuj członka";
+
+/* No comment provided by engineer. */
+"Block member?" = "Zablokować członka?";
+
+/* No comment provided by engineer. */
+"blocked" = "zablokowany";
+
/* No comment provided by engineer. */
"bold" = "pogrubiona";
@@ -726,6 +774,18 @@
/* No comment provided by engineer. */
"connect to SimpleX Chat developers." = "połącz się z deweloperami SimpleX Chat.";
+/* No comment provided by engineer. */
+"Connect to yourself?" = "Połączyć się ze sobą?";
+
+/* No comment provided by engineer. */
+"Connect to yourself?\nThis is your own one-time link!" = "Połączyć się ze sobą?\nTo jest twój jednorazowy link!";
+
+/* No comment provided by engineer. */
+"Connect to yourself?\nThis is your own SimpleX address!" = "Połączyć się ze sobą?\nTo jest twój własny adres SimpleX!";
+
+/* No comment provided by engineer. */
+"Connect via contact address" = "Połącz przez adres kontaktowy";
+
/* No comment provided by engineer. */
"Connect via link" = "Połącz się przez link";
@@ -735,6 +795,9 @@
/* No comment provided by engineer. */
"Connect via one-time link" = "Połącz przez jednorazowy link";
+/* No comment provided by engineer. */
+"Connect with %@" = "Połącz z %@";
+
/* No comment provided by engineer. */
"connected" = "połączony";
@@ -831,6 +894,9 @@
/* No comment provided by engineer. */
"Core version: v%@" = "Wersja rdzenia: v%@";
+/* No comment provided by engineer. */
+"Correct name to %@?" = "Poprawić imię na %@?";
+
/* No comment provided by engineer. */
"Create" = "Utwórz";
@@ -840,6 +906,9 @@
/* server test step */
"Create file" = "Utwórz plik";
+/* No comment provided by engineer. */
+"Create group" = "Utwórz grupę";
+
/* No comment provided by engineer. */
"Create group link" = "Utwórz link do grupy";
@@ -852,6 +921,9 @@
/* No comment provided by engineer. */
"Create one-time invitation link" = "Utwórz jednorazowy link do zaproszenia";
+/* No comment provided by engineer. */
+"Create profile" = "Utwórz profil";
+
/* server test step */
"Create queue" = "Utwórz kolejkę";
@@ -966,6 +1038,9 @@
/* chat item action */
"Delete" = "Usuń";
+/* No comment provided by engineer. */
+"Delete %lld messages?" = "Usunąć %lld wiadomości?";
+
/* No comment provided by engineer. */
"Delete address" = "Usuń adres";
@@ -978,6 +1053,9 @@
/* No comment provided by engineer. */
"Delete all files" = "Usuń wszystkie pliki";
+/* No comment provided by engineer. */
+"Delete and notify contact" = "Usuń i powiadom kontakt";
+
/* No comment provided by engineer. */
"Delete archive" = "Usuń archiwum";
@@ -999,6 +1077,9 @@
/* No comment provided by engineer. */
"Delete Contact" = "Usuń Kontakt";
+/* No comment provided by engineer. */
+"Delete contact?\nThis cannot be undone!" = "Usunąć kontakt?\nTo nie może być cofnięte!";
+
/* No comment provided by engineer. */
"Delete database" = "Usuń bazę danych";
@@ -1074,6 +1155,9 @@
/* copied message info */
"Deleted at: %@" = "Usunięto o: %@";
+/* rcv direct event chat item */
+"deleted contact" = "usunięto kontakt";
+
/* rcv group event chat item */
"deleted group" = "usunięta grupa";
@@ -1305,6 +1389,9 @@
/* No comment provided by engineer. */
"Enter correct passphrase." = "Wprowadź poprawne hasło.";
+/* No comment provided by engineer. */
+"Enter group name…" = "Wpisz nazwę grupy…";
+
/* No comment provided by engineer. */
"Enter Passcode" = "Wprowadź Pin";
@@ -1323,6 +1410,9 @@
/* placeholder */
"Enter welcome message… (optional)" = "Wpisz wiadomość powitalną… (opcjonalne)";
+/* No comment provided by engineer. */
+"Enter your name…" = "Wpisz swoją nazwę…";
+
/* No comment provided by engineer. */
"error" = "błąd";
@@ -1494,6 +1584,9 @@
/* No comment provided by engineer. */
"Exit without saving" = "Wyjdź bez zapisywania";
+/* chat item action */
+"Expand" = "Rozszerz";
+
/* No comment provided by engineer. */
"Export database" = "Eksportuj bazę danych";
@@ -1581,6 +1674,9 @@
/* No comment provided by engineer. */
"Full name:" = "Pełna nazwa:";
+/* No comment provided by engineer. */
+"Fully decentralized – visible only to members." = "W pełni zdecentralizowana – widoczna tylko dla członków.";
+
/* No comment provided by engineer. */
"Fully re-implemented - work in background!" = "W pełni ponownie zaimplementowany - praca w tle!";
@@ -1593,6 +1689,12 @@
/* No comment provided by engineer. */
"Group" = "Grupa";
+/* No comment provided by engineer. */
+"Group already exists" = "Grupa już istnieje";
+
+/* No comment provided by engineer. */
+"Group already exists!" = "Grupa już istnieje!";
+
/* No comment provided by engineer. */
"group deleted" = "grupa usunięta";
@@ -1830,6 +1932,9 @@
/* invalid chat item */
"invalid data" = "nieprawidłowe dane";
+/* No comment provided by engineer. */
+"Invalid name!" = "Nieprawidłowa nazwa!";
+
/* No comment provided by engineer. */
"Invalid server address!" = "Nieprawidłowy adres serwera!";
@@ -1908,9 +2013,18 @@
/* No comment provided by engineer. */
"Join group" = "Dołącz do grupy";
+/* No comment provided by engineer. */
+"Join group?" = "Dołączyć do grupy?";
+
/* No comment provided by engineer. */
"Join incognito" = "Dołącz incognito";
+/* No comment provided by engineer. */
+"Join with current profile" = "Dołącz z obecnym profilem";
+
+/* No comment provided by engineer. */
+"Join your group?\nThis is your link for group %@!" = "Dołączyć do twojej grupy?\nTo jest twój link do grupy %@!";
+
/* No comment provided by engineer. */
"Joining group" = "Dołączanie do grupy";
@@ -2055,6 +2169,9 @@
/* No comment provided by engineer. */
"Messages & files" = "Wiadomości i pliki";
+/* No comment provided by engineer. */
+"Messages from %@ will be shown!" = "Wiadomości od %@ zostaną pokazane!";
+
/* No comment provided by engineer. */
"Migrating database archive…" = "Migrowanie archiwum bazy danych…";
@@ -2306,6 +2423,9 @@
/* authentication reason */
"Open chat console" = "Otwórz konsolę czatu";
+/* No comment provided by engineer. */
+"Open group" = "Grupa otwarta";
+
/* No comment provided by engineer. */
"Open Settings" = "Otwórz Ustawienia";
@@ -2438,6 +2558,12 @@
/* No comment provided by engineer. */
"Profile image" = "Zdjęcie profilowe";
+/* No comment provided by engineer. */
+"Profile name" = "Nazwa profilu";
+
+/* No comment provided by engineer. */
+"Profile name:" = "Nazwa profilu:";
+
/* No comment provided by engineer. */
"Profile password" = "Hasło profilu";
@@ -2603,6 +2729,12 @@
/* No comment provided by engineer. */
"Renegotiate encryption?" = "Renegocjować szyfrowanie?";
+/* No comment provided by engineer. */
+"Repeat connection request?" = "Powtórzyć prośbę połączenia?";
+
+/* No comment provided by engineer. */
+"Repeat join request?" = "Powtórzyć prośbę dołączenia?";
+
/* chat item action */
"Reply" = "Odpowiedz";
@@ -3044,6 +3176,9 @@
/* No comment provided by engineer. */
"Tap to activate profile." = "Dotknij, aby aktywować profil.";
+/* No comment provided by engineer. */
+"Tap to Connect" = "Dotknij aby połączyć";
+
/* No comment provided by engineer. */
"Tap to join" = "Dotknij, aby dołączyć";
@@ -3170,6 +3305,12 @@
/* No comment provided by engineer. */
"This group no longer exists." = "Ta grupa już nie istnieje.";
+/* No comment provided by engineer. */
+"This is your own one-time link!" = "To jest twój jednorazowy link!";
+
+/* No comment provided by engineer. */
+"This is your own SimpleX address!" = "To jest twój własny adres SimpleX!";
+
/* No comment provided by engineer. */
"This setting applies to messages in your current chat profile **%@**." = "To ustawienie dotyczy wiadomości Twojego bieżącego profilu czatu **%@**.";
@@ -3227,6 +3368,15 @@
/* No comment provided by engineer. */
"Unable to record voice message" = "Nie można nagrać wiadomości głosowej";
+/* No comment provided by engineer. */
+"Unblock" = "Odblokuj";
+
+/* No comment provided by engineer. */
+"Unblock member" = "Odblokuj członka";
+
+/* No comment provided by engineer. */
+"Unblock member?" = "Odblokować członka?";
+
/* item status description */
"Unexpected error: %@" = "Nieoczekiwany błąd: %@";
@@ -3473,6 +3623,27 @@
/* No comment provided by engineer. */
"You are already connected to %@." = "Jesteś już połączony z %@.";
+/* No comment provided by engineer. */
+"You are already connecting to %@." = "Już się łączysz z %@.";
+
+/* No comment provided by engineer. */
+"You are already connecting via this one-time link!" = "Już jesteś połączony z tym jednorazowym linkiem!";
+
+/* No comment provided by engineer. */
+"You are already in group %@." = "Już jesteś w grupie %@.";
+
+/* No comment provided by engineer. */
+"You are already joining the group %@." = "Już dołączasz do grupy %@.";
+
+/* No comment provided by engineer. */
+"You are already joining the group via this link!" = "Już dołączasz do grupy przez ten link!";
+
+/* No comment provided by engineer. */
+"You are already joining the group via this link." = "Już dołączasz do grupy przez ten link.";
+
+/* No comment provided by engineer. */
+"You are already joining the group!\nRepeat join request?" = "Już dołączasz do grupy!\nPowtórzyć prośbę dołączenia?";
+
/* No comment provided by engineer. */
"You are connected to the server used to receive messages from this contact." = "Jesteś połączony z serwerem używanym do odbierania wiadomości od tego kontaktu.";
@@ -3548,6 +3719,12 @@
/* No comment provided by engineer. */
"You could not be verified; please try again." = "Nie można zweryfikować użytkownika; proszę spróbować ponownie.";
+/* No comment provided by engineer. */
+"You have already requested connection via this address!" = "Już prosiłeś o połączenie na ten adres!";
+
+/* No comment provided by engineer. */
+"You have already requested connection!\nRepeat connection request?" = "Już prosiłeś o połączenie!\nPowtórzyć prośbę połączenia?";
+
/* No comment provided by engineer. */
"You have no chats" = "Nie masz czatów";
@@ -3590,6 +3767,9 @@
/* No comment provided by engineer. */
"You will be connected to group when the group host's device is online, please wait or check later!" = "Zostaniesz połączony do grupy, gdy urządzenie gospodarza grupy będzie online, proszę czekać lub sprawdzić później!";
+/* No comment provided by engineer. */
+"You will be connected when group link host's device is online, please wait or check later!" = "Zostaniesz połączony, gdy urządzenie hosta grupy będzie online, proszę czekać lub sprawdzić później!";
+
/* No comment provided by engineer. */
"You will be connected when your connection request is accepted, please wait or check later!" = "Zostaniesz połączony, gdy Twoje żądanie połączenia zostanie zaakceptowane, proszę czekać lub sprawdzić później!";
@@ -3599,6 +3779,9 @@
/* No comment provided by engineer. */
"You will be required to authenticate when you start or resume the app after 30 seconds in background." = "Uwierzytelnienie będzie wymagane przy uruchamianiu lub wznawianiu aplikacji po 30 sekundach w tle.";
+/* No comment provided by engineer. */
+"You will connect to all group members." = "Zostaniesz połączony ze wszystkimi członkami grupy.";
+
/* No comment provided by engineer. */
"You will still receive calls and notifications from muted profiles when they are active." = "Nadal będziesz otrzymywać połączenia i powiadomienia z wyciszonych profili, gdy są one aktywne.";
@@ -3662,6 +3845,9 @@
/* No comment provided by engineer. */
"Your privacy" = "Twoja prywatność";
+/* No comment provided by engineer. */
+"Your profile" = "Twój profil";
+
/* No comment provided by engineer. */
"Your profile **%@** will be shared." = "Twój profil **%@** zostanie udostępniony.";
diff --git a/apps/multiplatform/common/src/commonMain/resources/MR/de/strings.xml b/apps/multiplatform/common/src/commonMain/resources/MR/de/strings.xml
index 63c3d59cad..677033f7e9 100644
--- a/apps/multiplatform/common/src/commonMain/resources/MR/de/strings.xml
+++ b/apps/multiplatform/common/src/commonMain/resources/MR/de/strings.xml
@@ -3,11 +3,11 @@
SimpleX
k
- Über den Kontakt-Link verbinden?
- Über den Einladungslink verbinden\?
- Über den Gruppen-Link verbinden?
+ Über die Kontakt-Adresse verbinden?
+ Über den Einmal-Link verbinden?
+ Der Gruppe beitreten?
Ihr Profil wird an den Kontakt gesendet, von dem Sie diesen Link erhalten haben.
- Sie werden der Gruppe beitreten, auf die sich dieser Link bezieht, und sich mit deren Gruppenmitgliedern verbinden.
+ Sie werden mit allen Gruppenmitgliedern verbunden.
Verbinden
Verbunden
@@ -21,7 +21,7 @@
als gelöscht markiert
Das Senden von Dateien wird noch nicht unterstützt
Der Empfang von Dateien wird noch nicht unterstützt
- Meine Daten
+ Ihre Daten
Unbekanntes Nachrichtenformat
Unzulässiges Nachrichtenformat
@@ -344,8 +344,8 @@
Einmal-Einladungslink erstellen
Einmal-Einladungslink
- Meine Einstellungen
- Meine SimpleX-Adresse
+ Ihre Einstellungen
+ Ihre SimpleX-Adresse
Datenbank-Passwort & -Export
Über SimpleX Chat
Wie man SimpleX nutzt
@@ -418,7 +418,7 @@
Link teilen
Adresse löschen
- Angezeigter Name:
+ Profilname:
Vollständiger Name:
Mein aktuelles Chat-Profil
Ihr Profil wird auf Ihrem Gerät gespeichert und nur mit Ihren Kontakten geteilt. SimpleX-Server können Ihr Profil nicht sehen.
@@ -437,7 +437,7 @@
Ihr Profil, Ihre Kontakte und zugestellten Nachrichten werden auf Ihrem Gerät gespeichert.
Das Profil wird nur mit Ihren Kontakten geteilt.
Der angezeigte Name darf keine Leerzeichen enthalten.
- Angezeigter Name
+ Geben Sie Ihren Namen ein:
Erstellen
Über SimpleX
@@ -553,7 +553,7 @@
\n3. Die Verbindung wurde kompromittiert.
Datenschutz & Sicherheit
- Meine Privatsphäre
+ Ihre Privatsphäre
App-Bildschirm schützen
Bilder automatisch akzeptieren
Link-Vorschau senden
@@ -712,7 +712,7 @@
Die Gruppeneinladung ist abgelaufen
hat %1$s eingeladen.
- beigetreten
+ verbunden
hat die Gruppe verlassen
änderte die Rolle von %s auf %s
änderte Ihre Rolle auf %s
@@ -813,8 +813,8 @@
Empfängeradresse wechseln
Geheime Gruppe erstellen
- Die Gruppe ist vollständig dezentralisiert – sie ist nur für Mitglieder sichtbar.
- Anzeigename der Gruppe:
+ Vollständig dezentralisiert – nur für Mitglieder sichtbar.
+ Geben Sie den Gruppennamen ein:
Vollständiger Gruppenname:
Ihr Chat-Profil wird an die Gruppenmitglieder gesendet
@@ -1001,7 +1001,7 @@
Transport-Isolation
Chat-Profil löschen\?
Fehler beim Löschen des Benutzerprofils
- Meine Chat-Profile
+ Ihre Chat-Profile
Verbindung
Chat-Profil
Dateien für alle Chat-Profile löschen
@@ -1084,7 +1084,7 @@
Sie können Anrufe und Benachrichtigungen auch von stummgeschalteten Profilen empfangen, solange diese aktiv sind.
Begrüßungsmeldung
Sie können ein Benutzerprofil verbergen oder stummschalten – für das Menü gedrückt halten.
- Geben Sie ein vollständiges Passwort in das Suchfeld auf der Seite \"Meine Chat-Profile\" ein, um Ihr verborgenes Profil zu sehen.
+ Geben Sie ein vollständiges Passwort in das Suchfeld auf der Seite \"Ihre Chat-Profile\" ein, um Ihr verborgenes Profil zu sehen.
Migrations-Bestätigung ungültig
Aktualisieren und den Chat öffnen
Datenbank-Aktualisierungen bestätigen
@@ -1451,9 +1451,9 @@
SimpleX kann nicht im Hintergrund ablaufen. Sie erhalten Benachrichtigungen nur dann, solange die App läuft.
Die App kann nach einer Minute im Hintergrund geschlossen werden.
App-Akkuverbrauch / Unbeschränkt , um Benachrichtigungen zu aktivieren.]]>
- %s, %s und %d weitere Mitglieder wurden verbunden
- %s und %s wurden verbunden
- %s, %s und %s wurden verbunden
+ %s, %s und %d weitere Mitglieder wurden mit Ihnen verbunden
+ %s und %s wurden mit Ihnen verbunden
+ %s, %s und %s wurden mit Ihnen verbunden
Nachrichtenentwurf
Letzte Nachrichten anzeigen
Die Datenbank wird verschlüsselt und das Passwort in den Einstellungen gespeichert.
@@ -1487,4 +1487,59 @@
\n- Schneller und stabiler.
Direktnachricht senden
Direkt miteinander verbunden
+ Erweitern
+ Verbindungsanfrage wiederholen?
+ Gelöschter Kontakt
+ Sie sind bereits mit %1$s verbunden.
+ Fehler
+ Sie sind über diesen Link bereits Mitglied der Gruppe.
+ Gruppe erstellen
+ Profil erstellen
+ %s und %s
+ Ihrer Gruppe beitreten?
+ Sie sind bereits Mitglied in der Gruppe %1$s.
+ Das ist Ihr eigener Einmal-Link!
+ %d Nachrichten als gelöscht markiert
+ Gruppe besteht bereits!
+ Bereits verbunden!
+ Das Video kann nicht dekodiert werden. Bitte probieren Sie ein anderes Video aus, oder kontaktieren Sie die Entwickler.
+ %s wurde mit Ihnen verbunden
+ und %d weitere Ereignisse
+ Über einen Link verbinden?
+ Sie sind bereits Mitglied der Gruppe!
+ %s, %s und %d Mitglieder
+ %d Nachrichten von %s moderiert
+ Mitglied freigeben
+ Mit Ihnen selbst verbinden?
+ Zum Verbinden antippen
+ Sie sind bereits Mitglied in der Gruppe %1$s.
+ Das ist Ihre eigene SimpleX-Adresse!
+ Richtiger Name für %s?
+ %d Nachrichten löschen?
+ Mit %1$s verbinden?
+ Mitglied entfernen
+ Blockieren
+ Mitglied freigeben?
+ %d Nachrichten blockiert
+ Mitglied blockieren
+ Verbindungsanfrage wiederholen?
+ Mitglied entfernen?
+ Kontakt löschen und benachrichtigen
+ Sie sind bereits über diesen Einmal-Link verbunden!
+ Gruppe öffnen
+ Die Nachrichten von %s werden angezeigt!
+ Fehler beim Senden der Einladung
+ Sie haben einen ungültigen Datei-Pfad geteilt. Bitte melden Sie diesen Fehler den App-Entwicklern.
+ Mitglied blockieren?
+ %d Gruppenereignisse
+ Ungültiger Name!
+ Das ist Ihr Link für die Gruppe %1$s!
+ Freigeben
+ Ungültiger Datei-Pfad
+ Sie haben über diese Adresse bereits eine Verbindung beantragt!
+ Die Konsole in einem neuen Fenster anzeigen
+ Alle neuen Nachrichten von %s werden ausgeblendet!
+ blockiert
+ Fehler bei der Neuverhandlung der Verschlüsselung
+ Neuverhandlung der Verschlüsselung fehlgeschlagen
\ No newline at end of file
diff --git a/apps/multiplatform/common/src/commonMain/resources/MR/fr/strings.xml b/apps/multiplatform/common/src/commonMain/resources/MR/fr/strings.xml
index 590ba49db8..cdf07dff6d 100644
--- a/apps/multiplatform/common/src/commonMain/resources/MR/fr/strings.xml
+++ b/apps/multiplatform/common/src/commonMain/resources/MR/fr/strings.xml
@@ -1,13 +1,13 @@
k
- Se connecter via le lien du contact \?
+ Se connecter via l\'adresse du contact ?
SimpleX
Votre profil va être envoyé au contact qui vous a envoyé ce lien.
- Vous allez rejoindre le groupe correspondant à ce lien et être mis en relation avec les autres membres du groupe.
+ Vous vous connecterez à tous les membres du groupe.
Se connecter
- Se connecter via le lien du groupe \?
- Se connecter via un lien d\'invitation \?
+ Rejoindre le groupe ?
+ Se connecter via un lien unique ?
erreur
connexion
connecté
@@ -427,7 +427,7 @@
Tous vos contacts resteront connectés.
Partager le lien
Supprimer l\'adresse
- Nom affiché :
+ Nom du profil :
Nom complet :
Votre profil est stocké sur votre appareil et partagé uniquement avec vos contacts. Les serveurs SimpleX ne peuvent pas voir votre profil.
Supprimer l\'image
@@ -436,7 +436,7 @@
La plateforme de messagerie et d\'applications qui protège votre vie privée et votre sécurité.
Nous ne stockons aucun de vos contacts ou messages (une fois délivrés) sur les serveurs.
Créer le profil
- Nom affiché
+ Saisissez votre nom :
Comment utiliser markdown
a + b
coloré
@@ -835,7 +835,7 @@
reçu, non autorisé
Autorise votre contact à envoyer des messages éphémères.
directe
- Le groupe est entièrement décentralisé – il n\'est visible que par ses membres.
+ Entièrement décentralisé – visible que par ses membres.
Les membres du groupes peuvent envoyer des messages éphémères.
Revenir en arrière
Interdire l’envoi de messages éphémères.
@@ -849,7 +849,7 @@
Seulement votre contact peut envoyer des messages éphémères.
Vous et votre contact êtes tous deux en mesure d\'envoyer des messages éphémères.
Les messages vocaux sont interdits dans ce groupe.
- Nom affiché du groupe :
+ Saisir le nom du groupe :
indirecte (%1$s)
Groupe
Connexion
@@ -1406,4 +1406,59 @@
Envoyer un message direct pour vous connecter
envoyer un message direct
s\'est connecté.e de manière directe
+ Développer
+ Répéter la demande de connexion ?
+ contact supprimé
+ Vous êtes déjà connecté(e) à %1$s.
+ Erreur
+ Vous êtes déjà en train de rejoindre le groupe via ce lien.
+ Créer un groupe
+ Créer le profil
+ %s et %s
+ Rejoindre votre groupe ?
+ Vous êtes déjà en train de rejoindre le groupe %1$s.
+ Voici votre propre lien unique !
+ %d messages marqués comme supprimés
+ Ce groupe existe déjà !
+ Déjà en connexion !
+ La vidéo ne peut pas être décodée. Veuillez essayer une autre vidéo ou contacter les développeurs.
+ %s connecté(e)
+ et %d autres événements
+ Se connecter via un lien ?
+ Groupe déjà rejoint !
+ %s, %s et %d membres
+ %d messages modérés par %s
+ Débloquer ce membre
+ Se connecter à soi-même ?
+ Tapez pour vous connecter
+ Vous êtes déjà dans le groupe %1$s.
+ Voici votre propre adresse SimpleX !
+ Corriger le nom pour %s ?
+ Supprimer %d messages ?
+ Se connecter avec %1$s ?
+ Retirer le membre
+ Bloquer
+ Débloquer ce membre ?
+ %d messages bloqués
+ Bloquer ce membre
+ Répéter la requête d\'adhésion ?
+ Retirer ce membre ?
+ Supprimer et en informer le contact
+ Vous êtes déjà connecté(e) via ce lien unique !
+ Ouvrir le groupe
+ Les messages de %s seront affichés !
+ Erreur lors de l\'envoi de l\'invitation
+ Vous avez partagé un chemin de fichier non valide. Signalez le problème aux développeurs de l\'application.
+ Bloquer ce membre ?
+ %d événements de groupe
+ Nom invalide !
+ Voici votre lien pour le groupe %1$s !
+ Débloquer
+ Chemin du fichier invalide
+ Vous avez déjà demandé une connexion via cette adresse !
+ Afficher la console dans une nouvelle fenêtre
+ Tous les nouveaux messages provenant de %s seront cachés !
+ blocké
+ Erreur lors de la renégociation du chiffrement
+ La renégociation du chiffrement a échoué.
\ No newline at end of file
diff --git a/apps/multiplatform/common/src/commonMain/resources/MR/it/strings.xml b/apps/multiplatform/common/src/commonMain/resources/MR/it/strings.xml
index 10af1170b1..3cdd4676b7 100644
--- a/apps/multiplatform/common/src/commonMain/resources/MR/it/strings.xml
+++ b/apps/multiplatform/common/src/commonMain/resources/MR/it/strings.xml
@@ -11,8 +11,8 @@
In attesa dell\'immagine
SimpleX
k
- Connettere via link di invito\?
- Connettere via link del gruppo\?
+ Connettere via link una tantum?
+ Entrare nel gruppo?
Il tuo profilo verrà inviato al contatto da cui hai ricevuto questo link.
Connetti
connesso
@@ -200,9 +200,9 @@
Conferma
Ripristina
OK
- Connettere via link del contatto\?
+ Connettere via indirizzo del contatto?
Tentativo di connessione al server usato per ricevere messaggi da questo contatto (errore: %1$s).
- Entrerai in un gruppo a cui si riferisce questo link e ti connetterai ai suoi membri.
+ Ti connetterai a tutti i membri del gruppo.
connessione %1$d
Descrizione
via %1$s
@@ -291,7 +291,7 @@
La password del database è necessaria per aprire la chat.
Elimina
I messaggi diretti tra i membri sono vietati in questo gruppo.
- Nome da mostrare
+ Inserisci il tuo nome:
Aggiungi un contatto: per creare il tuo codice QR una tantum per il tuo contatto.]]>
Scansiona codice QR: per connetterti al contatto che ti mostra il codice QR.]]>
File
@@ -346,7 +346,7 @@
Crea
Crea profilo
Elimina immagine
- Nome da mostrare:
+ Nome del profilo:
Il nome da mostrare non può contenere spazi.
Modifica immagine
Esci senza salvare
@@ -415,7 +415,7 @@
File: %s
Gruppo inattivo
indirizzo cambiato per te
- cambiato il ruolo di %s in %s
+ ha cambiato il ruolo di %s in %s
cambiato il tuo ruolo in %s
cambio indirizzo…
cambio indirizzo per %s…
@@ -458,7 +458,7 @@
Errore nella rimozione del membro
Errore nel salvataggio del profilo del gruppo
Gruppo
- Nome da mostrare del gruppo:
+ Inserisci il nome del gruppo:
Il profilo del gruppo è memorizzato sui dispositivi dei membri, non sui server.
sempre
Sia tu che il tuo contatto potete eliminare irreversibilmente i messaggi inviati.
@@ -828,7 +828,7 @@
Cambia indirizzo di ricezione
Sistema
Scadenza connessione TCP
- Il gruppo è completamente decentralizzato: è visibile solo ai membri.
+ Completamente decentralizzato: visibile solo ai membri.
Il ruolo verrà cambiato in \"%s\". Tutti i membri del gruppo riceveranno una notifica.
Il ruolo verrà cambiato in \"%s\". Il membro riceverà un nuovo invito.
Aggiorna
@@ -1406,4 +1406,59 @@
Invia messaggio diretto per connetterti
invia messaggio diretto
si è connesso/a direttamente
+ Espandi
+ Ripetere la richiesta di connessione?
+ contatto eliminato
+ Ti stai già connettendo a %1$s.
+ Errore
+ Stai già entrando nel gruppo tramite questo link.
+ Crea gruppo
+ Crea profilo
+ %s e %s
+ Entrare nel tuo gruppo?
+ Stai già entrando nel gruppo %1$s.
+ Questo è il tuo link una tantum!
+ %d messaggi contrassegnati eliminati
+ Il gruppo esiste già!
+ Già in connessione!
+ Il video non può essere decodificato. Prova un video diverso o contatta gli sviluppatori.
+ %s si è connesso/a
+ e altri %d eventi
+ Connettere via link?
+ Stai già entrando nel gruppo!
+ %s, %s e %d membri
+ %d messaggi moderati da %s
+ Sblocca membro
+ Connettersi a te stesso?
+ Tocca per connettere
+ Sei già nel gruppo %1$s.
+ Questo è il tuo indirizzo SimpleX!
+ Correggere il nome a %s?
+ Eliminare %d messaggi?
+ Connettersi con %1$s?
+ Rimuovi membro
+ Blocca
+ Sbloccare il membro?
+ %d messaggi bloccati
+ Blocca membro
+ Ripetere la richiesta di ingresso?
+ Rimuovere il membro?
+ Elimina e avvisa il contatto
+ Ti stai già connettendo tramite questo link una tantum!
+ Apri gruppo
+ I messaggi da %s verranno mostrati!
+ Errore di invio dell\'invito
+ Hai condiviso un percorso di file non valido. Segnala il problema agli sviluppatori dell\'app.
+ Bloccare il membro?
+ %d eventi del gruppo
+ Nome non valido!
+ Questo è il tuo link per il gruppo %1$s!
+ Sblocca
+ Percorso file non valido
+ Hai già richiesto la connessione tramite questo indirizzo!
+ Mostra console in una nuova finestra
+ Tutti i nuovi messaggi di %s verranno nascosti!
+ bloccato
+ Errore di rinegoziazione crittografia
+ Rinegoziazione crittografia fallita.
\ No newline at end of file
diff --git a/apps/multiplatform/common/src/commonMain/resources/MR/nl/strings.xml b/apps/multiplatform/common/src/commonMain/resources/MR/nl/strings.xml
index 7cbb9bf757..522b22f7ab 100644
--- a/apps/multiplatform/common/src/commonMain/resources/MR/nl/strings.xml
+++ b/apps/multiplatform/common/src/commonMain/resources/MR/nl/strings.xml
@@ -155,9 +155,9 @@
Verbind
verbonden
Verbinden
- Verbinden via contact link\?
- Verbinden via groep link\?
- Verbinden via uitnodiging link\?
+ Verbinden via contact link?
+ Deelnemen aan groep?
+ Verbinden via eenmalige link?
Contact naam
Contact verborgen:
Decodeerfout
@@ -261,7 +261,7 @@
Scan QR-code.]]>
Ontwikkel gereedschap
Apparaatverificatie is uitgeschakeld. SimpleX Vergrendelen uitschakelen.
- Weergavenaam
+ Vul uw naam in:
Apparaatverificatie is niet ingeschakeld. Je kunt SimpleX Vergrendelen inschakelen via Instellingen zodra je apparaatverificatie hebt ingeschakeld.
Directe berichten tussen leden zijn verboden in deze groep.
%d bestand(en) met een totale grootte van %s
@@ -273,7 +273,7 @@
Verdwijnende berichten
Verbinding verbreken
Verbinding verbroken
- Weergavenaam:
+ Profielnaam:
Weergavenaam mag geen spatie bevatten.
%d min
%d maanden
@@ -300,7 +300,7 @@
Fout bij het importeren van de chat database
Database versleutelen\?
Groep niet gevonden!
- Weergave naam groep:
+ Groep naam invoeren:
Dubbele weergavenaam!
Fout bij verzenden van bericht
Fout bij wisselen van profiel!
@@ -340,7 +340,7 @@
Fout bij verwijderen groep link
Groep link
Fout bij wisselen van rol
- Fout bij verwijderen van gebruiker
+ Fout bij verwijderen van lid
Groep
Volledige naam groep:
Groep voorkeuren
@@ -362,7 +362,7 @@
Groep profiel bewerken
Schakel TCP keep-alive in
Versleutelen
- Fout bij het toevoegen van gebruiker(s)
+ Fout bij het toevoegen van leden
Voer de server handmatig in
Fout bij het accepteren van een contactverzoek
Groep uitnodiging verlopen
@@ -482,12 +482,12 @@
\n3. De verbinding is verbroken.
Deel nemen aan groep
Verlaten
- Gebruiker
+ Lid
link voorbeeld afbeelding
- GEBRUIKER
+ LID
BERICHTEN EN BESTANDEN
Openen in mobiele app en tik vervolgens op Verbinden in de app.]]>
- Gebruiker wordt uit de groep verwijderd, dit kan niet ongedaan worden gemaakt!
+ Lid wordt uit de groep verwijderd, dit kan niet ongedaan worden gemaakt!
Fout bij bezorging van bericht
Bericht wordt gemarkeerd voor verwijdering. De ontvanger(s) kunnen dit bericht onthullen.
Netwerk status
@@ -717,7 +717,7 @@
Opslaan en Contact melden
Je huidige profiel
Opslaan en Contacten melden
- Opslaan en Groepsleden melden
+ Opslaan en groepsleden melden
staking
Het berichten- en applicatieplatform dat uw privacy en veiligheid beschermt.
Het profiel wordt alleen gedeeld met uw contacten.
@@ -775,7 +775,7 @@
De rol wordt gewijzigd in \"%s\". Iedereen in de groep wordt op de hoogte gebracht.
Ontvang via
Groep profiel opslaan
- De groep is volledig gedecentraliseerd – het is alleen zichtbaar voor de leden.
+ Volledig gedecentraliseerd – alleen zichtbaar voor leden.
Timeout van TCP-verbinding
Spraak berichten
Spraak berichten zijn verboden in dit gesprek.
@@ -791,10 +791,10 @@
Databasefout herstellen
Onbekende fout
Verkeerd wachtwoord!
- U bent uitgenodigd voor de groep. Word lid om in contact te komen met groepsleden.
- Tik om mee te doen
- Tik om incognito deel te nemen
- Je bent lid geworden van deze groep. Verbinding maken met uitnodigend groepslid.
+ U bent uitgenodigd voor de groep. Word lid om in contact te komen met de groepsleden.
+ Tik om lid te worden
+ Tik om incognito lid te worden
+ Je bent lid geworden van deze groep. Verbinding maken met uitnodigend lid.
Je hebt een groep uitnodiging verzonden
jij bent vertrokken
je bent van adres veranderd
@@ -802,7 +802,7 @@
Sla het uitnodigen van leden over
%d contact(en) geselecteerd
Verwijderen
- Gebruiker verwijderen
+ Lid verwijderen
Rol
Direct bericht sturen
De rol wordt gewijzigd in \"%s\". De gebruiker ontvangt een nieuwe uitnodiging.
@@ -831,7 +831,7 @@
SimpleX
U bent verbonden met de server die wordt gebruikt om berichten van dit contact te ontvangen.
Je profiel wordt verzonden naar het contact van wie je deze link hebt ontvangen.
- U wordt lid van de groep waar deze link naar verwijst en maakt verbinding met de groepsleden.
+ Je maakt verbinding met alle groepsleden.
Uitvoeren bij geopende app
Toon voorbeeld
SimpleX Chat oproepen
@@ -949,7 +949,7 @@
%1$d bericht(en) overgeslagen
gemodereerd
gemodereerd door %s
- Bericht van lid verwijderen\?
+ Bericht van lid verwijderen?
Modereren
Het bericht wordt verwijderd voor alle leden.
Het bericht wordt gemarkeerd als gemodereerd voor alle leden.
@@ -1263,7 +1263,7 @@
Annuleer het wijzigen van het adres
Afbreken
Geen gefilterde gesprekken
- Alleen groepseigenaren kunnen bestanden en media inschakelen.
+ Alleen groep eigenaren kunnen bestanden en media inschakelen.
Bestanden en media zijn verboden in deze groep.
Favoriet
Bestanden en media verboden!
@@ -1404,4 +1404,59 @@
Stuur een direct bericht om verbinding te maken
stuur een direct bericht
direct verbonden
+ Uitbreiden
+ Verbindingsverzoek herhalen?
+ verwijderd contact
+ Fout
+ Groep aanmaken
+ Maak een profiel aan
+ %s en %s
+ Deelnemen aan uw groep?
+ %d berichten gemarkeerd als verwijderd
+ Groep bestaat al!
+ Al bezig met verbinden!
+ De video kan niet worden gedecodeerd. Probeer een andere video of neem contact op met de ontwikkelaars.
+ %s verbonden
+ en %d andere gebeurtenissen
+ Verbinden via link?
+ Al lid van de groep!
+ %s, %s en %d leden
+ %d berichten gemodereerd door %s
+ Verbinding maken met jezelf?
+ Tik om verbinding te maken
+ Juiste naam voor %s?
+ %d berichten verwijderen?
+ Verbinding maken met %1$s?
+ Lid verwijderen
+ Blokkeren
+ %d berichten geblokkeerd
+ Lid blokkeren
+ Deelnameverzoek herhalen?
+ Lid verwijderen?
+ Contact verwijderen en op de hoogte stellen
+ Open groep
+ Berichten van %s worden getoond!
+ Fout bij verzenden van uitnodiging
+ Lid blokkeren?
+ %d groep gebeurtenissen
+ Ongeldige naam!
+ Ongeldig bestandspad
+ Console in nieuw venster weergeven
+ Alle nieuwe berichten van %s worden verborgen!
+ geblokkeerd
+ Je bent al verbonden met %1$s.
+ Je wordt al lid van de groep via deze link.
+ Je bent al lid van de groep %1$s.
+ Dit is uw eigen eenmalige link!
+ Lid deblokkeren
+ Je zit al in groep %1$s.
+ Dit is uw eigen SimpleX adres!
+ Lid deblokkeren?
+ Je maakt al verbinding via deze eenmalige link!
+ Je hebt een ongeldig bestandslocatie gedeeld. Rapporteer het probleem aan de app-ontwikkelaars.
+ Dit is jouw link voor groep %1$s!
+ Deblokkeren
+ U heeft al een verbinding aangevraagd via dit adres!
+ Fout bij heronderhandeling van codering
+ Opnieuw onderhandelen over de codering is mislukt.
\ No newline at end of file
diff --git a/apps/multiplatform/common/src/commonMain/resources/MR/pl/strings.xml b/apps/multiplatform/common/src/commonMain/resources/MR/pl/strings.xml
index 4e14345e94..e61f040a89 100644
--- a/apps/multiplatform/common/src/commonMain/resources/MR/pl/strings.xml
+++ b/apps/multiplatform/common/src/commonMain/resources/MR/pl/strings.xml
@@ -9,9 +9,9 @@
łączenie…
połączenie ustanowione
połączenie %1$d
- Połączyć się przez link kontaktowy\?
- Połącz się przez link grupowy\?
- Połączyć się przez link zapraszający\?
+ Połączyć się przez adres kontaktowy?
+ Dołączyć do grupy?
+ Połączyć się przez link jednorazowy?
usunięty
błąd
nieprawidłowy czat
@@ -367,7 +367,7 @@
Usuń adres
Usunąć adres\?
Usuń obraz
- Wyświetlana nazwa:
+ Nazwa profilu:
Edytuj obraz
Wyjdź bez zapisywania
Pełna nazwa:
@@ -723,7 +723,7 @@
Błąd usuwania członka
DLA KONSOLI
Grupa
- Wyświetlana nazwa grupy:
+ Wprowadź nazwę grupy:
Pełna nazwa grupy:
Nazwa lokalna
CZŁONEK
@@ -741,7 +741,7 @@
SERWERY
Przełącz
Zmień adres odbioru
- Grupa jest w pełni zdecentralizowana – jest widoczna tylko dla członków.
+ W pełni zdecentralizowana – widoczna tylko dla członków.
Rola zostanie zmieniona na \"%s\". Członek otrzyma nowe zaproszenie.
Wiadomość powitalna
Nie można usunąć profilu użytkownika!
@@ -1033,7 +1033,7 @@
usunąłeś %1$s
Twój profil jest przechowywany na Twoim urządzeniu i udostępniany tylko Twoim kontaktom. Serwery SimpleX nie widzą Twojego profilu.
udostępniłeś jednorazowy link incognito
- Dołączysz do grupy, do której odnosi się ten link i połączysz się z jej członkami.
+ Zostaniesz połączony ze wszystkimi członkami grupy.
Twoje serwery SMP
Zostaniesz połączony, gdy Twoje żądanie połączenia zostanie zaakceptowane, proszę czekać lub sprawdzić później!
Błąd ładowania serwerów SMP
@@ -1406,4 +1406,59 @@
Wyślij wiadomość bezpośrednią aby połączyć
wyślij wiadomość bezpośrednią
połącz bezpośrednio
+ usunięto kontakt
+ Utwórz grupę
+ Utwórz profil
+ %d wiadomości oznaczonych do usunięcia
+ Już połączony!
+ i %d innych wydarzeń
+ Połączyć przez link?
+ Już dołączono do grupy!
+ %d wiadomości zmoderowanych przez %s
+ Połączyć się ze sobą?
+ Poprawić nazwę do %s?
+ Usunąć %d wiadomości?
+ Połączyć z %1$s?
+ Zablokuj
+ %d wiadomości zablokowanych
+ Zablokuj członka
+ Usuń i powiadom kontakt
+ Zablokować członka?
+ %d wydarzeń grupy
+ Wszystkie nowe wiadomości z %s będą ukryte!
+ zablokowany
+ Rozszerz
+ Powtórzyć prośbę połączenia?
+ Już jesteś połączony z %1$s.
+ Błąd
+ Już dołączasz do grupy przez ten link.
+ %s i %s
+ Dołączyć do twojej grupy?
+ Już dołączasz do grupy %1$s.
+ To jest twój jednorazowy link!
+ Grupa już istnieje!
+ Wideo nie może zostać zdekodowane, spróbuj inne wideo lub skontaktuj się z deweloperami.
+ %s połączony
+ %s, %s i %d członków
+ Odblokuj członka
+ Dotknij aby połączyć
+ Już jesteś w grupie %1$s.
+ To jest twój własny adres SimpleX!
+ Usuń członka
+ Odblokować członka?
+ Powtórzyć prośbę dołączenia?
+ Usunąć członka?
+ Już jesteś połączony z tym jednorazowym linkiem!
+ Grupa otwarta
+ Wiadomości z %s zostaną wyświetlone!
+ Błąd wysyłania zaproszenia
+ Udostępniłeś nieprawidłową ścieżkę pliku. Zgłoś problem do deweloperów aplikacji.
+ Nieprawidłowa nazwa!
+ To jest twój link zaproszenia do grupy %1$s!
+ Odblokuj
+ Nieprawidłowa ścieżka pliku
+ Już prosiłeś o połączenie na ten adres!
+ Pokaż konsolę w nowym oknie
+ Błąd renegocjacji szyfrowania
+ Renegocjacja szyfrowania nie powiodła się.
\ No newline at end of file
diff --git a/apps/multiplatform/common/src/commonMain/resources/MR/uk/strings.xml b/apps/multiplatform/common/src/commonMain/resources/MR/uk/strings.xml
index fbf62d4f35..dd8d9ac857 100644
--- a/apps/multiplatform/common/src/commonMain/resources/MR/uk/strings.xml
+++ b/apps/multiplatform/common/src/commonMain/resources/MR/uk/strings.xml
@@ -116,11 +116,11 @@
SimpleX
Дзвонити можете як ви, так і ваш контакт.
k
- Підключитися за контактним посиланням\?
- Підключитися за посиланням-запрошенням\?
- Підключитися за груповим посиланням\?
+ Підключитися через контактну адресу?
+ Підключитися за одноразовим посиланням?
+ Приєднатися до групи?
Ваш профіль буде надіслано контакту, від якого ви отримали це посилання.
- Ви приєднаєтеся до групи, на яку посилається це посилання, і з\'єднаєтеся з її учасниками.
+ Ви з\'єднаєтеся з усіма учасниками групи.
Підключіться
підключений
помилка
@@ -265,7 +265,7 @@
Повне ім\'я:
Ви керуєте своїм чатом!
Платформа для обміну повідомленнями та додатків, що захищає вашу конфіденційність і безпеку.
- Відображуване ім\'я
+ Введіть своє ім\'я:
виклик у процесі
починаючи…
Перша платформа без жодних ідентифікаторів користувачів – приватна за дизайном.
@@ -411,7 +411,7 @@
Кілька профілів чату
Основна версія: v%s
Видалити адресу\?
- Відображуване ім\'я:
+ Ім\'я профілю:
чекаємо на підтвердження…
Переосмислення конфіденційності
Люди можуть підключатися до вас лише за посиланнями, якими ви ділитеся.
@@ -488,7 +488,7 @@
Видалити посилання
Перемикач
Помилка, що змінює роль
- Відображувана назва групи:
+ Введіть назву групи:
Повна назва групи:
Помилка збереження профілю групи
Скидання до налаштувань за замовчуванням
@@ -643,7 +643,8 @@
Використовуєте сервери SimpleX Chat\?
Сервери ICE (по одному на лінію)
Помилка збереження серверів ICE
- Для з\'єднання будуть потрібні хости onion.
+ Onion hosts will be required for connection.
+\nPlease note: you will not be able to connect to the servers without .onion address.
Onion хости будуть використовуватися за наявності.
Для з\'єднання будуть потрібні хости onion.
Показати опції розробника
@@ -1101,7 +1102,7 @@
Введіть вітальне повідомлення…
Змінити адресу отримання
Створити секретну групу
- Група повністю децентралізована - її бачать лише учасники.
+ Повністю децентралізована - видима лише для учасників.
Тайм-аут підключення TCP
Більше не показувати
Вторинний
@@ -1372,4 +1373,90 @@
Переузгодьте шифрування\?
Шифрування працює і нова угода про шифрування не потрібна. Це може призвести до помилок з\'єднання!
Учасники групи можуть надсилати файли та медіа.
+ База даних буде зашифрована, а ключова фраза збережена в налаштуваннях.
+ Розгорнути
+ Повторити запит на підключення?
+ Помилка повторного узгодження шифрування
+ видалений контакт
+ Ви вже з\'єднані з %1$s.
+ Відкрити
+ Шифрування збережених файлів і носіїв
+ Помилка
+ Помилка при створенні контакту користувача
+ Ви вже приєдналися до групи за цим посиланням.
+ Створити групу
+ Зверніть увагу: ретранслятори повідомлень і файлів підключаються через проксі SOCKS. Дзвінки та надсилання попередніх переглядів посилань використовують пряме з’єднання.]]>
+ Створити профіль
+ %s та %s
+ Приєднатися до групи?
+ Ви вже приєдналися до групи %1$s.
+ Шифрування локальних файлів
+ Це ваше власне одноразове посилання!
+ %d повідомлень позначено як видалені
+ Новий десктопний застосунок!
+ 6 нових мов інтерфейсу
+ Група вже існує!
+ Застосунок шифрує нові локальні файли (крім відео).
+ Вже під\'єднуємося!
+ Випадкова фраза зберігається у налаштуваннях у вигляді відкритого тексту.
+\nВи можете змінити його пізніше.
+ Надішліть пряме повідомлення, щоб підключитися
+ Відео не може бути декодовано. Будь ласка, спробуйте інше відео або зверніться до розробників.
+ %s підключено
+ ще інших подій - %d
+ Знаходьте та приєднуйтесь до груп
+ Підключитися за посиланням?
+ Пароль для шифрування бази даних буде оновлено і збережено в налаштуваннях.
+ Вже долучаємось до групи!
+ члени %s, %s та %d
+ %d повідомлень модерує %s
+ Видалити парольну фразу з налаштувань?
+ Розблокувати
+ Використовуйте випадкову парольну фразу
+ Підключитися до себе?
+ Зберегти парольну фразу в налаштуваннях
+ Спрощений режим інкогніто
+ Натисніть, щоб підключитися
+ Ключова фраза для налаштування бази даних
+ Ви вже в групі %1$s.
+ Це ваша власна SimpleX адреса!
+ Повторне узгодження шифрування не вдалося.
+ Виправити ім\'я на %s?
+ Видалити %d повідомлень?
+ Підключитися до %1$s?
+ Видалити учасника
+ Встановити пароль до бази даних
+ Заблокувати
+ Розблокувати учасника?
+ %d повідомлень заблоковано
+ Заблокувати учасника
+ Відкрийте теку з базою даних
+ Повторити запит на приєднання?
+ Видалити учасника?
+ Видалити та повідомити контакт
+ Арабська, Болгарська, Фінська, Іврит, Тайська та Українська – завдяки користувачам і Weblate.
+ Ви вже підключаєтеся за цим одноразовим посиланням!
+ Відкрити групу
+ Будуть показані повідомлення від %s!
+ Створіть новий профіль у десктопному застосунку. 💻
+ Помилка надсилання запрошення
+ Після зміни пароля або перезапуску програми він буде збережений у налаштуваннях у вигляді відкритого тексту.
+ Ви надали невірний шлях до файлу. Повідомте про проблему розробникам програми.
+ Заблокувати учасника?
+ %d групових подій
+ Неправильне ім\'я!
+ Увімкніть інкогніто при підключенні.
+ Це ваше посилання для групи %1$s!
+ Розблокувати
+ Неправильний шлях до файлу
+ - підключитися до служби каталогів (БЕТА)!
+\n- квитанції про доставку (до 20 учасників).
+\n- швидше і стабільніше.
+ Пароль зберігається у налаштуваннях у вигляді відкритого тексту.
+ Ви вже надсилали запит на підключення за цією адресою!
+ надіслати пряме повідомлення
+ Показати консоль у новому вікні
+ Всі повідомлення від %s будуть приховані
+ підключений безпосередньо
+ заблоковано
\ No newline at end of file
diff --git a/apps/multiplatform/common/src/commonMain/resources/MR/zh-rCN/strings.xml b/apps/multiplatform/common/src/commonMain/resources/MR/zh-rCN/strings.xml
index 1d266beaf4..a4f990455f 100644
--- a/apps/multiplatform/common/src/commonMain/resources/MR/zh-rCN/strings.xml
+++ b/apps/multiplatform/common/src/commonMain/resources/MR/zh-rCN/strings.xml
@@ -62,9 +62,9 @@
删除消息?
删除消息
连接
- 通过邀请链接进行连接?
- 通过联系人链接进行连接?
- 通过群组链接连接?
+ 通过一次性链接进行连接?
+ 通过联系人地址进行连接?
+ 加群吗?
通过群组链接/二维码连接
总是通过中继连接
允许您的联系人不可撤回地删除已发送消息。
@@ -358,7 +358,7 @@
核心版本: v%s
显示名:
全名:
- 显示名
+ 输入你的名字:
已结束
群组已删除
将为所有成员删除群组——此操作无法撤消!
@@ -410,7 +410,7 @@
保存 ICE 服务器错误
发送消息错误
完整链接
- 群组显示名称:
+ 输入群组名:
群组邀请已过期
将为您删除群组——此操作无法撤消!
群组资料存储在成员的设备上,而不是服务器上。
@@ -735,7 +735,7 @@
%s 未验证
感谢用户——通过 Weblate 做出贡献!
第一个没有任何用户标识符的平台——专为隐私保护设计。
- 该小组是完全分散式的——它只对成员可见。
+ 完全去中心化 - 仅对成员可见。
图像无法解码。 请尝试不同的图像或联系开发者。
主题
此操作无法撤消——所有接收和发送的文件和媒体都将被删除。 低分辨率图片将保留。
@@ -918,7 +918,7 @@
间接(%1$s)
在移动应用程序中打开按钮。]]>
SimpleX
- 您将加入此链接指向的群组并连接到其群组成员。
+ 你将连接到所有群成员。
通过群组链接
通过一次性链接
通过联系地址链接
@@ -1406,4 +1406,59 @@
发送私信来连接
发送私信
已直连
+ 展开
+ 重复连接请求吗?
+ 已删除联系人
+ 你已经在连接到 %1$s。
+ 错误
+ 你已经在通过此链接加入该群。
+ 建群
+ 创建个人资料
+ %s 和 %s
+ 加入你的群吗?
+ 你已经在加入 %1$s 群。
+ 这是你自己的一次性链接!
+ %d 条消息被标记为删除
+ 群已存在!
+ 已经在连接了!
+ 无法解码该视频。请尝试不同视频或联络开发者。
+ %s 已连接
+ 及其他 %d 个事件
+ 通过链接进行连接吗?
+ 已经加入了该群组!
+ %s、 %s 和 %d 名成员
+ %s 审核了 %d 条消息
+ 解封成员
+ 连接到你自己?
+ 轻按连接
+ 你已经在%1$s 群内。
+ 这是你自己的 SimpleX 地址!
+ 更正名称为 %s?
+ 删除 %d 条消息吗?
+ 和 %1$s 连接吗?
+ 删除成员
+ 封禁
+ 解封成员吗?
+ 拦截了 %d 条消息
+ 封禁成员
+ 重复加入请求吗?
+ 删除成员吗?
+ 删除并通知联系人
+ 你已经在通过这个一次性链接进行连接!
+ 打开群
+ 将显示来自 %s 的消息!
+ 发送邀请出错
+ 你分享了无效的文件路径。请将此问题报告给应用开发者。
+ 封禁成员吗?
+ %d 个群事件
+ 无效名称!
+ 这是给你的 %1$s 群链接!
+ 解封
+ 无效的文件路径
+ 你已经请求通过此地址进行连接!
+ 在新窗口中显示控制台
+ 所有来自 %s 的新消息都将被隐藏!
+ 已封禁
+ 加密重协商错误
+ 加密重协商失败了。
\ No newline at end of file
diff --git a/apps/multiplatform/common/src/desktopMain/kotlin/chat/simplex/common/DesktopApp.kt b/apps/multiplatform/common/src/desktopMain/kotlin/chat/simplex/common/DesktopApp.kt
index 6b81209d4c..2931e0e014 100644
--- a/apps/multiplatform/common/src/desktopMain/kotlin/chat/simplex/common/DesktopApp.kt
+++ b/apps/multiplatform/common/src/desktopMain/kotlin/chat/simplex/common/DesktopApp.kt
@@ -29,10 +29,6 @@ import java.io.File
val simplexWindowState = SimplexWindowState()
fun showApp() = application {
- // TODO: remove after update to compose 1.5.0+
- // See: https://github.com/JetBrains/compose-multiplatform/issues/3366#issuecomment-1643799976
- System.setProperty("compose.scrolling.smooth.enabled", "false")
-
// For some reason on Linux actual width will be 10.dp less after specifying it here. If we specify 1366,
// it will show 1356. But after that we can still update it to 1366 by changing window state. Just making it +10 now here
val width = if (desktopPlatform.isLinux()) 1376.dp else 1366.dp
diff --git a/simplex-chat.cabal b/simplex-chat.cabal
index a1d7df9b21..add61dc061 100644
--- a/simplex-chat.cabal
+++ b/simplex-chat.cabal
@@ -120,6 +120,7 @@ library
Simplex.Chat.Migrations.M20231019_indexes
Simplex.Chat.Migrations.M20231030_xgrplinkmem_received
Simplex.Chat.Migrations.M20231107_indexes
+ Simplex.Chat.Migrations.M20231113_group_forward
Simplex.Chat.Mobile
Simplex.Chat.Mobile.File
Simplex.Chat.Mobile.Shared
diff --git a/src/Simplex/Chat.hs b/src/Simplex/Chat.hs
index 6fed6c3db2..58495727af 100644
--- a/src/Simplex/Chat.hs
+++ b/src/Simplex/Chat.hs
@@ -28,6 +28,7 @@ import Data.Bifunctor (bimap, first)
import qualified Data.ByteString.Base64 as B64
import Data.ByteString.Char8 (ByteString)
import qualified Data.ByteString.Char8 as B
+import qualified Data.ByteString.Lazy.Char8 as LB
import Data.Char
import Data.Constraint (Dict (..))
import Data.Either (fromRight, rights)
@@ -140,7 +141,8 @@ defaultChatConfig =
cleanupManagerInterval = 30 * 60, -- 30 minutes
cleanupManagerStepDelay = 3 * 1000000, -- 3 seconds
ciExpirationInterval = 30 * 60 * 1000000, -- 30 minutes
- coreApi = False
+ coreApi = False,
+ highlyAvailable = False
}
_defaultSMPServers :: NonEmpty SMPServerWithAuth
@@ -184,9 +186,9 @@ createChatDatabase filePrefix key confirmMigrations = runExceptT $ do
pure ChatDatabase {chatStore, agentStore}
newChatController :: ChatDatabase -> Maybe User -> ChatConfig -> ChatOpts -> IO ChatController
-newChatController ChatDatabase {chatStore, agentStore} user cfg@ChatConfig {agentConfig = aCfg, defaultServers, inlineFiles, tempDir} ChatOpts {coreOptions = CoreChatOpts {smpServers, xftpServers, networkConfig, logLevel, logConnections, logServerHosts, logFile, tbqSize}, optFilesFolder, showReactions, allowInstantFiles, autoAcceptFileSize} = do
+newChatController ChatDatabase {chatStore, agentStore} user cfg@ChatConfig {agentConfig = aCfg, defaultServers, inlineFiles, tempDir} ChatOpts {coreOptions = CoreChatOpts {smpServers, xftpServers, networkConfig, logLevel, logConnections, logServerHosts, logFile, tbqSize, highlyAvailable}, optFilesFolder, showReactions, allowInstantFiles, autoAcceptFileSize} = do
let inlineFiles' = if allowInstantFiles || autoAcceptFileSize > 0 then inlineFiles else inlineFiles {sendChunks = 0, receiveInstant = False}
- config = cfg {logLevel, showReactions, tbqSize, subscriptionEvents = logConnections, hostEvents = logServerHosts, defaultServers = configServers, inlineFiles = inlineFiles', autoAcceptFileSize}
+ config = cfg {logLevel, showReactions, tbqSize, subscriptionEvents = logConnections, hostEvents = logServerHosts, defaultServers = configServers, inlineFiles = inlineFiles', autoAcceptFileSize, highlyAvailable}
firstTime = dbNew chatStore
currentUser <- newTVarIO user
servers <- agentServers config
@@ -1563,7 +1565,7 @@ processChatCommand = \case
gVar <- asks idsDrg
subMode <- chatReadVar subscriptionMode
(agentConnId, cReq) <- withAgent $ \a -> createConnection a (aUserId user) True SCMInvitation Nothing subMode
- member <- withStore $ \db -> createNewContactMember db gVar user groupId contact memRole agentConnId cReq subMode
+ member <- withStore $ \db -> createNewContactMember db gVar user gInfo contact memRole agentConnId cReq subMode
sendInvitation member cReq
pure $ CRSentGroupInvitation user gInfo contact member
Just member@GroupMember {groupMemberId, memberStatus, memberRole = mRole}
@@ -3220,7 +3222,7 @@ processAgentMessageConn user@User {userId} corrId agentConnId agentMessage = do
MSG meta _msgFlags msgBody -> do
cmdId <- createAckCmd conn
withAckMessage agentConnId cmdId meta $ do
- (_conn', _) <- saveRcvMSG conn (ConnectionId connId) meta msgBody cmdId
+ (_conn', _) <- saveDirectRcvMSG conn meta cmdId msgBody
pure False
SENT msgId ->
sentMsgDeliveryEvent conn msgId
@@ -3251,14 +3253,13 @@ processAgentMessageConn user@User {userId} corrId agentConnId agentMessage = do
MSG msgMeta _msgFlags msgBody -> do
cmdId <- createAckCmd conn
withAckMessage agentConnId cmdId msgMeta $ do
- (conn', msg@RcvMessage {chatMsgEvent = ACME _ event}) <- saveRcvMSG conn (ConnectionId connId) msgMeta msgBody cmdId
+ (conn', msg@RcvMessage {chatMsgEvent = ACME _ event}) <- saveDirectRcvMSG conn msgMeta cmdId msgBody
let ct' = ct {activeConn = Just conn'} :: Contact
assertDirectAllowed user MDRcv ct' $ toCMEventTag event
updateChatLock "directMessage" event
case event of
XMsgNew mc -> newContentMessage ct' mc msg msgMeta
XMsgFileDescr sharedMsgId fileDescr -> messageFileDescription ct' sharedMsgId fileDescr msgMeta
- XMsgFileCancel sharedMsgId -> cancelMessageFile ct' sharedMsgId msgMeta
XMsgUpdate sharedMsgId mContent ttl live -> messageUpdate ct' sharedMsgId mContent msg msgMeta ttl live
XMsgDel sharedMsgId _ -> messageDelete ct' sharedMsgId msg msgMeta
XMsgReact sharedMsgId _ reaction add -> directMsgReaction ct' sharedMsgId reaction add msg msgMeta
@@ -3335,10 +3336,11 @@ processAgentMessageConn user@User {userId} corrId agentConnId agentMessage = do
ci <- saveSndChatItem user (CDDirectSnd ct) msg (CISndMsgContent mc)
toView $ CRNewChatItem user (AChatItem SCTDirect SMDSnd (DirectChat ct) ci)
forM_ groupId_ $ \groupId -> do
+ groupInfo <- withStore $ \db -> getGroupInfo db user groupId
subMode <- chatReadVar subscriptionMode
- gVar <- asks idsDrg
groupConnIds <- createAgentConnectionAsync user CFCreateConnGrpInv True SCMInvitation subMode
- withStore $ \db -> createNewContactMemberAsync db gVar user groupId ct gLinkMemRole groupConnIds (fromJVersionRange peerChatVRange) subMode
+ gVar <- asks idsDrg
+ withStore $ \db -> createNewContactMemberAsync db gVar user groupInfo ct gLinkMemRole groupConnIds (fromJVersionRange peerChatVRange) subMode
_ -> pure ()
Just (gInfo, m@GroupMember {activeConn}) ->
when (maybe False ((== ConnReady) . connStatus) activeConn) $ do
@@ -3508,61 +3510,118 @@ processAgentMessageConn user@User {userId} corrId agentConnId agentMessage = do
withStore' $ \db -> updateIntroStatus db introId GMIntroSent
_ -> do
-- TODO notify member who forwarded introduction - question - where it is stored? There is via_contact but probably there should be via_member in group_members table
+ let memCategory = memberCategory m
withStore' (\db -> getViaGroupContact db user m) >>= \case
Nothing -> do
notifyMemberConnected gInfo m Nothing
let connectedIncognito = memberIncognito membership
- when (memberCategory m == GCPreMember) $ probeMatchingMemberContact m connectedIncognito
+ when (memCategory == GCPreMember) $ probeMatchingMemberContact m connectedIncognito
Just ct@Contact {activeConn} ->
forM_ activeConn $ \Connection {connStatus} ->
when (connStatus == ConnReady) $ do
notifyMemberConnected gInfo m $ Just ct
let connectedIncognito = contactConnIncognito ct || incognitoMembership gInfo
- when (memberCategory m == GCPreMember) $ probeMatchingContactsAndMembers ct connectedIncognito True
+ when (memCategory == GCPreMember) $ probeMatchingContactsAndMembers ct connectedIncognito True
+ sendXGrpMemCon memCategory
+ where
+ sendXGrpMemCon = \case
+ GCPreMember ->
+ forM_ (invitedByGroupMemberId membership) $ \hostId -> do
+ host <- withStore $ \db -> getGroupMember db user groupId hostId
+ forM_ (memberConn host) $ \hostConn ->
+ void $ sendDirectMessage hostConn (XGrpMemCon m.memberId) (GroupId groupId)
+ GCPostMember ->
+ forM_ (invitedByGroupMemberId m) $ \invitingMemberId -> do
+ im <- withStore $ \db -> getGroupMember db user groupId invitingMemberId
+ forM_ (memberConn im) $ \imConn ->
+ void $ sendDirectMessage imConn (XGrpMemCon m.memberId) (GroupId groupId)
+ _ -> messageWarning "sendXGrpMemCon: member category GCPreMember or GCPostMember is expected"
MSG msgMeta _msgFlags msgBody -> do
cmdId <- createAckCmd conn
- withAckMessage agentConnId cmdId msgMeta $ do
- (conn', msg@RcvMessage {chatMsgEvent = ACME _ event}) <- saveRcvMSG conn (GroupId groupId) msgMeta msgBody cmdId
- let m' = m {activeConn = Just conn'} :: GroupMember
- updateChatLock "groupMessage" event
- case event of
- XMsgNew mc -> canSend m' $ newGroupContentMessage gInfo m' mc msg msgMeta
- XMsgFileDescr sharedMsgId fileDescr -> canSend m' $ groupMessageFileDescription gInfo m' sharedMsgId fileDescr msgMeta
- XMsgFileCancel sharedMsgId -> cancelGroupMessageFile gInfo m' sharedMsgId msgMeta
- XMsgUpdate sharedMsgId mContent ttl live -> canSend m' $ groupMessageUpdate gInfo m' sharedMsgId mContent msg msgMeta ttl live
- XMsgDel sharedMsgId memberId -> groupMessageDelete gInfo m' sharedMsgId memberId msg msgMeta
- XMsgReact sharedMsgId (Just memberId) reaction add -> groupMsgReaction gInfo m' sharedMsgId memberId reaction add msg msgMeta
- -- TODO discontinue XFile
- XFile fInv -> processGroupFileInvitation' gInfo m' fInv msg msgMeta
- XFileCancel sharedMsgId -> xFileCancelGroup gInfo m' sharedMsgId msgMeta
- XFileAcptInv sharedMsgId fileConnReq_ fName -> xFileAcptInvGroup gInfo m' sharedMsgId fileConnReq_ fName msgMeta
- -- XInfo p -> xInfoMember gInfo m' p -- TODO use for member profile update
- XGrpLinkMem p -> xGrpLinkMem gInfo m' conn' p
- XGrpMemNew memInfo -> xGrpMemNew gInfo m' memInfo msg msgMeta
- XGrpMemIntro memInfo -> xGrpMemIntro gInfo m' memInfo
- XGrpMemInv memId introInv -> xGrpMemInv gInfo m' memId introInv
- XGrpMemFwd memInfo introInv -> xGrpMemFwd gInfo m' memInfo introInv
- XGrpMemRole memId memRole -> xGrpMemRole gInfo m' memId memRole msg msgMeta
- XGrpMemDel memId -> xGrpMemDel gInfo m' memId msg msgMeta
- XGrpLeave -> xGrpLeave gInfo m' msg msgMeta
- XGrpDel -> xGrpDel gInfo m' msg msgMeta
- XGrpInfo p' -> xGrpInfo gInfo m' p' msg msgMeta
- XGrpDirectInv connReq mContent_ -> canSend m' $ xGrpDirectInv gInfo m' conn' connReq mContent_ msg msgMeta
- XInfoProbe probe -> xInfoProbe (COMGroupMember m') probe
- XInfoProbeCheck probeHash -> xInfoProbeCheck (COMGroupMember m') probeHash
- XInfoProbeOk probe -> xInfoProbeOk (COMGroupMember m') probe
- BFileChunk sharedMsgId chunk -> bFileChunkGroup gInfo sharedMsgId chunk msgMeta
- _ -> messageError $ "unsupported message: " <> T.pack (show event)
- currentMemCount <- withStore' $ \db -> getGroupCurrentMembersCount db user gInfo
- let GroupInfo {chatSettings = ChatSettings {sendRcpts}} = gInfo
- pure $
- fromMaybe (sendRcptsSmallGroups user) sendRcpts
- && hasDeliveryReceipt (toCMEventTag event)
- && currentMemCount <= smallGroupsRcptsMemLimit
+ tryChatError (processChatMessage cmdId) >>= \case
+ Right (ACMsg _ chatMsg, withRcpt) -> do
+ ackMsg agentConnId cmdId msgMeta $ if withRcpt then Just "" else Nothing
+ when (membership.memberRole >= GRAdmin) $ forwardMsg_ chatMsg
+ Left e -> ackMsg agentConnId cmdId msgMeta Nothing >> throwError e
where
- canSend mem a
- | memberRole (mem :: GroupMember) <= GRObserver = messageError "member is not allowed to send messages"
- | otherwise = a
+ processChatMessage :: Int64 -> m (AChatMessage, Bool)
+ processChatMessage cmdId = do
+ msg@(ACMsg _ chatMsg) <- parseAChatMessage conn msgMeta msgBody
+ checkIntegrity chatMsg `catchChatError` \_ -> pure ()
+ (msg,) <$> processEvent cmdId chatMsg
+ brokerTs = metaBrokerTs msgMeta
+ checkIntegrity :: ChatMessage e -> m ()
+ checkIntegrity ChatMessage {chatMsgEvent} = do
+ when checkForEvent $ checkIntegrityCreateItem (CDGroupRcv gInfo m) msgMeta
+ where
+ checkForEvent = case chatMsgEvent of
+ XMsgNew _ -> True
+ XFileCancel _ -> True
+ XFileAcptInv {} -> True
+ XGrpMemNew _ -> True
+ XGrpMemRole {} -> True
+ XGrpMemDel _ -> True
+ XGrpLeave -> True
+ XGrpDel -> True
+ XGrpInfo _ -> True
+ XGrpDirectInv {} -> True
+ _ -> False
+ processEvent :: MsgEncodingI e => CommandId -> ChatMessage e -> m Bool
+ processEvent cmdId chatMsg = do
+ (m', conn', msg@RcvMessage {chatMsgEvent = ACME _ event}) <- saveGroupRcvMsg user groupId m conn msgMeta cmdId msgBody chatMsg
+ updateChatLock "groupMessage" event
+ case event of
+ XMsgNew mc -> memberCanSend m' $ newGroupContentMessage gInfo m' mc msg brokerTs
+ XMsgFileDescr sharedMsgId fileDescr -> memberCanSend m' $ groupMessageFileDescription gInfo m' sharedMsgId fileDescr
+ XMsgUpdate sharedMsgId mContent ttl live -> memberCanSend m' $ groupMessageUpdate gInfo m' sharedMsgId mContent msg brokerTs ttl live
+ XMsgDel sharedMsgId memberId -> groupMessageDelete gInfo m' sharedMsgId memberId msg brokerTs
+ XMsgReact sharedMsgId (Just memberId) reaction add -> groupMsgReaction gInfo m' sharedMsgId memberId reaction add msg brokerTs
+ -- TODO discontinue XFile
+ XFile fInv -> processGroupFileInvitation' gInfo m' fInv msg brokerTs
+ XFileCancel sharedMsgId -> xFileCancelGroup gInfo m' sharedMsgId
+ XFileAcptInv sharedMsgId fileConnReq_ fName -> xFileAcptInvGroup gInfo m' sharedMsgId fileConnReq_ fName
+ XInfo p -> xInfoMember gInfo m' p
+ XGrpLinkMem p -> xGrpLinkMem gInfo m' conn' p
+ XGrpMemNew memInfo -> xGrpMemNew gInfo m' memInfo msg brokerTs
+ XGrpMemIntro memInfo -> xGrpMemIntro gInfo m' memInfo
+ XGrpMemInv memId introInv -> xGrpMemInv gInfo m' memId introInv
+ XGrpMemFwd memInfo introInv -> xGrpMemFwd gInfo m' memInfo introInv
+ XGrpMemRole memId memRole -> xGrpMemRole gInfo m' memId memRole msg brokerTs
+ XGrpMemCon memId -> xGrpMemCon gInfo m' memId
+ XGrpMemDel memId -> xGrpMemDel gInfo m' memId msg brokerTs
+ XGrpLeave -> xGrpLeave gInfo m' msg brokerTs
+ XGrpDel -> xGrpDel gInfo m' msg brokerTs
+ XGrpInfo p' -> xGrpInfo gInfo m' p' msg brokerTs
+ XGrpDirectInv connReq mContent_ -> memberCanSend m' $ xGrpDirectInv gInfo m' conn' connReq mContent_ msg brokerTs
+ XGrpMsgForward memberId msg' msgTs -> xGrpMsgForward gInfo m' memberId msg' msgTs
+ XInfoProbe probe -> xInfoProbe (COMGroupMember m') probe
+ XInfoProbeCheck probeHash -> xInfoProbeCheck (COMGroupMember m') probeHash
+ XInfoProbeOk probe -> xInfoProbeOk (COMGroupMember m') probe
+ BFileChunk sharedMsgId chunk -> bFileChunkGroup gInfo sharedMsgId chunk msgMeta
+ _ -> messageError $ "unsupported message: " <> T.pack (show event)
+ checkSendRcpt event
+ checkSendRcpt :: ChatMsgEvent e -> m Bool
+ checkSendRcpt event = do
+ currentMemCount <- withStore' $ \db -> getGroupCurrentMembersCount db user gInfo
+ let GroupInfo {chatSettings = ChatSettings {sendRcpts}} = gInfo
+ pure $
+ fromMaybe (sendRcptsSmallGroups user) sendRcpts
+ && hasDeliveryReceipt (toCMEventTag event)
+ && currentMemCount <= smallGroupsRcptsMemLimit
+ forwardMsg_ :: MsgEncodingI e => ChatMessage e -> m ()
+ forwardMsg_ chatMsg =
+ forM_ (forwardedGroupMsg chatMsg) $ \chatMsg' -> do
+ ChatConfig {highlyAvailable} <- asks config
+ -- members introduced to this invited member
+ introducedMembers <- if memberCategory m == GCInviteeMember
+ then withStore' $ \db -> getForwardIntroducedMembers db user m highlyAvailable
+ else pure []
+ -- invited members to which this member was introduced
+ invitedMembers <- withStore' $ \db -> getForwardInvitedMembers db user m highlyAvailable
+ let ms = introducedMembers <> invitedMembers
+ msg = XGrpMsgForward m.memberId chatMsg' brokerTs
+ unless (null ms) $
+ void $ sendGroupMessage user gInfo ms msg
RCVD msgMeta msgRcpt ->
withAckMessage' agentConnId conn msgMeta $
groupMsgReceived gInfo m conn msgMeta msgRcpt
@@ -3821,6 +3880,11 @@ processAgentMessageConn user@User {userId} corrId agentConnId agentMessage = do
_ -> toView $ CRReceivedContactRequest user cReq
_ -> pure ()
+ memberCanSend :: GroupMember -> m () -> m ()
+ memberCanSend GroupMember {memberRole} a
+ | memberRole <= GRObserver = messageError "member is not allowed to send messages"
+ | otherwise = a
+
incAuthErrCounter :: ConnectionEntity -> Connection -> AgentErrorType -> m ()
incAuthErrCounter connEntity conn err = do
case err of
@@ -3864,7 +3928,7 @@ processAgentMessageConn user@User {userId} corrId agentConnId agentMessage = do
withAckMessage cId cmdId msgMeta $ action $> False
withAckMessage :: ConnId -> CommandId -> MsgMeta -> m Bool -> m ()
- withAckMessage cId cmdId MsgMeta {recipient = (msgId, _)} action = do
+ withAckMessage cId cmdId msgMeta action = do
-- [async agent commands] command should be asynchronous, continuation is ackMsgDeliveryEvent
-- TODO catching error and sending ACK after an error, particularly if it is a database error, will result in the message not processed (and no notification to the user).
-- Possible solutions are:
@@ -3872,10 +3936,11 @@ processAgentMessageConn user@User {userId} corrId agentConnId agentMessage = do
-- 2) stabilize database
-- 3) show screen of death to the user asking to restart
tryChatError action >>= \case
- Right withRcpt -> ack $ if withRcpt then Just "" else Nothing
- Left e -> ack Nothing >> throwError e
- where
- ack rcpt = withAgent $ \a -> ackMessageAsync a (aCorrId cmdId) cId msgId rcpt
+ Right withRcpt -> ackMsg cId cmdId msgMeta $ if withRcpt then Just "" else Nothing
+ Left e -> ackMsg cId cmdId msgMeta Nothing >> throwError e
+
+ ackMsg :: ConnId -> CommandId -> MsgMeta -> Maybe MsgReceiptInfo -> m ()
+ ackMsg cId cmdId MsgMeta {recipient = (msgId, _)} rcpt = withAgent $ \a -> ackMessageAsync a (aCorrId cmdId) cId msgId rcpt
ackMsgDeliveryEvent :: Connection -> CommandId -> m ()
ackMsgDeliveryEvent Connection {connId} ackCmdId =
@@ -3995,8 +4060,9 @@ processAgentMessageConn user@User {userId} corrId agentConnId agentMessage = do
newChatItem (CIRcvMsgContent content) (snd <$> file_) timed_ live
autoAcceptFile file_
where
+ brokerTs = metaBrokerTs msgMeta
newChatItem ciContent ciFile_ timed_ live = do
- ci <- saveRcvChatItem' user (CDDirectRcv ct) msg sharedMsgId_ msgMeta ciContent ciFile_ timed_ live
+ ci <- saveRcvChatItem' user (CDDirectRcv ct) msg sharedMsgId_ brokerTs ciContent ciFile_ timed_ live
reactions <- maybe (pure []) (\sharedMsgId -> withStore' $ \db -> getDirectCIReactions db ct sharedMsgId) sharedMsgId_
toView $ CRNewChatItem user (AChatItem SCTDirect SMDRcv (DirectChat ct) ci {reactions})
@@ -4011,8 +4077,8 @@ processAgentMessageConn user@User {userId} corrId agentConnId agentMessage = do
fileId <- withStore $ \db -> getFileIdBySharedMsgId db userId contactId sharedMsgId
processFDMessage fileId fileDescr
- groupMessageFileDescription :: GroupInfo -> GroupMember -> SharedMsgId -> FileDescr -> MsgMeta -> m ()
- groupMessageFileDescription GroupInfo {groupId} _m sharedMsgId fileDescr _msgMeta = do
+ groupMessageFileDescription :: GroupInfo -> GroupMember -> SharedMsgId -> FileDescr -> m ()
+ groupMessageFileDescription GroupInfo {groupId} _m sharedMsgId fileDescr = do
fileId <- withStore $ \db -> getGroupFileIdBySharedMsgId db userId groupId sharedMsgId
processFDMessage fileId fileDescr
@@ -4030,17 +4096,6 @@ processAgentMessageConn user@User {userId} corrId agentConnId agentMessage = do
(RFSAccepted _, Just XFTPRcvFile {}) -> receiveViaCompleteFD user fileId rfd cryptoArgs
_ -> pure ()
- cancelMessageFile :: Contact -> SharedMsgId -> MsgMeta -> m ()
- cancelMessageFile ct _sharedMsgId msgMeta = do
- checkIntegrityCreateItem (CDDirectRcv ct) msgMeta
- -- find the original chat item and file
- -- mark file as cancelled, remove description if exists
- pure ()
-
- cancelGroupMessageFile :: GroupInfo -> GroupMember -> SharedMsgId -> MsgMeta -> m ()
- cancelGroupMessageFile _gInfo _m _sharedMsgId _msgMeta = do
- pure ()
-
processFileInvitation :: Maybe FileInvitation -> MsgContent -> (DB.Connection -> FileInvitation -> Maybe InlineFileMode -> Integer -> ExceptT StoreError IO RcvFileTransfer) -> m (Maybe (RcvFileTransfer, CIFile 'MDRcv))
processFileInvitation fInv_ mc createRcvFT = forM fInv_ $ \fInv@FileInvitation {fileName, fileSize} -> do
ChatConfig {fileChunkSize} <- asks config
@@ -4067,13 +4122,13 @@ processAgentMessageConn user@User {userId} corrId agentConnId agentMessage = do
-- received an update from the sender, so that it can be referenced later (e.g. by broadcast delete).
-- Chat item and update message which created it will have different sharedMsgId in this case...
let timed_ = rcvContactCITimed ct ttl
- ci <- saveRcvChatItem' user (CDDirectRcv ct) msg (Just sharedMsgId) msgMeta content Nothing timed_ live
+ ci <- saveRcvChatItem' user (CDDirectRcv ct) msg (Just sharedMsgId) brokerTs content Nothing timed_ live
ci' <- withStore' $ \db -> do
createChatItemVersion db (chatItemId' ci) brokerTs mc
updateDirectChatItem' db user contactId ci content live Nothing
toView $ CRChatItemUpdated user (AChatItem SCTDirect SMDRcv (DirectChat ct) ci')
where
- MsgMeta {broker = (_, brokerTs)} = msgMeta
+ brokerTs = metaBrokerTs msgMeta
content = CIRcvMsgContent mc
live = fromMaybe False live_
updateRcvChatItem = do
@@ -4128,8 +4183,8 @@ processAgentMessageConn user@User {userId} corrId agentConnId agentMessage = do
else pure Nothing
mapM_ toView cr_
- groupMsgReaction :: GroupInfo -> GroupMember -> SharedMsgId -> MemberId -> MsgReaction -> Bool -> RcvMessage -> MsgMeta -> m ()
- groupMsgReaction g@GroupInfo {groupId} m sharedMsgId itemMemberId reaction add RcvMessage {msgId} MsgMeta {broker = (_, brokerTs)} = do
+ groupMsgReaction :: GroupInfo -> GroupMember -> SharedMsgId -> MemberId -> MsgReaction -> Bool -> RcvMessage -> UTCTime -> m ()
+ groupMsgReaction g@GroupInfo {groupId} m sharedMsgId itemMemberId reaction add RcvMessage {msgId} brokerTs = do
when (groupFeatureAllowed SGFReactions g) $ do
rs <- withStore' $ \db -> getGroupReactions db g m itemMemberId sharedMsgId False
when (reactionAllowed add reaction rs) $ do
@@ -4158,8 +4213,8 @@ processAgentMessageConn user@User {userId} corrId agentConnId agentMessage = do
ChatErrorStore (SEChatItemSharedMsgIdNotFound sharedMsgId) -> handle sharedMsgId
e -> throwError e
- newGroupContentMessage :: GroupInfo -> GroupMember -> MsgContainer -> RcvMessage -> MsgMeta -> m ()
- newGroupContentMessage gInfo m@GroupMember {memberId, memberRole} mc msg@RcvMessage {sharedMsgId_} msgMeta
+ newGroupContentMessage :: GroupInfo -> GroupMember -> MsgContainer -> RcvMessage -> UTCTime -> m ()
+ newGroupContentMessage gInfo m@GroupMember {memberId, memberRole} mc msg@RcvMessage {sharedMsgId_} brokerTs
| isVoice content && not (groupFeatureAllowed SGFVoice gInfo) = rejected GFVoice
| not (isVoice content) && isJust fInv_ && not (groupFeatureAllowed SGFFiles gInfo) = rejected GFFiles
| otherwise = do
@@ -4179,38 +4234,37 @@ processAgentMessageConn user@User {userId} corrId agentConnId agentMessage = do
| moderatorRole < GRAdmin || moderatorRole < memberRole =
createItem timed_ live
| groupFeatureAllowed SGFFullDelete gInfo = do
- ci <- saveRcvChatItem' user (CDGroupRcv gInfo m) msg sharedMsgId_ msgMeta CIRcvModerated Nothing timed_ False
+ ci <- saveRcvChatItem' user (CDGroupRcv gInfo m) msg sharedMsgId_ brokerTs CIRcvModerated Nothing timed_ False
ci' <- withStore' $ \db -> updateGroupChatItemModerated db user gInfo ci moderator moderatedAt
toView $ CRNewChatItem user $ AChatItem SCTGroup SMDRcv (GroupChat gInfo) ci'
| otherwise = do
file_ <- processFileInvitation fInv_ content $ \db -> createRcvGroupFileTransfer db userId m
- ci <- saveRcvChatItem' user (CDGroupRcv gInfo m) msg sharedMsgId_ msgMeta (CIRcvMsgContent content) (snd <$> file_) timed_ False
+ ci <- saveRcvChatItem' user (CDGroupRcv gInfo m) msg sharedMsgId_ brokerTs (CIRcvMsgContent content) (snd <$> file_) timed_ False
toView =<< markGroupCIDeleted user gInfo ci createdByMsgId False (Just moderator) moderatedAt
createItem timed_ live = do
file_ <- processFileInvitation fInv_ content $ \db -> createRcvGroupFileTransfer db userId m
newChatItem (CIRcvMsgContent content) (snd <$> file_) timed_ live
when (showMessages $ memberSettings m) $ autoAcceptFile file_
newChatItem ciContent ciFile_ timed_ live = do
- ci <- saveRcvChatItem' user (CDGroupRcv gInfo m) msg sharedMsgId_ msgMeta ciContent ciFile_ timed_ live
+ ci <- saveRcvChatItem' user (CDGroupRcv gInfo m) msg sharedMsgId_ brokerTs ciContent ciFile_ timed_ live
ci' <- blockedMember m ci $ withStore' $ \db -> markGroupChatItemBlocked db user gInfo ci
reactions <- maybe (pure []) (\sharedMsgId -> withStore' $ \db -> getGroupCIReactions db gInfo memberId sharedMsgId) sharedMsgId_
- groupMsgToView gInfo m ci' {reactions} msgMeta
+ groupMsgToView gInfo ci' {reactions}
- groupMessageUpdate :: GroupInfo -> GroupMember -> SharedMsgId -> MsgContent -> RcvMessage -> MsgMeta -> Maybe Int -> Maybe Bool -> m ()
- groupMessageUpdate gInfo@GroupInfo {groupId} m@GroupMember {groupMemberId, memberId} sharedMsgId mc msg@RcvMessage {msgId} msgMeta ttl_ live_ =
+ groupMessageUpdate :: GroupInfo -> GroupMember -> SharedMsgId -> MsgContent -> RcvMessage -> UTCTime -> Maybe Int -> Maybe Bool -> m ()
+ groupMessageUpdate gInfo@GroupInfo {groupId} m@GroupMember {groupMemberId, memberId} sharedMsgId mc msg@RcvMessage {msgId} brokerTs ttl_ live_ =
updateRcvChatItem `catchCINotFound` \_ -> do
-- This patches initial sharedMsgId into chat item when locally deleted chat item
-- received an update from the sender, so that it can be referenced later (e.g. by broadcast delete).
-- Chat item and update message which created it will have different sharedMsgId in this case...
let timed_ = rcvGroupCITimed gInfo ttl_
- ci <- saveRcvChatItem' user (CDGroupRcv gInfo m) msg (Just sharedMsgId) msgMeta content Nothing timed_ live
+ ci <- saveRcvChatItem' user (CDGroupRcv gInfo m) msg (Just sharedMsgId) brokerTs content Nothing timed_ live
ci' <- withStore' $ \db -> do
createChatItemVersion db (chatItemId' ci) brokerTs mc
ci' <- updateGroupChatItem db user groupId ci content live Nothing
blockedMember m ci' $ markGroupChatItemBlocked db user gInfo ci'
toView $ CRChatItemUpdated user (AChatItem SCTGroup SMDRcv (GroupChat gInfo) ci')
where
- MsgMeta {broker = (_, brokerTs)} = msgMeta
content = CIRcvMsgContent mc
live = fromMaybe False live_
updateRcvChatItem = do
@@ -4233,8 +4287,8 @@ processAgentMessageConn user@User {userId} corrId agentConnId agentMessage = do
else messageError "x.msg.update: group member attempted to update a message of another member"
_ -> messageError "x.msg.update: group member attempted invalid message update"
- groupMessageDelete :: GroupInfo -> GroupMember -> SharedMsgId -> Maybe MemberId -> RcvMessage -> MsgMeta -> m ()
- groupMessageDelete gInfo@GroupInfo {groupId, membership} m@GroupMember {memberId, memberRole = senderRole} sharedMsgId sndMemberId_ RcvMessage {msgId} MsgMeta {broker = (_, brokerTs)} = do
+ groupMessageDelete :: GroupInfo -> GroupMember -> SharedMsgId -> Maybe MemberId -> RcvMessage -> UTCTime -> m ()
+ groupMessageDelete gInfo@GroupInfo {groupId, membership} m@GroupMember {memberId, memberRole = senderRole} sharedMsgId sndMemberId_ RcvMessage {msgId} brokerTs = do
let msgMemberId = fromMaybe memberId sndMemberId_
withStore' (\db -> runExceptT $ getGroupMemberCIBySharedMsgId db user groupId msgMemberId sharedMsgId) >>= \case
Right (CChatItem _ ci@ChatItem {chatDir}) -> case chatDir of
@@ -4271,20 +4325,22 @@ processAgentMessageConn user@User {userId} corrId agentConnId agentMessage = do
RcvFileTransfer {fileId, xftpRcvFile} <- withStore $ \db -> createRcvFileTransfer db userId ct fInv inline fileChunkSize
let fileProtocol = if isJust xftpRcvFile then FPXFTP else FPSMP
ciFile = Just $ CIFile {fileId, fileName, fileSize, fileSource = Nothing, fileStatus = CIFSRcvInvitation, fileProtocol}
- ci <- saveRcvChatItem' user (CDDirectRcv ct) msg sharedMsgId_ msgMeta (CIRcvMsgContent $ MCFile "") ciFile Nothing False
+ ci <- saveRcvChatItem' user (CDDirectRcv ct) msg sharedMsgId_ brokerTs (CIRcvMsgContent $ MCFile "") ciFile Nothing False
toView $ CRNewChatItem user (AChatItem SCTDirect SMDRcv (DirectChat ct) ci)
+ where
+ brokerTs = metaBrokerTs msgMeta
-- TODO remove once XFile is discontinued
- processGroupFileInvitation' :: GroupInfo -> GroupMember -> FileInvitation -> RcvMessage -> MsgMeta -> m ()
- processGroupFileInvitation' gInfo m fInv@FileInvitation {fileName, fileSize} msg@RcvMessage {sharedMsgId_} msgMeta = do
+ processGroupFileInvitation' :: GroupInfo -> GroupMember -> FileInvitation -> RcvMessage -> UTCTime -> m ()
+ processGroupFileInvitation' gInfo m fInv@FileInvitation {fileName, fileSize} msg@RcvMessage {sharedMsgId_} brokerTs = do
ChatConfig {fileChunkSize} <- asks config
inline <- receiveInlineMode fInv Nothing fileChunkSize
RcvFileTransfer {fileId, xftpRcvFile} <- withStore $ \db -> createRcvGroupFileTransfer db userId m fInv inline fileChunkSize
let fileProtocol = if isJust xftpRcvFile then FPXFTP else FPSMP
ciFile = Just $ CIFile {fileId, fileName, fileSize, fileSource = Nothing, fileStatus = CIFSRcvInvitation, fileProtocol}
- ci <- saveRcvChatItem' user (CDGroupRcv gInfo m) msg sharedMsgId_ msgMeta (CIRcvMsgContent $ MCFile "") ciFile Nothing False
+ ci <- saveRcvChatItem' user (CDGroupRcv gInfo m) msg sharedMsgId_ brokerTs (CIRcvMsgContent $ MCFile "") ciFile Nothing False
ci' <- blockedMember m ci $ withStore' $ \db -> markGroupChatItemBlocked db user gInfo ci
- groupMsgToView gInfo m ci' msgMeta
+ groupMsgToView gInfo ci'
blockedMember :: Monad m' => GroupMember -> ChatItem c d -> m' (ChatItem c d) -> m' (ChatItem c d)
blockedMember m ci blockedCI
@@ -4391,9 +4447,8 @@ processAgentMessageConn user@User {userId} corrId agentConnId agentMessage = do
_ -> pure ()
receiveFileChunk ft Nothing meta chunk
- xFileCancelGroup :: GroupInfo -> GroupMember -> SharedMsgId -> MsgMeta -> m ()
- xFileCancelGroup g@GroupInfo {groupId} mem@GroupMember {groupMemberId, memberId} sharedMsgId msgMeta = do
- checkIntegrityCreateItem (CDGroupRcv g mem) msgMeta
+ xFileCancelGroup :: GroupInfo -> GroupMember -> SharedMsgId -> m ()
+ xFileCancelGroup GroupInfo {groupId} GroupMember {groupMemberId, memberId} sharedMsgId = do
fileId <- withStore $ \db -> getGroupFileIdBySharedMsgId db userId groupId sharedMsgId
CChatItem msgDir ChatItem {chatDir} <- withStore $ \db -> getGroupChatItemBySharedMsgId db user groupId groupMemberId sharedMsgId
case (msgDir, chatDir) of
@@ -4408,9 +4463,8 @@ processAgentMessageConn user@User {userId} corrId agentConnId agentMessage = do
else messageError "x.file.cancel: group member attempted to cancel file of another member" -- shouldn't happen now that query includes group member id
(SMDSnd, _) -> messageError "x.file.cancel: group member attempted invalid file cancel"
- xFileAcptInvGroup :: GroupInfo -> GroupMember -> SharedMsgId -> Maybe ConnReqInvitation -> String -> MsgMeta -> m ()
- xFileAcptInvGroup g@GroupInfo {groupId} m@GroupMember {activeConn} sharedMsgId fileConnReq_ fName msgMeta = do
- checkIntegrityCreateItem (CDGroupRcv g m) msgMeta
+ xFileAcptInvGroup :: GroupInfo -> GroupMember -> SharedMsgId -> Maybe ConnReqInvitation -> String -> m ()
+ xFileAcptInvGroup GroupInfo {groupId} m@GroupMember {activeConn} sharedMsgId fileConnReq_ fName = do
fileId <- withStore $ \db -> getGroupFileIdBySharedMsgId db userId groupId sharedMsgId
(AChatItem _ _ _ ci) <- withStore $ \db -> getChatItemByFileId db user fileId
assertSMPAcceptNotProhibited ci
@@ -4439,9 +4493,8 @@ processAgentMessageConn user@User {userId} corrId agentConnId agentMessage = do
_ -> messageError "x.file.acpt.inv: member connection is not active"
else messageError "x.file.acpt.inv: fileName is different from expected"
- groupMsgToView :: GroupInfo -> GroupMember -> ChatItem 'CTGroup 'MDRcv -> MsgMeta -> m ()
- groupMsgToView gInfo m ci msgMeta = do
- checkIntegrityCreateItem (CDGroupRcv gInfo m) msgMeta
+ groupMsgToView :: GroupInfo -> ChatItem 'CTGroup 'MDRcv -> m ()
+ groupMsgToView gInfo ci =
toView $ CRNewChatItem user (AChatItem SCTGroup SMDRcv (GroupChat gInfo) ci)
processGroupInvitation :: Contact -> GroupInvitation -> RcvMessage -> MsgMeta -> m ()
@@ -4467,11 +4520,12 @@ processAgentMessageConn user@User {userId} corrId agentConnId agentMessage = do
toView $ CRUserAcceptedGroupSent user gInfo {membership = membership {memberStatus = GSMemAccepted}} (Just ct)
else do
let content = CIRcvGroupInvitation (CIGroupInvitation {groupId, groupMemberId, localDisplayName, groupProfile, status = CIGISPending}) memRole
- ci <- saveRcvChatItem user (CDDirectRcv ct) msg msgMeta content
+ ci <- saveRcvChatItem user (CDDirectRcv ct) msg brokerTs content
withStore' $ \db -> setGroupInvitationChatItemId db user groupId (chatItemId' ci)
toView $ CRNewChatItem user (AChatItem SCTDirect SMDRcv (DirectChat ct) ci)
toView $ CRReceivedGroupInvitation {user, groupInfo = gInfo, contact = ct, fromMemberRole = fromRole, memberRole = memRole}
where
+ brokerTs = metaBrokerTs msgMeta
sameGroupLinkId :: Maybe GroupLinkId -> Maybe GroupLinkId -> Bool
sameGroupLinkId (Just gli) (Just gli') = gli == gli'
sameGroupLinkId _ _ = False
@@ -4495,13 +4549,15 @@ processAgentMessageConn user@User {userId} corrId agentConnId agentMessage = do
forM_ contactConns $ \conn -> withStore' $ \db -> updateConnectionStatus db conn ConnDeleted
activeConn' <- forM (contactConn ct') $ \conn -> pure conn {connStatus = ConnDeleted}
let ct'' = ct' {activeConn = activeConn'} :: Contact
- ci <- saveRcvChatItem user (CDDirectRcv ct'') msg msgMeta (CIRcvDirectEvent RDEContactDeleted)
+ ci <- saveRcvChatItem user (CDDirectRcv ct'') msg brokerTs (CIRcvDirectEvent RDEContactDeleted)
toView $ CRNewChatItem user (AChatItem SCTDirect SMDRcv (DirectChat ct'') ci)
toView $ CRContactDeletedByContact user ct''
else do
contactConns <- withStore' $ \db -> getContactConnections db userId c
deleteAgentConnectionsAsync user $ map aConnId contactConns
withStore' $ \db -> deleteContact db user c
+ where
+ brokerTs = metaBrokerTs msgMeta
processContactProfileUpdate :: Contact -> Profile -> Bool -> m Contact
processContactProfileUpdate c@Contact {profile = p} p' createItems
@@ -4532,9 +4588,8 @@ processAgentMessageConn user@User {userId} corrId agentConnId agentMessage = do
| otherwise -> Nothing
in setPreference_ SCFTimedMessages ctUserTMPref' ctUserPrefs
- -- TODO use for member profile update
- -- xInfoMember :: GroupInfo -> GroupMember -> Profile -> m ()
- -- xInfoMember gInfo m p' = void $ processMemberProfileUpdate gInfo m p'
+ xInfoMember :: GroupInfo -> GroupMember -> Profile -> m ()
+ xInfoMember gInfo m p' = void $ processMemberProfileUpdate gInfo m p'
xGrpLinkMem :: GroupInfo -> GroupMember -> Connection -> Profile -> m ()
xGrpLinkMem gInfo@GroupInfo {membership} m@GroupMember {groupMemberId, memberCategory} Connection {viaGroupLink} p' = do
@@ -4666,9 +4721,10 @@ processAgentMessageConn user@User {userId} corrId agentConnId agentMessage = do
toView $ CRNewChatItem user $ AChatItem SCTDirect SMDRcv (DirectChat ct) ci
else featureRejected CFCalls
where
- saveCallItem status = saveRcvChatItem user (CDDirectRcv ct) msg msgMeta (CIRcvCall status 0)
+ brokerTs = metaBrokerTs msgMeta
+ saveCallItem status = saveRcvChatItem user (CDDirectRcv ct) msg brokerTs (CIRcvCall status 0)
featureRejected f = do
- ci <- saveRcvChatItem' user (CDDirectRcv ct) msg sharedMsgId_ msgMeta (CIRcvChatFeatureRejected f) Nothing Nothing False
+ ci <- saveRcvChatItem' user (CDDirectRcv ct) msg sharedMsgId_ brokerTs (CIRcvChatFeatureRejected f) Nothing Nothing False
toView $ CRNewChatItem user (AChatItem SCTDirect SMDRcv (DirectChat ct) ci)
-- to party initiating call
@@ -4827,21 +4883,21 @@ processAgentMessageConn user@User {userId} corrId agentConnId agentMessage = do
-- TODO show/log error, other events in SMP confirmation
_ -> pure conn'
- xGrpMemNew :: GroupInfo -> GroupMember -> MemberInfo -> RcvMessage -> MsgMeta -> m ()
- xGrpMemNew gInfo m memInfo@(MemberInfo memId memRole _ memberProfile) msg msgMeta = do
+ xGrpMemNew :: GroupInfo -> GroupMember -> MemberInfo -> RcvMessage -> UTCTime -> m ()
+ xGrpMemNew gInfo m memInfo@(MemberInfo memId memRole _ memberProfile) msg brokerTs = do
checkHostRole m memRole
members <- withStore' $ \db -> getGroupMembers db user gInfo
unless (sameMemberId memId $ membership gInfo) $
if isMember memId gInfo members
then messageError "x.grp.mem.new error: member already exists"
else do
- newMember@GroupMember {groupMemberId} <- withStore $ \db -> createNewGroupMember db user gInfo memInfo GCPostMember GSMemAnnounced
- ci <- saveRcvChatItem user (CDGroupRcv gInfo m) msg msgMeta (CIRcvGroupEvent $ RGEMemberAdded groupMemberId memberProfile)
- groupMsgToView gInfo m ci msgMeta
+ newMember@GroupMember {groupMemberId} <- withStore $ \db -> createNewGroupMember db user gInfo m memInfo GCPostMember GSMemAnnounced
+ ci <- saveRcvChatItem user (CDGroupRcv gInfo m) msg brokerTs (CIRcvGroupEvent $ RGEMemberAdded groupMemberId memberProfile)
+ groupMsgToView gInfo ci
toView $ CRJoinedGroupMemberConnecting user gInfo m newMember
xGrpMemIntro :: GroupInfo -> GroupMember -> MemberInfo -> m ()
- xGrpMemIntro gInfo@GroupInfo {chatSettings} m@GroupMember {memberRole, localDisplayName = c} memInfo@(MemberInfo memId _ memberChatVRange _) = do
+ xGrpMemIntro gInfo@GroupInfo {chatSettings} m@GroupMember {memberRole, localDisplayName = c} memInfo@(MemberInfo memId _ memChatVRange _) = do
case memberCategory m of
GCHostMember -> do
members <- withStore' $ \db -> getGroupMembers db user gInfo
@@ -4852,7 +4908,7 @@ processAgentMessageConn user@User {userId} corrId agentConnId agentMessage = do
subMode <- chatReadVar subscriptionMode
-- [async agent commands] commands should be asynchronous, continuation is to send XGrpMemInv - have to remember one has completed and process on second
groupConnIds <- createConn subMode
- directConnIds <- case memberChatVRange of
+ directConnIds <- case memChatVRange of
Nothing -> Just <$> createConn subMode
Just mcvr
| isCompatibleRange (fromChatVRange mcvr) groupNoDirectVRange -> pure Nothing
@@ -4884,7 +4940,7 @@ processAgentMessageConn user@User {userId} corrId agentConnId agentMessage = do
_ -> messageError "x.grp.mem.inv can be only sent by invitee member"
xGrpMemFwd :: GroupInfo -> GroupMember -> MemberInfo -> IntroInvitation -> m ()
- xGrpMemFwd gInfo@GroupInfo {membership, chatSettings} m memInfo@(MemberInfo memId memRole memberChatVRange _) introInv@IntroInvitation {groupConnReq, directConnReq} = do
+ xGrpMemFwd gInfo@GroupInfo {membership, chatSettings} m memInfo@(MemberInfo memId memRole memChatVRange _) introInv@IntroInvitation {groupConnReq, directConnReq} = do
checkHostRole m memRole
members <- withStore' $ \db -> getGroupMembers db user gInfo
toMember <- case find (sameMemberId memId) members of
@@ -4892,7 +4948,7 @@ processAgentMessageConn user@User {userId} corrId agentConnId agentMessage = do
-- the situation when member does not exist is an error
-- member receiving x.grp.mem.fwd should have also received x.grp.mem.new prior to that.
-- For now, this branch compensates for the lack of delayed message delivery.
- Nothing -> withStore $ \db -> createNewGroupMember db user gInfo memInfo GCPostMember GSMemAnnounced
+ Nothing -> withStore $ \db -> createNewGroupMember db user gInfo m memInfo GCPostMember GSMemAnnounced
Just m' -> pure m'
withStore' $ \db -> saveMemberInvitation db toMember introInv
subMode <- chatReadVar subscriptionMode
@@ -4902,11 +4958,11 @@ processAgentMessageConn user@User {userId} corrId agentConnId agentMessage = do
groupConnIds <- joinAgentConnectionAsync user (chatHasNtfs chatSettings) groupConnReq dm subMode
directConnIds <- forM directConnReq $ \dcr -> joinAgentConnectionAsync user True dcr dm subMode
let customUserProfileId = localProfileId <$> incognitoMembershipProfile gInfo
- mcvr = maybe chatInitialVRange fromChatVRange memberChatVRange
+ mcvr = maybe chatInitialVRange fromChatVRange memChatVRange
withStore' $ \db -> createIntroToMemberContact db user m toMember mcvr groupConnIds directConnIds customUserProfileId subMode
- xGrpMemRole :: GroupInfo -> GroupMember -> MemberId -> GroupMemberRole -> RcvMessage -> MsgMeta -> m ()
- xGrpMemRole gInfo@GroupInfo {membership} m@GroupMember {memberRole = senderRole} memId memRole msg msgMeta
+ xGrpMemRole :: GroupInfo -> GroupMember -> MemberId -> GroupMemberRole -> RcvMessage -> UTCTime -> m ()
+ xGrpMemRole gInfo@GroupInfo {membership} m@GroupMember {memberRole = senderRole} memId memRole msg brokerTs
| memberId (membership :: GroupMember) == memId =
let gInfo' = gInfo {membership = membership {memberRole = memRole}}
in changeMemberRole gInfo' membership $ RGEUserRole memRole
@@ -4920,16 +4976,54 @@ processAgentMessageConn user@User {userId} corrId agentConnId agentMessage = do
| senderRole < GRAdmin || senderRole < fromRole = messageError "x.grp.mem.role with insufficient member permissions"
| otherwise = do
withStore' $ \db -> updateGroupMemberRole db user member memRole
- ci <- saveRcvChatItem user (CDGroupRcv gInfo m) msg msgMeta (CIRcvGroupEvent gEvent)
- groupMsgToView gInfo m ci msgMeta
+ ci <- saveRcvChatItem user (CDGroupRcv gInfo m) msg brokerTs (CIRcvGroupEvent gEvent)
+ groupMsgToView gInfo ci
toView CRMemberRole {user, groupInfo = gInfo', byMember = m, member = member {memberRole = memRole}, fromRole, toRole = memRole}
checkHostRole :: GroupMember -> GroupMemberRole -> m ()
checkHostRole GroupMember {memberRole, localDisplayName} memRole =
when (memberRole < GRAdmin || memberRole < memRole) $ throwChatError (CEGroupContactRole localDisplayName)
- xGrpMemDel :: GroupInfo -> GroupMember -> MemberId -> RcvMessage -> MsgMeta -> m ()
- xGrpMemDel gInfo@GroupInfo {membership} m@GroupMember {memberRole = senderRole} memId msg msgMeta = do
+ xGrpMemCon :: GroupInfo -> GroupMember -> MemberId -> m ()
+ xGrpMemCon gInfo sendingMember memId = do
+ refMember <- withStore $ \db -> getGroupMemberByMemberId db user gInfo memId
+ case (memberCategory sendingMember, memberCategory refMember) of
+ (GCInviteeMember, GCInviteeMember) ->
+ withStore' (\db -> runExceptT $ getIntroduction db refMember sendingMember) >>= \case
+ Right intro -> inviteeXGrpMemCon intro
+ Left _ -> withStore' (\db -> runExceptT $ getIntroduction db sendingMember refMember) >>= \case
+ Right intro -> forwardMemberXGrpMemCon intro
+ Left _ -> messageWarning "x.grp.mem.con: no introduction"
+ (GCInviteeMember, _) ->
+ withStore' (\db -> runExceptT $ getIntroduction db refMember sendingMember) >>= \case
+ Right intro -> inviteeXGrpMemCon intro
+ Left _ -> messageWarning "x.grp.mem.con: no introduction"
+ (_, GCInviteeMember) ->
+ withStore' (\db -> runExceptT $ getIntroduction db sendingMember refMember) >>= \case
+ Right intro -> forwardMemberXGrpMemCon intro
+ Left _ -> messageWarning "x.grp.mem.con: no introductiosupportn"
+ -- Note: we can allow XGrpMemCon to all member categories if we decide to support broader group forwarding,
+ -- deduplication (see saveGroupRcvMsg, saveGroupFwdRcvMsg) already supports sending XGrpMemCon
+ -- to any forwarding member, not only host/inviting member;
+ -- database would track all members connections then
+ -- (currently it's done via group_member_intros for introduced connections only)
+ _ ->
+ messageWarning "x.grp.mem.con: neither member is invitee"
+ where
+ inviteeXGrpMemCon :: GroupMemberIntro -> m ()
+ inviteeXGrpMemCon GroupMemberIntro {introId, introStatus}
+ | introStatus == GMIntroReConnected = updateStatus introId GMIntroConnected
+ | introStatus `elem` [GMIntroToConnected, GMIntroConnected] = pure ()
+ | otherwise = updateStatus introId GMIntroToConnected
+ forwardMemberXGrpMemCon :: GroupMemberIntro -> m ()
+ forwardMemberXGrpMemCon GroupMemberIntro {introId, introStatus}
+ | introStatus == GMIntroToConnected = updateStatus introId GMIntroConnected
+ | introStatus `elem` [GMIntroReConnected, GMIntroConnected] = pure ()
+ | otherwise = updateStatus introId GMIntroReConnected
+ updateStatus introId status = withStore' $ \db -> updateIntroStatus db introId status
+
+ xGrpMemDel :: GroupInfo -> GroupMember -> MemberId -> RcvMessage -> UTCTime -> m ()
+ xGrpMemDel gInfo@GroupInfo {membership} m@GroupMember {memberRole = senderRole} memId msg brokerTs = do
members <- withStore' $ \db -> getGroupMembers db user gInfo
if memberId (membership :: GroupMember) == memId
then checkRole membership $ do
@@ -4955,23 +5049,20 @@ processAgentMessageConn user@User {userId} corrId agentConnId agentMessage = do
messageError "x.grp.mem.del with insufficient member permissions"
| otherwise = a
deleteMemberItem gEvent = do
- ci <- saveRcvChatItem user (CDGroupRcv gInfo m) msg msgMeta (CIRcvGroupEvent gEvent)
- groupMsgToView gInfo m ci msgMeta
+ ci <- saveRcvChatItem user (CDGroupRcv gInfo m) msg brokerTs (CIRcvGroupEvent gEvent)
+ groupMsgToView gInfo ci
- sameMemberId :: MemberId -> GroupMember -> Bool
- sameMemberId memId GroupMember {memberId} = memId == memberId
-
- xGrpLeave :: GroupInfo -> GroupMember -> RcvMessage -> MsgMeta -> m ()
- xGrpLeave gInfo m msg msgMeta = do
+ xGrpLeave :: GroupInfo -> GroupMember -> RcvMessage -> UTCTime -> m ()
+ xGrpLeave gInfo m msg brokerTs = do
deleteMemberConnection user m
-- member record is not deleted to allow creation of "member left" chat item
withStore' $ \db -> updateGroupMemberStatus db userId m GSMemLeft
- ci <- saveRcvChatItem user (CDGroupRcv gInfo m) msg msgMeta (CIRcvGroupEvent RGEMemberLeft)
- groupMsgToView gInfo m ci msgMeta
+ ci <- saveRcvChatItem user (CDGroupRcv gInfo m) msg brokerTs (CIRcvGroupEvent RGEMemberLeft)
+ groupMsgToView gInfo ci
toView $ CRLeftMember user gInfo m {memberStatus = GSMemLeft}
- xGrpDel :: GroupInfo -> GroupMember -> RcvMessage -> MsgMeta -> m ()
- xGrpDel gInfo@GroupInfo {membership} m@GroupMember {memberRole} msg msgMeta = do
+ xGrpDel :: GroupInfo -> GroupMember -> RcvMessage -> UTCTime -> m ()
+ xGrpDel gInfo@GroupInfo {membership} m@GroupMember {memberRole} msg brokerTs = do
when (memberRole /= GROwner) $ throwChatError $ CEGroupUserRole gInfo GROwner
ms <- withStore' $ \db -> do
members <- getGroupMembers db user gInfo
@@ -4979,24 +5070,24 @@ processAgentMessageConn user@User {userId} corrId agentConnId agentMessage = do
pure members
-- member records are not deleted to keep history
deleteMembersConnections user ms
- ci <- saveRcvChatItem user (CDGroupRcv gInfo m) msg msgMeta (CIRcvGroupEvent RGEGroupDeleted)
- groupMsgToView gInfo m ci msgMeta
+ ci <- saveRcvChatItem user (CDGroupRcv gInfo m) msg brokerTs (CIRcvGroupEvent RGEGroupDeleted)
+ groupMsgToView gInfo ci
toView $ CRGroupDeleted user gInfo {membership = membership {memberStatus = GSMemGroupDeleted}} m
- xGrpInfo :: GroupInfo -> GroupMember -> GroupProfile -> RcvMessage -> MsgMeta -> m ()
- xGrpInfo g@GroupInfo {groupProfile = p} m@GroupMember {memberRole} p' msg msgMeta
+ xGrpInfo :: GroupInfo -> GroupMember -> GroupProfile -> RcvMessage -> UTCTime -> m ()
+ xGrpInfo g@GroupInfo {groupProfile = p} m@GroupMember {memberRole} p' msg brokerTs
| memberRole < GROwner = messageError "x.grp.info with insufficient member permissions"
| otherwise = unless (p == p') $ do
g' <- withStore $ \db -> updateGroupProfile db user g p'
toView $ CRGroupUpdated user g g' (Just m)
let cd = CDGroupRcv g' m
unless (sameGroupProfileInfo p p') $ do
- ci <- saveRcvChatItem user cd msg msgMeta (CIRcvGroupEvent $ RGEGroupUpdated p')
- groupMsgToView g' m ci msgMeta
+ ci <- saveRcvChatItem user cd msg brokerTs (CIRcvGroupEvent $ RGEGroupUpdated p')
+ groupMsgToView g' ci
createGroupFeatureChangedItems user cd CIRcvGroupFeature g g'
- xGrpDirectInv :: GroupInfo -> GroupMember -> Connection -> ConnReqInvitation -> Maybe MsgContent -> RcvMessage -> MsgMeta -> m ()
- xGrpDirectInv g m mConn connReq mContent_ msg msgMeta = do
+ xGrpDirectInv :: GroupInfo -> GroupMember -> Connection -> ConnReqInvitation -> Maybe MsgContent -> RcvMessage -> UTCTime -> m ()
+ xGrpDirectInv g m mConn connReq mContent_ msg brokerTs = do
unless (groupFeatureAllowed SGFDirectMessages g) $ messageError "x.grp.direct.inv: direct messages not allowed"
let GroupMember {memberContactId} = m
subMode <- chatReadVar subscriptionMode
@@ -5032,11 +5123,10 @@ processAgentMessageConn user@User {userId} corrId agentConnId agentMessage = do
dm <- directMessage $ XInfo p
joinAgentConnectionAsync user True connReq dm subMode
createItems mCt' m' = do
- checkIntegrityCreateItem (CDGroupRcv g m') msgMeta
createInternalChatItem user (CDGroupRcv g m') (CIRcvGroupEvent RGEMemberCreatedContact) Nothing
toView $ CRNewMemberContactReceivedInv user mCt' g m'
forM_ mContent_ $ \mc -> do
- ci <- saveRcvChatItem user (CDDirectRcv mCt') msg msgMeta (CIRcvMsgContent mc)
+ ci <- saveRcvChatItem user (CDDirectRcv mCt') msg brokerTs (CIRcvMsgContent mc)
toView $ CRNewChatItem user (AChatItem SCTDirect SMDRcv (DirectChat mCt') ci)
securityCodeChanged :: Contact -> m ()
@@ -5044,6 +5134,33 @@ processAgentMessageConn user@User {userId} corrId agentConnId agentMessage = do
toView $ CRContactVerificationReset user ct
createInternalChatItem user (CDDirectRcv ct) (CIRcvConnEvent RCEVerificationCodeReset) Nothing
+ xGrpMsgForward :: GroupInfo -> GroupMember -> MemberId -> ChatMessage 'Json -> UTCTime -> m ()
+ xGrpMsgForward gInfo@GroupInfo {groupId} m memberId msg msgTs = do
+ when (m.memberRole < GRAdmin) $ throwChatError (CEGroupContactRole m.localDisplayName)
+ author <- withStore $ \db -> getGroupMemberByMemberId db user gInfo memberId
+ processForwardedMsg author msg
+ where
+ -- Note: forwarded group events (see forwardedGroupMsg) should include msgId to be deduplicated
+ processForwardedMsg :: GroupMember -> ChatMessage 'Json -> m ()
+ processForwardedMsg author chatMsg = do
+ let body = LB.toStrict $ J.encode msg
+ rcvMsg@RcvMessage {chatMsgEvent = ACME _ event} <- saveGroupFwdRcvMsg user groupId m author body chatMsg
+ case event of
+ XMsgNew mc -> memberCanSend author $ newGroupContentMessage gInfo author mc rcvMsg msgTs
+ XMsgFileDescr sharedMsgId fileDescr -> memberCanSend author $ groupMessageFileDescription gInfo author sharedMsgId fileDescr
+ XMsgUpdate sharedMsgId mContent ttl live -> memberCanSend author $ groupMessageUpdate gInfo author sharedMsgId mContent rcvMsg msgTs ttl live
+ XMsgDel sharedMsgId memId -> groupMessageDelete gInfo author sharedMsgId memId rcvMsg msgTs
+ XMsgReact sharedMsgId (Just memId) reaction add -> groupMsgReaction gInfo author sharedMsgId memId reaction add rcvMsg msgTs
+ XFileCancel sharedMsgId -> xFileCancelGroup gInfo author sharedMsgId
+ XInfo p -> xInfoMember gInfo author p
+ XGrpMemNew memInfo -> xGrpMemNew gInfo author memInfo rcvMsg msgTs
+ XGrpMemRole memId memRole -> xGrpMemRole gInfo author memId memRole rcvMsg msgTs
+ XGrpMemDel memId -> xGrpMemDel gInfo author memId rcvMsg msgTs
+ XGrpLeave -> xGrpLeave gInfo author rcvMsg msgTs
+ XGrpDel -> xGrpDel gInfo author rcvMsg msgTs
+ XGrpInfo p' -> xGrpInfo gInfo author p' rcvMsg msgTs
+ _ -> messageError $ "x.grp.msg.forward: unsupported forwarded event " <> T.pack (show $ toCMEventTag event)
+
directMsgReceived :: Contact -> Connection -> MsgMeta -> NonEmpty MsgReceipt -> m ()
directMsgReceived ct conn@Connection {connId} msgMeta msgRcpts = do
checkIntegrityCreateItem (CDDirectRcv ct) msgMeta
@@ -5092,6 +5209,12 @@ processAgentMessageConn user@User {userId} corrId agentConnId agentMessage = do
toView $ CRChatItemStatusUpdated user (AChatItem SCTGroup SMDSnd (GroupChat gInfo) chatItem)
_ -> pure ()
+metaBrokerTs :: MsgMeta -> UTCTime
+metaBrokerTs MsgMeta {broker = (_, brokerTs)} = brokerTs
+
+sameMemberId :: MemberId -> GroupMember -> Bool
+sameMemberId memId GroupMember {memberId} = memId == memberId
+
updatePeerChatVRange :: ChatMonad m => Connection -> VersionRange -> m Connection
updatePeerChatVRange conn@Connection {connId, peerChatVRange} msgChatVRange = do
let jMsgChatVRange = JVersionRange msgChatVRange
@@ -5101,6 +5224,18 @@ updatePeerChatVRange conn@Connection {connId, peerChatVRange} msgChatVRange = do
pure conn {peerChatVRange = jMsgChatVRange}
else pure conn
+updateMemberChatVRange :: ChatMonad m => GroupMember -> Connection -> VersionRange -> m (GroupMember, Connection)
+updateMemberChatVRange mem@GroupMember {groupMemberId} conn@Connection {connId, peerChatVRange} msgChatVRange = do
+ let jMsgChatVRange = JVersionRange msgChatVRange
+ if jMsgChatVRange /= peerChatVRange
+ then do
+ withStore' $ \db -> do
+ setPeerChatVRange db connId msgChatVRange
+ setMemberChatVRange db groupMemberId msgChatVRange
+ let conn' = conn {peerChatVRange = jMsgChatVRange}
+ pure (mem {memberChatVRange = jMsgChatVRange, activeConn = Just conn'}, conn')
+ else pure (mem, conn)
+
parseFileDescription :: (ChatMonad m, FilePartyI p) => Text -> m (ValidFileDescription p)
parseFileDescription =
liftEither . first (ChatError . CEInvalidFileDescription) . (strDecode . encodeUtf8)
@@ -5349,18 +5484,36 @@ sendGroupMessage' user members chatMsgEvent groupId introId_ postDeliver = do
where
messageMember :: GroupMember -> SndMessage -> m (Maybe GroupMember)
messageMember m@GroupMember {groupMemberId} SndMessage {msgId, msgBody} = case memberConn m of
- Nothing -> do
- withStore' $ \db -> createPendingGroupMessage db groupMemberId msgId introId_
- pure $ Just m
+ Nothing -> pendingOrForwarded
Just conn@Connection {connStatus}
| connDisabled conn || connStatus == ConnDeleted -> pure Nothing
| connStatus == ConnSndReady || connStatus == ConnReady -> do
let tag = toCMEventTag chatMsgEvent
deliverMessage conn tag msgBody msgId >> postDeliver
pure $ Just m
- | otherwise -> do
- withStore' $ \db -> createPendingGroupMessage db groupMemberId msgId introId_
- pure $ Just m
+ | otherwise -> pendingOrForwarded
+ where
+ pendingOrForwarded
+ | forwardSupported && isForwardedGroupMsg chatMsgEvent = pure Nothing
+ | isXGrpMsgForward chatMsgEvent = pure Nothing
+ | otherwise = do
+ withStore' $ \db -> createPendingGroupMessage db groupMemberId msgId introId_
+ pure $ Just m
+ forwardSupported = do
+ let mcvr = memberChatVRange' m
+ isCompatibleRange mcvr groupForwardVRange && invitingMemberSupportsForward
+ invitingMemberSupportsForward = case m.invitedByGroupMemberId of
+ Just invMemberId ->
+ -- can be optimized for large groups by replacing [GroupMember] with Map GroupMemberId GroupMember
+ case find (\m' -> groupMemberId' m' == invMemberId) members of
+ Just invitingMember -> do
+ let mcvr = memberChatVRange' invitingMember
+ isCompatibleRange mcvr groupForwardVRange
+ Nothing -> False
+ Nothing -> False
+ isXGrpMsgForward ev = case ev of
+ XGrpMsgForward {} -> True
+ _ -> False
sendPendingGroupMessages :: ChatMonad m => User -> GroupMember -> Connection -> m ()
sendPendingGroupMessages user GroupMember {groupMemberId, localDisplayName} conn = do
@@ -5378,18 +5531,49 @@ sendPendingGroupMessages user GroupMember {groupMemberId, localDisplayName} conn
_ -> throwChatError $ CEGroupMemberIntroNotFound localDisplayName
_ -> pure ()
-saveRcvMSG :: ChatMonad m => Connection -> ConnOrGroupId -> MsgMeta -> MsgBody -> CommandId -> m (Connection, RcvMessage)
-saveRcvMSG conn@Connection {connId} connOrGroupId agentMsgMeta msgBody agentAckCmdId = do
+saveDirectRcvMSG :: ChatMonad m => Connection -> MsgMeta -> CommandId -> MsgBody -> m (Connection, RcvMessage)
+saveDirectRcvMSG conn@Connection {connId} agentMsgMeta agentAckCmdId msgBody = do
ACMsg _ ChatMessage {chatVRange, msgId = sharedMsgId_, chatMsgEvent} <- parseAChatMessage conn agentMsgMeta msgBody
conn' <- updatePeerChatVRange conn chatVRange
let agentMsgId = fst $ recipient agentMsgMeta
newMsg = NewMessage {chatMsgEvent, msgBody}
rcvMsgDelivery = RcvMsgDelivery {connId, agentMsgId, agentMsgMeta, agentAckCmdId}
- msg <- withStoreCtx'
- (Just $ "createNewMessageAndRcvMsgDelivery, rcvMsgDelivery: " <> show rcvMsgDelivery <> ", sharedMsgId_: " <> show sharedMsgId_ <> ", msgDeliveryStatus: MDSRcvAgent")
- $ \db -> createNewMessageAndRcvMsgDelivery db connOrGroupId newMsg sharedMsgId_ rcvMsgDelivery
+ msg <- withStore $ \db -> createNewMessageAndRcvMsgDelivery db (ConnectionId connId) newMsg sharedMsgId_ rcvMsgDelivery Nothing
pure (conn', msg)
+saveGroupRcvMsg :: (MsgEncodingI e, ChatMonad m) => User -> GroupId -> GroupMember -> Connection -> MsgMeta -> CommandId -> MsgBody -> ChatMessage e -> m (GroupMember, Connection, RcvMessage)
+saveGroupRcvMsg user groupId authorMember conn@Connection {connId} agentMsgMeta agentAckCmdId msgBody ChatMessage {chatVRange, msgId = sharedMsgId_, chatMsgEvent} = do
+ (am', conn') <- updateMemberChatVRange authorMember conn chatVRange
+ let agentMsgId = fst $ recipient agentMsgMeta
+ newMsg = NewMessage {chatMsgEvent, msgBody}
+ rcvMsgDelivery = RcvMsgDelivery {connId, agentMsgId, agentMsgMeta, agentAckCmdId}
+ amId = Just am'.groupMemberId
+ msg <- withStore (\db -> createNewMessageAndRcvMsgDelivery db (GroupId groupId) newMsg sharedMsgId_ rcvMsgDelivery amId)
+ `catchChatError` \e -> case e of
+ ChatErrorStore (SEDuplicateGroupMessage _ _ _ (Just forwardedByGroupMemberId)) -> do
+ fm <- withStore $ \db -> getGroupMember db user groupId forwardedByGroupMemberId
+ forM_ (memberConn fm) $ \fmConn ->
+ void $ sendDirectMessage fmConn (XGrpMemCon am'.memberId) (GroupId groupId)
+ throwError e
+ _ -> throwError e
+ pure (am', conn', msg)
+
+saveGroupFwdRcvMsg :: (MsgEncodingI e, ChatMonad m) => User -> GroupId -> GroupMember -> GroupMember -> MsgBody -> ChatMessage e -> m RcvMessage
+saveGroupFwdRcvMsg user groupId forwardingMember refAuthorMember msgBody ChatMessage {msgId = sharedMsgId_, chatMsgEvent} = do
+ let newMsg = NewMessage {chatMsgEvent, msgBody}
+ fwdMemberId = Just $ groupMemberId' forwardingMember
+ refAuthorId = Just $ groupMemberId' refAuthorMember
+ withStore (\db -> createNewRcvMessage db (GroupId groupId) newMsg sharedMsgId_ refAuthorId fwdMemberId)
+ `catchChatError` \e -> case e of
+ ChatErrorStore (SEDuplicateGroupMessage _ _ (Just authorGroupMemberId) Nothing) -> do
+ am <- withStore $ \db -> getGroupMember db user groupId authorGroupMemberId
+ if sameMemberId refAuthorMember.memberId am
+ then forM_ (memberConn forwardingMember) $ \fmConn ->
+ void $ sendDirectMessage fmConn (XGrpMemCon am.memberId) (GroupId groupId)
+ else toView $ CRMessageError user "error" "saveGroupFwdRcvMsg: referenced author member id doesn't match message member id"
+ throwError e
+ _ -> throwError e
+
saveSndChatItem :: ChatMonad m => User -> ChatDirection c 'MDSnd -> SndMessage -> CIContent 'MDSnd -> m (ChatItem c 'MDSnd)
saveSndChatItem user cd msg content = saveSndChatItem' user cd msg content Nothing Nothing Nothing False
@@ -5401,27 +5585,27 @@ saveSndChatItem' user cd msg@SndMessage {sharedMsgId} content ciFile quotedItem
ciId <- createNewSndChatItem db user cd msg content quotedItem itemTimed live createdAt
forM_ ciFile $ \CIFile {fileId} -> updateFileTransferChatItemId db fileId ciId createdAt
pure ciId
- liftIO $ mkChatItem cd ciId content ciFile quotedItem (Just sharedMsgId) itemTimed live createdAt createdAt
+ liftIO $ mkChatItem cd ciId content ciFile quotedItem (Just sharedMsgId) itemTimed live createdAt Nothing createdAt
-saveRcvChatItem :: ChatMonad m => User -> ChatDirection c 'MDRcv -> RcvMessage -> MsgMeta -> CIContent 'MDRcv -> m (ChatItem c 'MDRcv)
-saveRcvChatItem user cd msg@RcvMessage {sharedMsgId_} msgMeta content =
- saveRcvChatItem' user cd msg sharedMsgId_ msgMeta content Nothing Nothing False
+saveRcvChatItem :: ChatMonad m => User -> ChatDirection c 'MDRcv -> RcvMessage -> UTCTime -> CIContent 'MDRcv -> m (ChatItem c 'MDRcv)
+saveRcvChatItem user cd msg@RcvMessage {sharedMsgId_} brokerTs content =
+ saveRcvChatItem' user cd msg sharedMsgId_ brokerTs content Nothing Nothing False
-saveRcvChatItem' :: ChatMonad m => User -> ChatDirection c 'MDRcv -> RcvMessage -> Maybe SharedMsgId -> MsgMeta -> CIContent 'MDRcv -> Maybe (CIFile 'MDRcv) -> Maybe CITimed -> Bool -> m (ChatItem c 'MDRcv)
-saveRcvChatItem' user cd msg sharedMsgId_ MsgMeta {broker = (_, brokerTs)} content ciFile itemTimed live = do
+saveRcvChatItem' :: ChatMonad m => User -> ChatDirection c 'MDRcv -> RcvMessage -> Maybe SharedMsgId -> UTCTime -> CIContent 'MDRcv -> Maybe (CIFile 'MDRcv) -> Maybe CITimed -> Bool -> m (ChatItem c 'MDRcv)
+saveRcvChatItem' user cd msg sharedMsgId_ brokerTs content ciFile itemTimed live = do
createdAt <- liftIO getCurrentTime
(ciId, quotedItem) <- withStore' $ \db -> do
when (ciRequiresAttention content) $ updateChatTs db user cd createdAt
(ciId, quotedItem) <- createNewRcvChatItem db user cd msg sharedMsgId_ content itemTimed live brokerTs createdAt
forM_ ciFile $ \CIFile {fileId} -> updateFileTransferChatItemId db fileId ciId createdAt
pure (ciId, quotedItem)
- liftIO $ mkChatItem cd ciId content ciFile quotedItem sharedMsgId_ itemTimed live brokerTs createdAt
+ liftIO $ mkChatItem cd ciId content ciFile quotedItem sharedMsgId_ itemTimed live brokerTs msg.forwardedByGroupMemberId createdAt
-mkChatItem :: forall c d. MsgDirectionI d => ChatDirection c d -> ChatItemId -> CIContent d -> Maybe (CIFile d) -> Maybe (CIQuote c) -> Maybe SharedMsgId -> Maybe CITimed -> Bool -> ChatItemTs -> UTCTime -> IO (ChatItem c d)
-mkChatItem cd ciId content file quotedItem sharedMsgId itemTimed live itemTs currentTs = do
+mkChatItem :: forall c d. MsgDirectionI d => ChatDirection c d -> ChatItemId -> CIContent d -> Maybe (CIFile d) -> Maybe (CIQuote c) -> Maybe SharedMsgId -> Maybe CITimed -> Bool -> ChatItemTs -> Maybe GroupMemberId -> UTCTime -> IO (ChatItem c d)
+mkChatItem cd ciId content file quotedItem sharedMsgId itemTimed live itemTs forwardedByGroupMemberId currentTs = do
let itemText = ciContentToText content
itemStatus = ciCreateStatus content
- meta = mkCIMeta ciId content itemText itemStatus sharedMsgId Nothing False itemTimed (justTrue live) currentTs itemTs currentTs currentTs
+ meta = mkCIMeta ciId content itemText itemStatus sharedMsgId Nothing False itemTimed (justTrue live) currentTs itemTs forwardedByGroupMemberId currentTs currentTs
pure ChatItem {chatDir = toCIDirection cd, meta, content, formattedText = parseMaybeMarkdownList itemText, quotedItem, reactions = [], file}
deleteDirectCI :: (ChatMonad m, MsgDirectionI d) => User -> Contact -> ChatItem 'CTDirect d -> Bool -> Bool -> m ChatResponse
@@ -5584,7 +5768,7 @@ createInternalChatItem user cd content itemTs_ = do
ciId <- withStore' $ \db -> do
when (ciRequiresAttention content) $ updateChatTs db user cd createdAt
createNewChatItemNoMsg db user cd content itemTs createdAt
- ci <- liftIO $ mkChatItem cd ciId content Nothing Nothing Nothing Nothing False itemTs createdAt
+ ci <- liftIO $ mkChatItem cd ciId content Nothing Nothing Nothing Nothing False itemTs Nothing createdAt
toView $ CRNewChatItem user (AChatItem (chatTypeI @c) (msgDirection @d) (toChatInfo cd) ci)
getCreateActiveUser :: SQLiteStore -> Bool -> IO User
diff --git a/src/Simplex/Chat/Controller.hs b/src/Simplex/Chat/Controller.hs
index d2e81f96f5..7c67cd9e52 100644
--- a/src/Simplex/Chat/Controller.hs
+++ b/src/Simplex/Chat/Controller.hs
@@ -126,7 +126,8 @@ data ChatConfig = ChatConfig
cleanupManagerInterval :: NominalDiffTime,
cleanupManagerStepDelay :: Int64,
ciExpirationInterval :: Int64, -- microseconds
- coreApi :: Bool
+ coreApi :: Bool,
+ highlyAvailable :: Bool
}
data DefaultAgentServers = DefaultAgentServers
diff --git a/src/Simplex/Chat/Messages.hs b/src/Simplex/Chat/Messages.hs
index 79574f8e20..e7d271b73c 100644
--- a/src/Simplex/Chat/Messages.hs
+++ b/src/Simplex/Chat/Messages.hs
@@ -159,7 +159,7 @@ isMention ChatItem {chatDir, quotedItem} = case chatDir of
CIQDirectSnd -> True
CIQGroupSnd -> True
_ -> False
-
+
data CIDirection (c :: ChatType) (d :: MsgDirection) where
CIDirectSnd :: CIDirection 'CTDirect 'MDSnd
CIDirectRcv :: CIDirection 'CTDirect 'MDRcv
@@ -338,17 +338,18 @@ data CIMeta (c :: ChatType) (d :: MsgDirection) = CIMeta
itemTimed :: Maybe CITimed,
itemLive :: Maybe Bool,
editable :: Bool,
+ forwardedByGroupMemberId :: Maybe GroupMemberId,
createdAt :: UTCTime,
updatedAt :: UTCTime
}
deriving (Show, Generic)
-mkCIMeta :: ChatItemId -> CIContent d -> Text -> CIStatus d -> Maybe SharedMsgId -> Maybe (CIDeleted c) -> Bool -> Maybe CITimed -> Maybe Bool -> UTCTime -> ChatItemTs -> UTCTime -> UTCTime -> CIMeta c d
-mkCIMeta itemId itemContent itemText itemStatus itemSharedMsgId itemDeleted itemEdited itemTimed itemLive currentTs itemTs createdAt updatedAt =
+mkCIMeta :: ChatItemId -> CIContent d -> Text -> CIStatus d -> Maybe SharedMsgId -> Maybe (CIDeleted c) -> Bool -> Maybe CITimed -> Maybe Bool -> UTCTime -> ChatItemTs -> Maybe GroupMemberId -> UTCTime -> UTCTime -> CIMeta c d
+mkCIMeta itemId itemContent itemText itemStatus itemSharedMsgId itemDeleted itemEdited itemTimed itemLive currentTs itemTs forwardedByGroupMemberId createdAt updatedAt =
let editable = case itemContent of
CISndMsgContent _ -> diffUTCTime currentTs itemTs < nominalDay && isNothing itemDeleted
_ -> False
- in CIMeta {itemId, itemTs, itemText, itemStatus, itemSharedMsgId, itemDeleted, itemEdited, itemTimed, itemLive, editable, createdAt, updatedAt}
+ in CIMeta {itemId, itemTs, itemText, itemStatus, itemSharedMsgId, itemDeleted, itemEdited, itemTimed, itemLive, editable, forwardedByGroupMemberId, createdAt, updatedAt}
instance ToJSON (CIMeta c d) where toEncoding = J.genericToEncoding J.defaultOptions
@@ -811,7 +812,9 @@ data RcvMessage = RcvMessage
{ msgId :: MessageId,
chatMsgEvent :: AChatMsgEvent,
sharedMsgId_ :: Maybe SharedMsgId,
- msgBody :: MsgBody
+ msgBody :: MsgBody,
+ authorGroupMemberId :: Maybe GroupMemberId,
+ forwardedByGroupMemberId :: Maybe GroupMemberId
}
data PendingGroupMessage = PendingGroupMessage
diff --git a/src/Simplex/Chat/Migrations/M20231113_group_forward.hs b/src/Simplex/Chat/Migrations/M20231113_group_forward.hs
new file mode 100644
index 0000000000..f23387f011
--- /dev/null
+++ b/src/Simplex/Chat/Migrations/M20231113_group_forward.hs
@@ -0,0 +1,53 @@
+{-# LANGUAGE QuasiQuotes #-}
+
+module Simplex.Chat.Migrations.M20231113_group_forward where
+
+import Database.SQLite.Simple (Query)
+import Database.SQLite.Simple.QQ (sql)
+
+m20231113_group_forward :: Query
+m20231113_group_forward =
+ [sql|
+ALTER TABLE group_member_intros ADD COLUMN intro_chat_protocol_version INTEGER NOT NULL DEFAULT 3;
+CREATE INDEX idx_group_member_intros_re_group_member_id ON group_member_intros(re_group_member_id);
+
+ALTER TABLE group_members ADD COLUMN invited_by_group_member_id INTEGER REFERENCES group_members ON DELETE SET NULL;
+ALTER TABLE group_members ADD COLUMN peer_chat_min_version INTEGER NOT NULL DEFAULT 1;
+ALTER TABLE group_members ADD COLUMN peer_chat_max_version INTEGER NOT NULL DEFAULT 1;
+CREATE INDEX idx_group_members_invited_by_group_member_id ON group_members(invited_by_group_member_id);
+
+UPDATE group_members
+SET (peer_chat_min_version, peer_chat_max_version) = (c.peer_chat_min_version, c.peer_chat_max_version)
+FROM connections c
+WHERE c.group_member_id = group_members.group_member_id;
+
+ALTER TABLE messages ADD COLUMN author_group_member_id INTEGER REFERENCES group_members ON DELETE SET NULL;
+ALTER TABLE messages ADD COLUMN forwarded_by_group_member_id INTEGER REFERENCES group_members ON DELETE SET NULL;
+CREATE INDEX idx_messages_author_group_member_id ON messages(author_group_member_id);
+CREATE INDEX idx_messages_forwarded_by_group_member_id ON messages(forwarded_by_group_member_id);
+CREATE INDEX idx_messages_group_id_shared_msg_id ON messages(group_id, shared_msg_id);
+
+ALTER TABLE chat_items ADD COLUMN forwarded_by_group_member_id INTEGER REFERENCES group_members ON DELETE SET NULL;
+CREATE INDEX idx_chat_items_forwarded_by_group_member_id ON chat_items(forwarded_by_group_member_id);
+|]
+
+down_m20231113_group_forward :: Query
+down_m20231113_group_forward =
+ [sql|
+DROP INDEX idx_chat_items_forwarded_by_group_member_id;
+ALTER TABLE chat_items DROP COLUMN forwarded_by_group_member_id;
+
+DROP INDEX idx_messages_group_id_shared_msg_id;
+DROP INDEX idx_messages_forwarded_by_group_member_id;
+DROP INDEX idx_messages_author_group_member_id;
+ALTER TABLE messages DROP COLUMN forwarded_by_group_member_id;
+ALTER TABLE messages DROP COLUMN author_group_member_id;
+
+DROP INDEX idx_group_members_invited_by_group_member_id;
+ALTER TABLE group_members DROP COLUMN peer_chat_max_version;
+ALTER TABLE group_members DROP COLUMN peer_chat_min_version;
+ALTER TABLE group_members DROP COLUMN invited_by_group_member_id;
+
+DROP INDEX idx_group_member_intros_re_group_member_id;
+ALTER TABLE group_member_intros DROP COLUMN intro_chat_protocol_version;
+|]
diff --git a/src/Simplex/Chat/Migrations/chat_schema.sql b/src/Simplex/Chat/Migrations/chat_schema.sql
index 875ee91de2..6f576a75e9 100644
--- a/src/Simplex/Chat/Migrations/chat_schema.sql
+++ b/src/Simplex/Chat/Migrations/chat_schema.sql
@@ -147,6 +147,9 @@ CREATE TABLE group_members(
member_profile_id INTEGER REFERENCES contact_profiles ON DELETE SET NULL,
show_messages INTEGER NOT NULL DEFAULT 1,
xgrplinkmem_received INTEGER NOT NULL DEFAULT 0,
+ invited_by_group_member_id INTEGER REFERENCES group_members ON DELETE SET NULL,
+ peer_chat_min_version INTEGER NOT NULL DEFAULT 1,
+ peer_chat_max_version INTEGER NOT NULL DEFAULT 1,
FOREIGN KEY(user_id, local_display_name)
REFERENCES display_names(user_id, local_display_name)
ON DELETE CASCADE
@@ -161,7 +164,8 @@ CREATE TABLE group_member_intros(
direct_queue_info BLOB,
intro_status TEXT NOT NULL,
created_at TEXT CHECK(created_at NOT NULL),
- updated_at TEXT CHECK(updated_at NOT NULL), -- see GroupMemberIntroStatus
+ updated_at TEXT CHECK(updated_at NOT NULL),
+ intro_chat_protocol_version INTEGER NOT NULL DEFAULT 3, -- see GroupMemberIntroStatus
UNIQUE(re_group_member_id, to_group_member_id)
);
CREATE TABLE files(
@@ -322,7 +326,9 @@ CREATE TABLE messages(
connection_id INTEGER DEFAULT NULL REFERENCES connections ON DELETE CASCADE,
group_id INTEGER DEFAULT NULL REFERENCES groups ON DELETE CASCADE,
shared_msg_id BLOB,
- shared_msg_id_user INTEGER
+ shared_msg_id_user INTEGER,
+ author_group_member_id INTEGER REFERENCES group_members ON DELETE SET NULL,
+ forwarded_by_group_member_id INTEGER REFERENCES group_members ON DELETE SET NULL
);
CREATE TABLE msg_deliveries(
msg_delivery_id INTEGER PRIMARY KEY,
@@ -372,7 +378,8 @@ CREATE TABLE chat_items(
timed_delete_at TEXT,
item_live INTEGER,
item_deleted_by_group_member_id INTEGER REFERENCES group_members ON DELETE SET NULL,
- item_deleted_ts TEXT
+ item_deleted_ts TEXT,
+ forwarded_by_group_member_id INTEGER REFERENCES group_members ON DELETE SET NULL
);
CREATE TABLE chat_item_messages(
chat_item_id INTEGER NOT NULL REFERENCES chat_items ON DELETE CASCADE,
@@ -752,3 +759,22 @@ CREATE INDEX idx_contact_profiles_contact_link ON contact_profiles(
user_id,
contact_link
);
+CREATE INDEX idx_group_member_intros_re_group_member_id ON group_member_intros(
+ re_group_member_id
+);
+CREATE INDEX idx_group_members_invited_by_group_member_id ON group_members(
+ invited_by_group_member_id
+);
+CREATE INDEX idx_messages_author_group_member_id ON messages(
+ author_group_member_id
+);
+CREATE INDEX idx_messages_forwarded_by_group_member_id ON messages(
+ forwarded_by_group_member_id
+);
+CREATE INDEX idx_messages_group_id_shared_msg_id ON messages(
+ group_id,
+ shared_msg_id
+);
+CREATE INDEX idx_chat_items_forwarded_by_group_member_id ON chat_items(
+ forwarded_by_group_member_id
+);
diff --git a/src/Simplex/Chat/Mobile.hs b/src/Simplex/Chat/Mobile.hs
index b444888145..8888ed13e0 100644
--- a/src/Simplex/Chat/Mobile.hs
+++ b/src/Simplex/Chat/Mobile.hs
@@ -152,7 +152,8 @@ mobileChatOpts dbFilePrefix dbKey =
logServerHosts = True,
logAgent = Nothing,
logFile = Nothing,
- tbqSize = 1024
+ tbqSize = 1024,
+ highlyAvailable = False
},
chatCmd = "",
chatCmdDelay = 3,
diff --git a/src/Simplex/Chat/Options.hs b/src/Simplex/Chat/Options.hs
index 0b39b8dd4f..04aef29dfa 100644
--- a/src/Simplex/Chat/Options.hs
+++ b/src/Simplex/Chat/Options.hs
@@ -54,7 +54,8 @@ data CoreChatOpts = CoreChatOpts
logServerHosts :: Bool,
logAgent :: Maybe LogLevel,
logFile :: Maybe FilePath,
- tbqSize :: Natural
+ tbqSize :: Natural,
+ highlyAvailable :: Bool
}
agentLogLevel :: ChatLogLevel -> LogLevel
@@ -172,6 +173,11 @@ coreChatOptsP appDir defaultDbFileName = do
<> value 1024
<> showDefault
)
+ highlyAvailable <-
+ switch
+ ( long "ha"
+ <> help "Run as a highly available client (this may increase traffic in groups)"
+ )
pure
CoreChatOpts
{ dbFilePrefix,
@@ -184,7 +190,8 @@ coreChatOptsP appDir defaultDbFileName = do
logServerHosts = logServerHosts || logLevel <= CLLInfo,
logAgent = if logAgent || logLevel == CLLDebug then Just $ agentLogLevel logLevel else Nothing,
logFile,
- tbqSize
+ tbqSize,
+ highlyAvailable
}
where
useTcpTimeout p t = 1000000 * if t > 0 then t else maybe 5 (const 10) p
diff --git a/src/Simplex/Chat/Protocol.hs b/src/Simplex/Chat/Protocol.hs
index 5d11bdcc86..86069d7793 100644
--- a/src/Simplex/Chat/Protocol.hs
+++ b/src/Simplex/Chat/Protocol.hs
@@ -2,6 +2,7 @@
{-# LANGUAGE DeriveAnyClass #-}
{-# LANGUAGE DeriveGeneric #-}
{-# LANGUAGE DuplicateRecordFields #-}
+{-# LANGUAGE FlexibleInstances #-}
{-# LANGUAGE GADTs #-}
{-# LANGUAGE KindSignatures #-}
{-# LANGUAGE LambdaCase #-}
@@ -17,7 +18,7 @@ module Simplex.Chat.Protocol where
import Control.Applicative ((<|>))
import Control.Monad ((<=<))
-import Data.Aeson (FromJSON, ToJSON, (.:), (.:?), (.=))
+import Data.Aeson (FromJSON (..), ToJSON (..), (.:), (.:?), (.=))
import qualified Data.Aeson as J
import qualified Data.Aeson.Encoding as JE
import qualified Data.Aeson.KeyMap as JM
@@ -49,7 +50,7 @@ import Simplex.Messaging.Util (eitherToMaybe, safeDecodeUtf8, (<$?>))
import Simplex.Messaging.Version hiding (version)
currentChatVersion :: Version
-currentChatVersion = 3
+currentChatVersion = 4
supportedChatVRange :: VersionRange
supportedChatVRange = mkVersionRange 1 currentChatVersion
@@ -66,6 +67,10 @@ xGrpDirectInvVRange = mkVersionRange 2 currentChatVersion
groupLinkNoContactVRange :: VersionRange
groupLinkNoContactVRange = mkVersionRange 3 currentChatVersion
+-- version range that supports group forwarding
+groupForwardVRange :: VersionRange
+groupForwardVRange = mkVersionRange 4 currentChatVersion
+
data ConnectionEntity
= RcvDirectMsgConnection {entityConnection :: Connection, contact :: Maybe Contact}
| RcvGroupMsgConnection {entityConnection :: Connection, groupInfo :: GroupInfo, groupMember :: GroupMember}
@@ -126,7 +131,7 @@ data AppMessageJson = AppMessageJson
event :: Text,
params :: J.Object
}
- deriving (Generic, FromJSON)
+ deriving (Eq, Show, Generic, FromJSON)
data AppMessageBinary = AppMessageBinary
{ msgId :: Maybe SharedMsgId,
@@ -206,7 +211,6 @@ instance StrEncoding AChatMessage where
data ChatMsgEvent (e :: MsgEncoding) where
XMsgNew :: MsgContainer -> ChatMsgEvent 'Json
XMsgFileDescr :: {msgId :: SharedMsgId, fileDescr :: FileDescr} -> ChatMsgEvent 'Json
- XMsgFileCancel :: SharedMsgId -> ChatMsgEvent 'Json
XMsgUpdate :: {msgId :: SharedMsgId, content :: MsgContent, ttl :: Maybe Int, live :: Maybe Bool} -> ChatMsgEvent 'Json
XMsgDel :: SharedMsgId -> Maybe MemberId -> ChatMsgEvent 'Json
XMsgDeleted :: ChatMsgEvent 'Json
@@ -228,13 +232,14 @@ data ChatMsgEvent (e :: MsgEncoding) where
XGrpMemFwd :: MemberInfo -> IntroInvitation -> ChatMsgEvent 'Json
XGrpMemInfo :: MemberId -> Profile -> ChatMsgEvent 'Json
XGrpMemRole :: MemberId -> GroupMemberRole -> ChatMsgEvent 'Json
- XGrpMemCon :: MemberId -> ChatMsgEvent 'Json -- TODO not implemented
+ XGrpMemCon :: MemberId -> ChatMsgEvent 'Json
XGrpMemConAll :: MemberId -> ChatMsgEvent 'Json -- TODO not implemented
XGrpMemDel :: MemberId -> ChatMsgEvent 'Json
XGrpLeave :: ChatMsgEvent 'Json
XGrpDel :: ChatMsgEvent 'Json
XGrpInfo :: GroupProfile -> ChatMsgEvent 'Json
XGrpDirectInv :: ConnReqInvitation -> Maybe MsgContent -> ChatMsgEvent 'Json
+ XGrpMsgForward :: MemberId -> ChatMessage 'Json -> UTCTime -> ChatMsgEvent 'Json
XInfoProbe :: Probe -> ChatMsgEvent 'Json
XInfoProbeCheck :: ProbeHash -> ChatMsgEvent 'Json
XInfoProbeOk :: Probe -> ChatMsgEvent 'Json
@@ -255,6 +260,30 @@ data AChatMsgEvent = forall e. MsgEncodingI e => ACME (SMsgEncoding e) (ChatMsgE
deriving instance Show AChatMsgEvent
+isForwardedGroupMsg :: ChatMsgEvent e -> Bool
+isForwardedGroupMsg ev = case ev of
+ XMsgNew mc -> case mcExtMsgContent mc of
+ ExtMsgContent {file = Just FileInvitation {fileInline = Just _}} -> False
+ _ -> True
+ XMsgFileDescr _ _ -> True
+ XMsgUpdate {} -> True
+ XMsgDel _ _ -> True
+ XMsgReact {} -> True
+ XFileCancel _ -> True
+ XInfo _ -> True
+ XGrpMemNew _ -> True
+ XGrpMemRole {} -> True
+ XGrpMemDel _ -> True -- TODO there should be a special logic when deleting host member (e.g., host forwards it before deleting connections)
+ XGrpLeave -> True
+ XGrpDel -> True -- TODO there should be a special logic - host should forward before deleting connections
+ XGrpInfo _ -> True
+ _ -> False
+
+forwardedGroupMsg :: forall e. MsgEncodingI e => ChatMessage e -> Maybe (ChatMessage 'Json)
+forwardedGroupMsg msg@ChatMessage {chatMsgEvent} = case encoding @e of
+ SJson | isForwardedGroupMsg chatMsgEvent -> Just msg
+ _ -> Nothing
+
data MsgReaction = MREmoji {emoji :: MREmojiChar} | MRUnknown {tag :: Text, json :: J.Object}
deriving (Eq, Show)
@@ -549,7 +578,6 @@ instance FromField MsgContent where
data CMEventTag (e :: MsgEncoding) where
XMsgNew_ :: CMEventTag 'Json
XMsgFileDescr_ :: CMEventTag 'Json
- XMsgFileCancel_ :: CMEventTag 'Json
XMsgUpdate_ :: CMEventTag 'Json
XMsgDel_ :: CMEventTag 'Json
XMsgDeleted_ :: CMEventTag 'Json
@@ -578,6 +606,7 @@ data CMEventTag (e :: MsgEncoding) where
XGrpDel_ :: CMEventTag 'Json
XGrpInfo_ :: CMEventTag 'Json
XGrpDirectInv_ :: CMEventTag 'Json
+ XGrpMsgForward_ :: CMEventTag 'Json
XInfoProbe_ :: CMEventTag 'Json
XInfoProbeCheck_ :: CMEventTag 'Json
XInfoProbeOk_ :: CMEventTag 'Json
@@ -598,7 +627,6 @@ instance MsgEncodingI e => StrEncoding (CMEventTag e) where
strEncode = \case
XMsgNew_ -> "x.msg.new"
XMsgFileDescr_ -> "x.msg.file.descr"
- XMsgFileCancel_ -> "x.msg.file.cancel"
XMsgUpdate_ -> "x.msg.update"
XMsgDel_ -> "x.msg.del"
XMsgDeleted_ -> "x.msg.deleted"
@@ -627,6 +655,7 @@ instance MsgEncodingI e => StrEncoding (CMEventTag e) where
XGrpDel_ -> "x.grp.del"
XGrpInfo_ -> "x.grp.info"
XGrpDirectInv_ -> "x.grp.direct.inv"
+ XGrpMsgForward_ -> "x.grp.msg.forward"
XInfoProbe_ -> "x.info.probe"
XInfoProbeCheck_ -> "x.info.probe.check"
XInfoProbeOk_ -> "x.info.probe.ok"
@@ -648,7 +677,6 @@ instance StrEncoding ACMEventTag where
('x', t) -> pure . ACMEventTag SJson $ case t of
"x.msg.new" -> XMsgNew_
"x.msg.file.descr" -> XMsgFileDescr_
- "x.msg.file.cancel" -> XMsgFileCancel_
"x.msg.update" -> XMsgUpdate_
"x.msg.del" -> XMsgDel_
"x.msg.deleted" -> XMsgDeleted_
@@ -677,6 +705,7 @@ instance StrEncoding ACMEventTag where
"x.grp.del" -> XGrpDel_
"x.grp.info" -> XGrpInfo_
"x.grp.direct.inv" -> XGrpDirectInv_
+ "x.grp.msg.forward" -> XGrpMsgForward_
"x.info.probe" -> XInfoProbe_
"x.info.probe.check" -> XInfoProbeCheck_
"x.info.probe.ok" -> XInfoProbeOk_
@@ -694,7 +723,6 @@ toCMEventTag :: ChatMsgEvent e -> CMEventTag e
toCMEventTag msg = case msg of
XMsgNew _ -> XMsgNew_
XMsgFileDescr _ _ -> XMsgFileDescr_
- XMsgFileCancel _ -> XMsgFileCancel_
XMsgUpdate {} -> XMsgUpdate_
XMsgDel {} -> XMsgDel_
XMsgDeleted -> XMsgDeleted_
@@ -723,6 +751,7 @@ toCMEventTag msg = case msg of
XGrpDel -> XGrpDel_
XGrpInfo _ -> XGrpInfo_
XGrpDirectInv _ _ -> XGrpDirectInv_
+ XGrpMsgForward {} -> XGrpMsgForward_
XInfoProbe _ -> XInfoProbe_
XInfoProbeCheck _ -> XInfoProbeCheck_
XInfoProbeOk _ -> XInfoProbeOk_
@@ -793,7 +822,6 @@ appJsonToCM AppMessageJson {v, msgId, event, params} = do
msg = \case
XMsgNew_ -> XMsgNew <$> JT.parseEither parseMsgContainer params
XMsgFileDescr_ -> XMsgFileDescr <$> p "msgId" <*> p "fileDescr"
- XMsgFileCancel_ -> XMsgFileCancel <$> p "msgId"
XMsgUpdate_ -> XMsgUpdate <$> p "msgId" <*> p "content" <*> opt "ttl" <*> opt "live"
XMsgDel_ -> XMsgDel <$> p "msgId" <*> opt "memberId"
XMsgDeleted_ -> pure XMsgDeleted
@@ -822,6 +850,7 @@ appJsonToCM AppMessageJson {v, msgId, event, params} = do
XGrpDel_ -> pure XGrpDel
XGrpInfo_ -> XGrpInfo <$> p "groupProfile"
XGrpDirectInv_ -> XGrpDirectInv <$> p "connReq" <*> opt "content"
+ XGrpMsgForward_ -> XGrpMsgForward <$> p "memberId" <*> p "msg" <*> p "msgTs"
XInfoProbe_ -> XInfoProbe <$> p "probe"
XInfoProbeCheck_ -> XInfoProbeCheck <$> p "probeHash"
XInfoProbeOk_ -> XInfoProbeOk <$> p "probe"
@@ -853,7 +882,6 @@ chatToAppMessage ChatMessage {chatVRange, msgId, chatMsgEvent} = case encoding @
params = \case
XMsgNew container -> msgContainerJSON container
XMsgFileDescr msgId' fileDescr -> o ["msgId" .= msgId', "fileDescr" .= fileDescr]
- XMsgFileCancel msgId' -> o ["msgId" .= msgId']
XMsgUpdate msgId' content ttl live -> o $ ("ttl" .=? ttl) $ ("live" .=? live) ["msgId" .= msgId', "content" .= content]
XMsgDel msgId' memberId -> o $ ("memberId" .=? memberId) ["msgId" .= msgId']
XMsgDeleted -> JM.empty
@@ -882,6 +910,7 @@ chatToAppMessage ChatMessage {chatVRange, msgId, chatMsgEvent} = case encoding @
XGrpDel -> JM.empty
XGrpInfo p -> o ["groupProfile" .= p]
XGrpDirectInv connReq content -> o $ ("content" .=? content) ["connReq" .= connReq]
+ XGrpMsgForward memberId msg msgTs -> o ["memberId" .= memberId, "msg" .= msg, "msgTs" .= msgTs]
XInfoProbe probe -> o ["probe" .= probe]
XInfoProbeCheck probeHash -> o ["probeHash" .= probeHash]
XInfoProbeOk probe -> o ["probe" .= probe]
@@ -892,3 +921,9 @@ chatToAppMessage ChatMessage {chatVRange, msgId, chatMsgEvent} = case encoding @
XCallEnd callId -> o ["callId" .= callId]
XOk -> JM.empty
XUnknown _ ps -> ps
+
+instance ToJSON (ChatMessage 'Json) where
+ toJSON = (\(AMJson msg) -> toJSON msg) . chatToAppMessage
+
+instance FromJSON (ChatMessage 'Json) where
+ parseJSON v = appJsonToCM <$?> parseJSON v
diff --git a/src/Simplex/Chat/Store/Connections.hs b/src/Simplex/Chat/Store/Connections.hs
index 3d175d5d58..e710a1d599 100644
--- a/src/Simplex/Chat/Store/Connections.hs
+++ b/src/Simplex/Chat/Store/Connections.hs
@@ -95,13 +95,13 @@ getConnectionEntity db user@User {userId, userContactId} agentConnId = do
-- GroupInfo
g.group_id, g.local_display_name, gp.display_name, gp.full_name, gp.description, gp.image, g.host_conn_custom_user_profile_id, g.enable_ntfs, g.send_rcpts, g.favorite, gp.preferences, g.created_at, g.updated_at, g.chat_ts,
-- GroupInfo {membership}
- mu.group_member_id, mu.group_id, mu.member_id, mu.member_role, mu.member_category,
- mu.member_status, mu.show_messages, mu.invited_by, mu.local_display_name, mu.contact_id, mu.contact_profile_id, pu.contact_profile_id,
+ mu.group_member_id, mu.group_id, mu.member_id, mu.peer_chat_min_version, mu.peer_chat_max_version, mu.member_role, mu.member_category,
+ mu.member_status, mu.show_messages, mu.invited_by, mu.invited_by_group_member_id, mu.local_display_name, mu.contact_id, mu.contact_profile_id, pu.contact_profile_id,
-- GroupInfo {membership = GroupMember {memberProfile}}
pu.display_name, pu.full_name, pu.image, pu.contact_link, pu.local_alias, pu.preferences,
-- from GroupMember
- m.group_member_id, m.group_id, m.member_id, m.member_role, m.member_category, m.member_status, m.show_messages,
- m.invited_by, m.local_display_name, m.contact_id, m.contact_profile_id, p.contact_profile_id, p.display_name, p.full_name, p.image, p.contact_link, p.local_alias, p.preferences
+ m.group_member_id, m.group_id, m.member_id, m.peer_chat_min_version, m.peer_chat_max_version, m.member_role, m.member_category, m.member_status, m.show_messages,
+ m.invited_by, m.invited_by_group_member_id, m.local_display_name, m.contact_id, m.contact_profile_id, p.contact_profile_id, p.display_name, p.full_name, p.image, p.contact_link, p.local_alias, p.preferences
FROM group_members m
JOIN contact_profiles p ON p.contact_profile_id = COALESCE(m.member_profile_id, m.contact_profile_id)
JOIN groups g ON g.group_id = m.group_id
diff --git a/src/Simplex/Chat/Store/Groups.hs b/src/Simplex/Chat/Store/Groups.hs
index 06d366d4f3..6361f7a6f7 100644
--- a/src/Simplex/Chat/Store/Groups.hs
+++ b/src/Simplex/Chat/Store/Groups.hs
@@ -42,6 +42,7 @@ module Simplex.Chat.Store.Groups
getGroupInfoByName,
getGroupMember,
getGroupMemberById,
+ getGroupMemberByMemberId,
getGroupMembers,
getGroupMembersForExpiration,
getGroupCurrentMembersCount,
@@ -74,6 +75,9 @@ module Simplex.Chat.Store.Groups
createIntroductions,
updateIntroStatus,
saveIntroInvitation,
+ getIntroduction,
+ getForwardIntroducedMembers,
+ getForwardInvitedMembers,
createIntroReMember,
createIntroToMemberContact,
saveMemberInvitation,
@@ -120,6 +124,7 @@ import Data.Time.Clock (UTCTime (..), getCurrentTime)
import Database.SQLite.Simple (NamedParam (..), Only (..), Query (..), (:.) (..))
import Database.SQLite.Simple.QQ (sql)
import Simplex.Chat.Messages
+import Simplex.Chat.Protocol (currentChatVersion, groupForwardVRange, supportedChatVRange)
import Simplex.Chat.Store.Direct
import Simplex.Chat.Store.Shared
import Simplex.Chat.Types
@@ -135,9 +140,9 @@ import UnliftIO.STM
type GroupInfoRow = (Int64, GroupName, GroupName, Text, Maybe Text, Maybe ImageData, Maybe ProfileId, Maybe MsgFilter, Maybe Bool, Bool, Maybe GroupPreferences) :. (UTCTime, UTCTime, Maybe UTCTime) :. GroupMemberRow
-type GroupMemberRow = ((Int64, Int64, MemberId, GroupMemberRole, GroupMemberCategory, GroupMemberStatus, Bool) :. (Maybe Int64, ContactName, Maybe ContactId, ProfileId, ProfileId, ContactName, Text, Maybe ImageData, Maybe ConnReqContact, LocalAlias, Maybe Preferences))
+type GroupMemberRow = ((Int64, Int64, MemberId, Version, Version, GroupMemberRole, GroupMemberCategory, GroupMemberStatus, Bool) :. (Maybe Int64, Maybe GroupMemberId, ContactName, Maybe ContactId, ProfileId, ProfileId, ContactName, Text, Maybe ImageData, Maybe ConnReqContact, LocalAlias, Maybe Preferences))
-type MaybeGroupMemberRow = ((Maybe Int64, Maybe Int64, Maybe MemberId, Maybe GroupMemberRole, Maybe GroupMemberCategory, Maybe GroupMemberStatus, Maybe Bool) :. (Maybe Int64, Maybe ContactName, Maybe ContactId, Maybe ProfileId, Maybe ProfileId, Maybe ContactName, Maybe Text, Maybe ImageData, Maybe ConnReqContact, Maybe LocalAlias, Maybe Preferences))
+type MaybeGroupMemberRow = ((Maybe Int64, Maybe Int64, Maybe MemberId, Maybe Version, Maybe Version, Maybe GroupMemberRole, Maybe GroupMemberCategory, Maybe GroupMemberStatus, Maybe Bool) :. (Maybe Int64, Maybe GroupMemberId, Maybe ContactName, Maybe ContactId, Maybe ProfileId, Maybe ProfileId, Maybe ContactName, Maybe Text, Maybe ImageData, Maybe ConnReqContact, Maybe LocalAlias, Maybe Preferences))
toGroupInfo :: Int64 -> GroupInfoRow -> GroupInfo
toGroupInfo userContactId ((groupId, localDisplayName, displayName, fullName, description, image, hostConnCustomUserProfileId, enableNtfs_, sendRcpts, favorite, groupPreferences) :. (createdAt, updatedAt, chatTs) :. userMemberRow) =
@@ -148,16 +153,17 @@ toGroupInfo userContactId ((groupId, localDisplayName, displayName, fullName, de
in GroupInfo {groupId, localDisplayName, groupProfile, fullGroupPreferences, membership, hostConnCustomUserProfileId, chatSettings, createdAt, updatedAt, chatTs}
toGroupMember :: Int64 -> GroupMemberRow -> GroupMember
-toGroupMember userContactId ((groupMemberId, groupId, memberId, memberRole, memberCategory, memberStatus, showMessages) :. (invitedById, localDisplayName, memberContactId, memberContactProfileId, profileId, displayName, fullName, image, contactLink, localAlias, preferences)) =
+toGroupMember userContactId ((groupMemberId, groupId, memberId, minVer, maxVer, memberRole, memberCategory, memberStatus, showMessages) :. (invitedById, invitedByGroupMemberId, localDisplayName, memberContactId, memberContactProfileId, profileId, displayName, fullName, image, contactLink, localAlias, preferences)) =
let memberProfile = LocalProfile {profileId, displayName, fullName, image, contactLink, preferences, localAlias}
memberSettings = GroupMemberSettings {showMessages}
invitedBy = toInvitedBy userContactId invitedById
activeConn = Nothing
+ memberChatVRange = JVersionRange $ fromMaybe (versionToRange maxVer) $ safeVersionRange minVer maxVer
in GroupMember {..}
toMaybeGroupMember :: Int64 -> MaybeGroupMemberRow -> Maybe GroupMember
-toMaybeGroupMember userContactId ((Just groupMemberId, Just groupId, Just memberId, Just memberRole, Just memberCategory, Just memberStatus, Just showMessages) :. (invitedById, Just localDisplayName, memberContactId, Just memberContactProfileId, Just profileId, Just displayName, Just fullName, image, contactLink, Just localAlias, contactPreferences)) =
- Just $ toGroupMember userContactId ((groupMemberId, groupId, memberId, memberRole, memberCategory, memberStatus, showMessages) :. (invitedById, localDisplayName, memberContactId, memberContactProfileId, profileId, displayName, fullName, image, contactLink, localAlias, contactPreferences))
+toMaybeGroupMember userContactId ((Just groupMemberId, Just groupId, Just memberId, Just minVer, Just maxVer, Just memberRole, Just memberCategory, Just memberStatus, Just showMessages) :. (invitedById, invitedByGroupMemberId, Just localDisplayName, memberContactId, Just memberContactProfileId, Just profileId, Just displayName, Just fullName, image, contactLink, Just localAlias, contactPreferences)) =
+ Just $ toGroupMember userContactId ((groupMemberId, groupId, memberId, minVer, maxVer, memberRole, memberCategory, memberStatus, showMessages) :. (invitedById, invitedByGroupMemberId, localDisplayName, memberContactId, memberContactProfileId, profileId, displayName, fullName, image, contactLink, localAlias, contactPreferences))
toMaybeGroupMember _ _ = Nothing
createGroupLink :: DB.Connection -> User -> GroupInfo -> ConnId -> ConnReqContact -> GroupLinkId -> GroupMemberRole -> SubscriptionMode -> ExceptT StoreError IO ()
@@ -252,13 +258,13 @@ getGroupAndMember db User {userId, userContactId} groupMemberId =
-- GroupInfo
g.group_id, g.local_display_name, gp.display_name, gp.full_name, gp.description, gp.image, g.host_conn_custom_user_profile_id, g.enable_ntfs, g.send_rcpts, g.favorite, gp.preferences, g.created_at, g.updated_at, g.chat_ts,
-- GroupInfo {membership}
- mu.group_member_id, mu.group_id, mu.member_id, mu.member_role, mu.member_category,
- mu.member_status, mu.show_messages, mu.invited_by, mu.local_display_name, mu.contact_id, mu.contact_profile_id, pu.contact_profile_id,
+ mu.group_member_id, mu.group_id, mu.member_id, mu.peer_chat_min_version, mu.peer_chat_max_version, mu.member_role, mu.member_category,
+ mu.member_status, mu.show_messages, mu.invited_by, mu.invited_by_group_member_id, mu.local_display_name, mu.contact_id, mu.contact_profile_id, pu.contact_profile_id,
-- GroupInfo {membership = GroupMember {memberProfile}}
pu.display_name, pu.full_name, pu.image, pu.contact_link, pu.local_alias, pu.preferences,
-- from GroupMember
- m.group_member_id, m.group_id, m.member_id, m.member_role, m.member_category, m.member_status, m.show_messages,
- m.invited_by, m.local_display_name, m.contact_id, m.contact_profile_id, p.contact_profile_id, p.display_name, p.full_name, p.image, p.contact_link, p.local_alias, p.preferences,
+ m.group_member_id, m.group_id, m.member_id, m.peer_chat_min_version, m.peer_chat_max_version, m.member_role, m.member_category, m.member_status, m.show_messages,
+ m.invited_by, m.invited_by_group_member_id, m.local_display_name, m.contact_id, m.contact_profile_id, p.contact_profile_id, p.display_name, p.full_name, p.image, p.contact_link, p.local_alias, p.preferences,
c.connection_id, c.agent_conn_id, c.conn_level, c.via_contact, c.via_user_contact_link, c.via_group_link, c.group_link_id, c.custom_user_profile_id,
c.conn_status, c.conn_type, c.contact_conn_initiated, c.local_alias, c.contact_id, c.group_member_id, c.snd_file_id, c.rcv_file_id, c.user_contact_link_id, c.created_at, c.security_code, c.security_code_verified_at, c.auth_err_counter,
c.peer_chat_min_version, c.peer_chat_max_version
@@ -303,14 +309,14 @@ createNewGroup db gVar user@User {userId} groupProfile incognitoProfile = Except
(ldn, userId, profileId, True, currentTs, currentTs, currentTs)
insertedRowId db
memberId <- liftIO $ encodedRandomBytes gVar 12
- membership <- createContactMemberInv_ db user groupId user (MemberIdRole (MemberId memberId) GROwner) GCUserMember GSMemCreator IBUser customUserProfileId currentTs
+ membership <- createContactMemberInv_ db user groupId Nothing user (MemberIdRole (MemberId memberId) GROwner) GCUserMember GSMemCreator IBUser customUserProfileId currentTs supportedChatVRange
let chatSettings = ChatSettings {enableNtfs = MFAll, sendRcpts = Nothing, favorite = False}
pure GroupInfo {groupId, localDisplayName = ldn, groupProfile, fullGroupPreferences, membership, hostConnCustomUserProfileId = Nothing, chatSettings, createdAt = currentTs, updatedAt = currentTs, chatTs = Just currentTs}
-- | creates a new group record for the group the current user was invited to, or returns an existing one
createGroupInvitation :: DB.Connection -> User -> Contact -> GroupInvitation -> Maybe ProfileId -> ExceptT StoreError IO (GroupInfo, GroupMemberId)
createGroupInvitation _ _ Contact {localDisplayName, activeConn = Nothing} _ _ = throwError $ SEContactNotReady localDisplayName
-createGroupInvitation db user@User {userId} contact@Contact {contactId, activeConn = Just Connection {customUserProfileId}} GroupInvitation {fromMember, invitedMember, connRequest, groupProfile} incognitoProfileId = do
+createGroupInvitation db user@User {userId} contact@Contact {contactId, activeConn = Just hostConn@Connection {customUserProfileId}} GroupInvitation {fromMember, invitedMember, connRequest, groupProfile} incognitoProfileId = do
liftIO getInvitationGroupId_ >>= \case
Nothing -> createGroupInvitation_
Just gId -> do
@@ -348,8 +354,9 @@ createGroupInvitation db user@User {userId} contact@Contact {contactId, activeCo
"INSERT INTO groups (group_profile_id, local_display_name, inv_queue_info, host_conn_custom_user_profile_id, user_id, enable_ntfs, created_at, updated_at, chat_ts) VALUES (?,?,?,?,?,?,?,?,?)"
(profileId, localDisplayName, connRequest, customUserProfileId, userId, True, currentTs, currentTs, currentTs)
insertedRowId db
- GroupMember {groupMemberId} <- createContactMemberInv_ db user groupId contact fromMember GCHostMember GSMemInvited IBUnknown Nothing currentTs
- membership <- createContactMemberInv_ db user groupId user invitedMember GCUserMember GSMemInvited (IBContact contactId) incognitoProfileId currentTs
+ let JVersionRange hostVRange = hostConn.peerChatVRange
+ GroupMember {groupMemberId} <- createContactMemberInv_ db user groupId Nothing contact fromMember GCHostMember GSMemInvited IBUnknown Nothing currentTs hostVRange
+ membership <- createContactMemberInv_ db user groupId (Just groupMemberId) user invitedMember GCUserMember GSMemInvited (IBContact contactId) incognitoProfileId currentTs supportedChatVRange
let chatSettings = ChatSettings {enableNtfs = MFAll, sendRcpts = Nothing, favorite = False}
pure (GroupInfo {groupId, localDisplayName, groupProfile, fullGroupPreferences, membership, hostConnCustomUserProfileId = customUserProfileId, chatSettings, createdAt = currentTs, updatedAt = currentTs, chatTs = Just currentTs}, groupMemberId)
@@ -358,8 +365,8 @@ getHostMemberId_ db User {userId} groupId =
ExceptT . firstRow fromOnly (SEHostMemberIdNotFound groupId) $
DB.query db "SELECT group_member_id FROM group_members WHERE user_id = ? AND group_id = ? AND member_category = ?" (userId, groupId, GCHostMember)
-createContactMemberInv_ :: IsContact a => DB.Connection -> User -> GroupId -> a -> MemberIdRole -> GroupMemberCategory -> GroupMemberStatus -> InvitedBy -> Maybe ProfileId -> UTCTime -> ExceptT StoreError IO GroupMember
-createContactMemberInv_ db User {userId, userContactId} groupId userOrContact MemberIdRole {memberId, memberRole} memberCategory memberStatus invitedBy incognitoProfileId createdAt = do
+createContactMemberInv_ :: IsContact a => DB.Connection -> User -> GroupId -> Maybe GroupMemberId -> a -> MemberIdRole -> GroupMemberCategory -> GroupMemberStatus -> InvitedBy -> Maybe ProfileId -> UTCTime -> VersionRange -> ExceptT StoreError IO GroupMember
+createContactMemberInv_ db User {userId, userContactId} groupId invitedByGroupMemberId userOrContact MemberIdRole {memberId, memberRole} memberCategory memberStatus invitedBy incognitoProfileId createdAt memberChatVRange@(VersionRange minV maxV) = do
incognitoProfile <- forM incognitoProfileId $ \profileId -> getProfileById db userId profileId
(localDisplayName, memberProfile) <- case (incognitoProfile, incognitoProfileId) of
(Just profile@LocalProfile {displayName}, Just profileId) ->
@@ -376,11 +383,13 @@ createContactMemberInv_ db User {userId, userContactId} groupId userOrContact Me
memberStatus,
memberSettings = defaultMemberSettings,
invitedBy,
+ invitedByGroupMemberId,
localDisplayName,
memberProfile,
memberContactId = Just $ contactId' userOrContact,
memberContactProfileId = localProfileId (profile' userOrContact),
- activeConn = Nothing
+ activeConn = Nothing,
+ memberChatVRange = JVersionRange memberChatVRange
}
where
insertMember_ :: IO ContactName
@@ -390,12 +399,14 @@ createContactMemberInv_ db User {userId, userContactId} groupId userOrContact Me
db
[sql|
INSERT INTO group_members
- ( group_id, member_id, member_role, member_category, member_status, invited_by,
- user_id, local_display_name, contact_id, contact_profile_id, created_at, updated_at)
- VALUES (?,?,?,?,?,?,?,?,?,?,?,?)
+ ( group_id, member_id, member_role, member_category, member_status, invited_by, invited_by_group_member_id,
+ user_id, local_display_name, contact_id, contact_profile_id, created_at, updated_at,
+ peer_chat_min_version, peer_chat_max_version)
+ VALUES (?,?,?,?,?,?,?,?,?,?,?,?,?,?,?)
|]
- ( (groupId, memberId, memberRole, memberCategory, memberStatus, fromInvitedBy userContactId invitedBy)
+ ( (groupId, memberId, memberRole, memberCategory, memberStatus, fromInvitedBy userContactId invitedBy, invitedByGroupMemberId)
:. (userId, localDisplayName' userOrContact, contactId' userOrContact, localProfileId $ profile' userOrContact, createdAt, createdAt)
+ :. (minV, maxV)
)
pure localDisplayName
insertMemberIncognitoProfile_ :: ContactName -> ProfileId -> ExceptT StoreError IO ContactName
@@ -405,12 +416,14 @@ createContactMemberInv_ db User {userId, userContactId} groupId userOrContact Me
db
[sql|
INSERT INTO group_members
- ( group_id, member_id, member_role, member_category, member_status, invited_by,
- user_id, local_display_name, contact_id, contact_profile_id, member_profile_id, created_at, updated_at)
- VALUES (?,?,?,?,?,?,?,?,?,?,?,?,?)
+ ( group_id, member_id, member_role, member_category, member_status, invited_by, invited_by_group_member_id,
+ user_id, local_display_name, contact_id, contact_profile_id, member_profile_id, created_at, updated_at,
+ peer_chat_min_version, peer_chat_max_version)
+ VALUES (?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?)
|]
- ( (groupId, memberId, memberRole, memberCategory, memberStatus, fromInvitedBy userContactId invitedBy)
+ ( (groupId, memberId, memberRole, memberCategory, memberStatus, fromInvitedBy userContactId invitedBy, invitedByGroupMemberId)
:. (userId, incognitoLdn, contactId' userOrContact, localProfileId $ profile' userOrContact, customUserProfileId, createdAt, createdAt)
+ :. (minV, maxV)
)
pure $ Right incognitoLdn
@@ -425,7 +438,7 @@ createGroupInvitedViaLink
hostMemberId <- insertHost_ currentTs groupId
liftIO $ DB.execute db "UPDATE connections SET conn_type = ?, group_member_id = ?, updated_at = ? WHERE connection_id = ?" (ConnMember, hostMemberId, currentTs, connId)
-- using IBUnknown since host is created without contact
- void $ createContactMemberInv_ db user groupId user invitedMember GCUserMember GSMemAccepted IBUnknown customUserProfileId currentTs
+ void $ createContactMemberInv_ db user groupId (Just hostMemberId) user invitedMember GCUserMember GSMemAccepted IBUnknown customUserProfileId currentTs supportedChatVRange
liftIO $ setViaGroupLinkHash db groupId connId
(,) <$> getGroupInfo db user groupId <*> getGroupMemberById db user hostMemberId
where
@@ -547,8 +560,8 @@ getUserGroupDetails db User {userId, userContactId} _contactId_ search_ =
db
[sql|
SELECT g.group_id, g.local_display_name, gp.display_name, gp.full_name, gp.description, gp.image, g.host_conn_custom_user_profile_id, g.enable_ntfs, g.send_rcpts, g.favorite, gp.preferences, g.created_at, g.updated_at, g.chat_ts,
- mu.group_member_id, g.group_id, mu.member_id, mu.member_role, mu.member_category, mu.member_status, mu.show_messages,
- mu.invited_by, mu.local_display_name, mu.contact_id, mu.contact_profile_id, pu.contact_profile_id, pu.display_name, pu.full_name, pu.image, pu.contact_link, pu.local_alias, pu.preferences
+ mu.group_member_id, g.group_id, mu.member_id, mu.peer_chat_min_version, mu.peer_chat_max_version, mu.member_role, mu.member_category, mu.member_status, mu.show_messages,
+ mu.invited_by, mu.invited_by_group_member_id, mu.local_display_name, mu.contact_id, mu.contact_profile_id, pu.contact_profile_id, pu.display_name, pu.full_name, pu.image, pu.contact_link, pu.local_alias, pu.preferences
FROM groups g
JOIN group_profiles gp USING (group_profile_id)
JOIN group_members mu USING (group_id)
@@ -612,8 +625,8 @@ groupMemberQuery :: Query
groupMemberQuery =
[sql|
SELECT
- m.group_member_id, m.group_id, m.member_id, m.member_role, m.member_category, m.member_status, m.show_messages,
- m.invited_by, m.local_display_name, m.contact_id, m.contact_profile_id, p.contact_profile_id, p.display_name, p.full_name, p.image, p.contact_link, p.local_alias, p.preferences,
+ m.group_member_id, m.group_id, m.member_id, m.peer_chat_min_version, m.peer_chat_max_version, m.member_role, m.member_category, m.member_status, m.show_messages,
+ m.invited_by, m.invited_by_group_member_id, m.local_display_name, m.contact_id, m.contact_profile_id, p.contact_profile_id, p.display_name, p.full_name, p.image, p.contact_link, p.local_alias, p.preferences,
c.connection_id, c.agent_conn_id, c.conn_level, c.via_contact, c.via_user_contact_link, c.via_group_link, c.group_link_id, c.custom_user_profile_id,
c.conn_status, c.conn_type, c.contact_conn_initiated, c.local_alias, c.contact_id, c.group_member_id, c.snd_file_id, c.rcv_file_id, c.user_contact_link_id, c.created_at, c.security_code, c.security_code_verified_at, c.auth_err_counter,
c.peer_chat_min_version, c.peer_chat_max_version
@@ -642,6 +655,14 @@ getGroupMemberById db user@User {userId} groupMemberId =
(groupMemberQuery <> " WHERE m.group_member_id = ? AND m.user_id = ?")
(userId, groupMemberId, userId)
+getGroupMemberByMemberId :: DB.Connection -> User -> GroupInfo -> MemberId -> ExceptT StoreError IO GroupMember
+getGroupMemberByMemberId db user@User {userId} GroupInfo {groupId} memberId =
+ ExceptT . firstRow (toContactMember user) (SEGroupMemberNotFoundByMemberId memberId) $
+ DB.query
+ db
+ (groupMemberQuery <> " WHERE m.group_id = ? AND m.member_id = ?")
+ (userId, groupId, memberId)
+
getGroupMembers :: DB.Connection -> User -> GroupInfo -> IO [GroupMember]
getGroupMembers db user@User {userId, userContactId} GroupInfo {groupId} = do
map (toContactMember user)
@@ -700,15 +721,17 @@ getGroupInvitation db user groupId =
firstRow fromOnly (SEGroupNotFound groupId) $
DB.query db "SELECT g.inv_queue_info FROM groups g WHERE g.group_id = ? AND g.user_id = ?" (groupId, userId)
-createNewContactMember :: DB.Connection -> TVar ChaChaDRG -> User -> GroupId -> Contact -> GroupMemberRole -> ConnId -> ConnReqInvitation -> SubscriptionMode -> ExceptT StoreError IO GroupMember
+createNewContactMember :: DB.Connection -> TVar ChaChaDRG -> User -> GroupInfo -> Contact -> GroupMemberRole -> ConnId -> ConnReqInvitation -> SubscriptionMode -> ExceptT StoreError IO GroupMember
createNewContactMember _ _ _ _ Contact {localDisplayName, activeConn = Nothing} _ _ _ _ = throwError $ SEContactNotReady localDisplayName
-createNewContactMember db gVar User {userId, userContactId} groupId Contact {contactId, localDisplayName, profile, activeConn = Just Connection {peerChatVRange}} memberRole agentConnId connRequest subMode =
+createNewContactMember db gVar User {userId, userContactId} GroupInfo {groupId, membership} Contact {contactId, localDisplayName, profile, activeConn = Just Connection {peerChatVRange}} memberRole agentConnId connRequest subMode =
createWithRandomId gVar $ \memId -> do
createdAt <- liftIO getCurrentTime
member@GroupMember {groupMemberId} <- createMember_ (MemberId memId) createdAt
void $ createMemberConnection_ db userId groupMemberId agentConnId (fromJVersionRange peerChatVRange) Nothing 0 createdAt subMode
pure member
where
+ JVersionRange (VersionRange minV maxV) = peerChatVRange
+ invitedByGroupMemberId = groupMemberId' membership
createMember_ memberId createdAt = do
insertMember_
groupMemberId <- liftIO $ insertedRowId db
@@ -722,11 +745,13 @@ createNewContactMember db gVar User {userId, userContactId} groupId Contact {con
memberStatus = GSMemInvited,
memberSettings = defaultMemberSettings,
invitedBy = IBUser,
+ invitedByGroupMemberId = Just invitedByGroupMemberId,
localDisplayName,
memberProfile = profile,
memberContactId = Just contactId,
memberContactProfileId = localProfileId profile,
- activeConn = Nothing
+ activeConn = Nothing,
+ memberChatVRange = peerChatVRange
}
where
insertMember_ =
@@ -734,16 +759,18 @@ createNewContactMember db gVar User {userId, userContactId} groupId Contact {con
db
[sql|
INSERT INTO group_members
- ( group_id, member_id, member_role, member_category, member_status, invited_by,
- user_id, local_display_name, contact_id, contact_profile_id, sent_inv_queue_info, created_at, updated_at)
- VALUES (?,?,?,?,?,?,?,?,?,?,?,?,?)
+ ( group_id, member_id, member_role, member_category, member_status, invited_by, invited_by_group_member_id,
+ user_id, local_display_name, contact_id, contact_profile_id, sent_inv_queue_info, created_at, updated_at,
+ peer_chat_min_version, peer_chat_max_version)
+ VALUES (?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?)
|]
- ( (groupId, memberId, memberRole, GCInviteeMember, GSMemInvited, fromInvitedBy userContactId IBUser)
+ ( (groupId, memberId, memberRole, GCInviteeMember, GSMemInvited, fromInvitedBy userContactId IBUser, invitedByGroupMemberId)
:. (userId, localDisplayName, contactId, localProfileId profile, connRequest, createdAt, createdAt)
+ :. (minV, maxV)
)
-createNewContactMemberAsync :: DB.Connection -> TVar ChaChaDRG -> User -> GroupId -> Contact -> GroupMemberRole -> (CommandId, ConnId) -> VersionRange -> SubscriptionMode -> ExceptT StoreError IO ()
-createNewContactMemberAsync db gVar user@User {userId, userContactId} groupId Contact {contactId, localDisplayName, profile} memberRole (cmdId, agentConnId) peerChatVRange subMode =
+createNewContactMemberAsync :: DB.Connection -> TVar ChaChaDRG -> User -> GroupInfo -> Contact -> GroupMemberRole -> (CommandId, ConnId) -> VersionRange -> SubscriptionMode -> ExceptT StoreError IO ()
+createNewContactMemberAsync db gVar user@User {userId, userContactId} GroupInfo {groupId, membership} Contact {contactId, localDisplayName, profile} memberRole (cmdId, agentConnId) peerChatVRange subMode =
createWithRandomId gVar $ \memId -> do
createdAt <- liftIO getCurrentTime
insertMember_ (MemberId memId) createdAt
@@ -751,17 +778,20 @@ createNewContactMemberAsync db gVar user@User {userId, userContactId} groupId Co
Connection {connId} <- createMemberConnection_ db userId groupMemberId agentConnId peerChatVRange Nothing 0 createdAt subMode
setCommandConnId db user cmdId connId
where
+ VersionRange minV maxV = peerChatVRange
insertMember_ memberId createdAt =
DB.execute
db
[sql|
INSERT INTO group_members
- ( group_id, member_id, member_role, member_category, member_status, invited_by,
- user_id, local_display_name, contact_id, contact_profile_id, created_at, updated_at)
- VALUES (?,?,?,?,?,?,?,?,?,?,?,?)
+ ( group_id, member_id, member_role, member_category, member_status, invited_by, invited_by_group_member_id,
+ user_id, local_display_name, contact_id, contact_profile_id, created_at, updated_at,
+ peer_chat_min_version, peer_chat_max_version)
+ VALUES (?,?,?,?,?,?,?,?,?,?,?,?,?,?,?)
|]
- ( (groupId, memberId, memberRole, GCInviteeMember, GSMemInvited, fromInvitedBy userContactId IBUser)
+ ( (groupId, memberId, memberRole, GCInviteeMember, GSMemInvited, fromInvitedBy userContactId IBUser, groupMemberId' membership)
:. (userId, localDisplayName, contactId, localProfileId profile, createdAt, createdAt)
+ :. (minV, maxV)
)
createAcceptedMember :: DB.Connection -> TVar ChaChaDRG -> User -> GroupInfo -> UserContactRequest -> GroupMemberRole -> ExceptT StoreError IO (GroupMemberId, MemberId)
@@ -769,8 +799,8 @@ createAcceptedMember
db
gVar
User {userId, userContactId}
- GroupInfo {groupId}
- UserContactRequest {localDisplayName, profileId}
+ GroupInfo {groupId, membership}
+ UserContactRequest {cReqChatVRange, localDisplayName, profileId}
memberRole = do
liftIO $
DB.execute db "DELETE FROM contact_requests WHERE user_id = ? AND local_display_name = ?" (userId, localDisplayName)
@@ -780,17 +810,20 @@ createAcceptedMember
groupMemberId <- liftIO $ insertedRowId db
pure (groupMemberId, MemberId memId)
where
+ JVersionRange (VersionRange minV maxV) = cReqChatVRange
insertMember_ memberId createdAt =
DB.execute
db
[sql|
INSERT INTO group_members
- ( group_id, member_id, member_role, member_category, member_status, invited_by,
- user_id, local_display_name, contact_id, contact_profile_id, created_at, updated_at)
- VALUES (?,?,?,?,?,?,?,?,?,?,?,?)
+ ( group_id, member_id, member_role, member_category, member_status, invited_by, invited_by_group_member_id,
+ user_id, local_display_name, contact_id, contact_profile_id, created_at, updated_at,
+ peer_chat_min_version, peer_chat_max_version)
+ VALUES (?,?,?,?,?,?,?,?,?,?,?,?,?,?,?)
|]
- ( (groupId, memberId, memberRole, GCInviteeMember, GSMemAccepted, fromInvitedBy userContactId IBUser)
+ ( (groupId, memberId, memberRole, GCInviteeMember, GSMemAccepted, fromInvitedBy userContactId IBUser, groupMemberId' membership)
:. (userId, localDisplayName, Nothing :: (Maybe Int64), profileId, createdAt, createdAt)
+ :. (minV, maxV)
)
createAcceptedMemberConnection :: DB.Connection -> User -> (CommandId, ConnId) -> UserContactRequest -> GroupMemberId -> SubscriptionMode -> IO ()
@@ -859,8 +892,8 @@ updateGroupMemberStatusById db userId groupMemberId memStatus = do
(memStatus, currentTs, userId, groupMemberId)
-- | add new member with profile
-createNewGroupMember :: DB.Connection -> User -> GroupInfo -> MemberInfo -> GroupMemberCategory -> GroupMemberStatus -> ExceptT StoreError IO GroupMember
-createNewGroupMember db user gInfo memInfo@MemberInfo {profile} memCategory memStatus = do
+createNewGroupMember :: DB.Connection -> User -> GroupInfo -> GroupMember -> MemberInfo -> GroupMemberCategory -> GroupMemberStatus -> ExceptT StoreError IO GroupMember
+createNewGroupMember db user gInfo invitingMember memInfo@MemberInfo {profile} memCategory memStatus = do
currentTs <- liftIO getCurrentTime
(localDisplayName, memProfileId) <- createNewMemberProfile_ db user profile currentTs
let newMember =
@@ -869,6 +902,7 @@ createNewGroupMember db user gInfo memInfo@MemberInfo {profile} memCategory memS
memCategory,
memStatus,
memInvitedBy = IBUnknown,
+ memInvitedByGroupMemberId = Just $ groupMemberId' invitingMember,
localDisplayName,
memContactId = Nothing,
memProfileId
@@ -891,10 +925,11 @@ createNewMember_
User {userId, userContactId}
GroupInfo {groupId}
NewGroupMember
- { memInfo = MemberInfo memberId memberRole _ memberProfile,
+ { memInfo = MemberInfo memberId memberRole memChatVRange memberProfile,
memCategory = memberCategory,
memStatus = memberStatus,
memInvitedBy = invitedBy,
+ memInvitedByGroupMemberId,
localDisplayName,
memContactId = memberContactId,
memProfileId = memberContactProfileId
@@ -902,18 +937,38 @@ createNewMember_
createdAt = do
let invitedById = fromInvitedBy userContactId invitedBy
activeConn = Nothing
+ mcvr@(VersionRange minV maxV) = maybe chatInitialVRange fromChatVRange memChatVRange
DB.execute
db
[sql|
INSERT INTO group_members
- (group_id, member_id, member_role, member_category, member_status,
- invited_by, user_id, local_display_name, contact_id, contact_profile_id, created_at, updated_at)
- VALUES (?,?,?,?,?,?,?,?,?,?,?,?)
+ (group_id, member_id, member_role, member_category, member_status, invited_by, invited_by_group_member_id,
+ user_id, local_display_name, contact_id, contact_profile_id, created_at, updated_at,
+ peer_chat_min_version, peer_chat_max_version)
+ VALUES (?,?,?,?,?,?,?,?,?,?,?,?,?,?,?)
|]
- (groupId, memberId, memberRole, memberCategory, memberStatus, invitedById, userId, localDisplayName, memberContactId, memberContactProfileId, createdAt, createdAt)
+ ( (groupId, memberId, memberRole, memberCategory, memberStatus, invitedById, memInvitedByGroupMemberId)
+ :. (userId, localDisplayName, memberContactId, memberContactProfileId, createdAt, createdAt)
+ :. (minV, maxV)
+ )
groupMemberId <- insertedRowId db
- let memberSettings = defaultMemberSettings
- pure GroupMember {groupMemberId, groupId, memberId, memberRole, memberCategory, memberStatus, memberSettings, invitedBy, localDisplayName, memberProfile = toLocalProfile memberContactProfileId memberProfile "", memberContactId, memberContactProfileId, activeConn}
+ pure GroupMember {
+ groupMemberId,
+ groupId,
+ memberId,
+ memberRole,
+ memberCategory,
+ memberStatus,
+ memberSettings = defaultMemberSettings,
+ invitedBy,
+ invitedByGroupMemberId = memInvitedByGroupMemberId,
+ localDisplayName,
+ memberProfile = toLocalProfile memberContactProfileId memberProfile "",
+ memberContactId,
+ memberContactProfileId,
+ activeConn,
+ memberChatVRange = JVersionRange mcvr
+ }
checkGroupMemberHasItems :: DB.Connection -> User -> GroupMember -> IO (Maybe ChatItemId)
checkGroupMemberHasItems db User {userId} GroupMember {groupMemberId, groupId} =
@@ -960,10 +1015,10 @@ createIntroductions db members toMember = do
db
[sql|
INSERT INTO group_member_intros
- (re_group_member_id, to_group_member_id, intro_status, created_at, updated_at)
- VALUES (?,?,?,?,?)
+ (re_group_member_id, to_group_member_id, intro_status, intro_chat_protocol_version, created_at, updated_at)
+ VALUES (?,?,?,?,?,?)
|]
- (groupMemberId' reMember, groupMemberId' toMember, GMIntroPending, ts, ts)
+ (groupMemberId' reMember, groupMemberId' toMember, GMIntroPending, currentChatVersion, ts, ts)
introId <- insertedRowId db
pure GroupMemberIntro {introId, reMember, toMember, introStatus = GMIntroPending, introInvitation = Nothing}
@@ -981,7 +1036,7 @@ updateIntroStatus db introId introStatus = do
saveIntroInvitation :: DB.Connection -> GroupMember -> GroupMember -> IntroInvitation -> ExceptT StoreError IO GroupMemberIntro
saveIntroInvitation db reMember toMember introInv = do
- intro <- getIntroduction_ db reMember toMember
+ intro <- getIntroduction db reMember toMember
liftIO $ do
currentTs <- getCurrentTime
DB.executeNamed
@@ -1022,8 +1077,8 @@ saveMemberInvitation db GroupMember {groupMemberId} IntroInvitation {groupConnRe
":group_member_id" := groupMemberId
]
-getIntroduction_ :: DB.Connection -> GroupMember -> GroupMember -> ExceptT StoreError IO GroupMemberIntro
-getIntroduction_ db reMember toMember = ExceptT $ do
+getIntroduction :: DB.Connection -> GroupMember -> GroupMember -> ExceptT StoreError IO GroupMemberIntro
+getIntroduction db reMember toMember = ExceptT $ do
toIntro
<$> DB.query
db
@@ -1040,10 +1095,50 @@ getIntroduction_ db reMember toMember = ExceptT $ do
in Right GroupMemberIntro {introId, reMember, toMember, introStatus, introInvitation}
toIntro _ = Left SEIntroNotFound
+getForwardIntroducedMembers :: DB.Connection -> User -> GroupMember -> Bool -> IO [GroupMember]
+getForwardIntroducedMembers db user invitee highlyAvailable = do
+ memberIds <- map fromOnly <$> query
+ filter memberCurrent . rights <$> mapM (runExceptT . getGroupMemberById db user) memberIds
+ where
+ mId = groupMemberId' invitee
+ query
+ | highlyAvailable = DB.query db q (mId, GMIntroReConnected, GMIntroToConnected, GMIntroConnected)
+ | otherwise =
+ DB.query
+ db
+ (q <> " AND intro_chat_protocol_version >= ?")
+ (mId, GMIntroReConnected, GMIntroToConnected, GMIntroConnected, minVersion groupForwardVRange)
+ q =
+ [sql|
+ SELECT re_group_member_id
+ FROM group_member_intros
+ WHERE to_group_member_id = ? AND intro_status NOT IN (?,?,?)
+ |]
+
+getForwardInvitedMembers :: DB.Connection -> User -> GroupMember -> Bool -> IO [GroupMember]
+getForwardInvitedMembers db user forwardMember highlyAvailable = do
+ memberIds <- map fromOnly <$> query
+ filter memberCurrent . rights <$> mapM (runExceptT . getGroupMemberById db user) memberIds
+ where
+ mId = groupMemberId' forwardMember
+ query
+ | highlyAvailable = DB.query db q (mId, GMIntroReConnected, GMIntroToConnected, GMIntroConnected)
+ | otherwise =
+ DB.query
+ db
+ (q <> " AND intro_chat_protocol_version >= ?")
+ (mId, GMIntroReConnected, GMIntroToConnected, GMIntroConnected, minVersion groupForwardVRange)
+ q =
+ [sql|
+ SELECT to_group_member_id
+ FROM group_member_intros
+ WHERE re_group_member_id = ? AND intro_status NOT IN (?,?,?)
+ |]
+
createIntroReMember :: DB.Connection -> User -> GroupInfo -> GroupMember -> MemberInfo -> (CommandId, ConnId) -> Maybe (CommandId, ConnId) -> Maybe ProfileId -> SubscriptionMode -> ExceptT StoreError IO GroupMember
-createIntroReMember db user@User {userId} gInfo@GroupInfo {groupId} _host@GroupMember {memberContactId, activeConn} memInfo@(MemberInfo _ _ memberChatVRange memberProfile) (groupCmdId, groupAgentConnId) directConnIds customUserProfileId subMode = do
- let mcvr = maybe chatInitialVRange fromChatVRange memberChatVRange
- cLevel = 1 + maybe 0 (connLevel :: Connection -> Int) activeConn
+createIntroReMember db user@User {userId} gInfo@GroupInfo {groupId} _host@GroupMember {memberContactId, activeConn} memInfo@(MemberInfo _ _ memChatVRange memberProfile) (groupCmdId, groupAgentConnId) directConnIds customUserProfileId subMode = do
+ let mcvr = maybe chatInitialVRange fromChatVRange memChatVRange
+ cLevel = 1 + maybe 0 (\Connection {connLevel} -> connLevel) activeConn
currentTs <- liftIO getCurrentTime
newMember <- case directConnIds of
Just (directCmdId, directAgentConnId) -> do
@@ -1051,10 +1146,10 @@ createIntroReMember db user@User {userId} gInfo@GroupInfo {groupId} _host@GroupM
liftIO $ setCommandConnId db user directCmdId directConnId
(localDisplayName, contactId, memProfileId) <- createContact_ db userId memberProfile "" (Just groupId) currentTs Nothing
liftIO $ DB.execute db "UPDATE connections SET contact_id = ?, updated_at = ? WHERE connection_id = ?" (contactId, currentTs, directConnId)
- pure $ NewGroupMember {memInfo, memCategory = GCPreMember, memStatus = GSMemIntroduced, memInvitedBy = IBUnknown, localDisplayName, memContactId = Just contactId, memProfileId}
+ pure $ NewGroupMember {memInfo, memCategory = GCPreMember, memStatus = GSMemIntroduced, memInvitedBy = IBUnknown, memInvitedByGroupMemberId = Nothing, localDisplayName, memContactId = Just contactId, memProfileId}
Nothing -> do
(localDisplayName, memProfileId) <- createNewMemberProfile_ db user memberProfile currentTs
- pure $ NewGroupMember {memInfo, memCategory = GCPreMember, memStatus = GSMemIntroduced, memInvitedBy = IBUnknown, localDisplayName, memContactId = Nothing, memProfileId}
+ pure $ NewGroupMember {memInfo, memCategory = GCPreMember, memStatus = GSMemIntroduced, memInvitedBy = IBUnknown, memInvitedByGroupMemberId = Nothing, localDisplayName, memContactId = Nothing, memProfileId}
liftIO $ do
member <- createNewMember_ db user gInfo newMember currentTs
conn@Connection {connId = groupConnId} <- createMemberConnection_ db userId (groupMemberId' member) groupAgentConnId mcvr memberContactId cLevel currentTs subMode
@@ -1111,13 +1206,13 @@ getViaGroupMember db User {userId, userContactId} Contact {contactId} =
-- GroupInfo
g.group_id, g.local_display_name, gp.display_name, gp.full_name, gp.description, gp.image, g.host_conn_custom_user_profile_id, g.enable_ntfs, g.send_rcpts, g.favorite, gp.preferences, g.created_at, g.updated_at, g.chat_ts,
-- GroupInfo {membership}
- mu.group_member_id, mu.group_id, mu.member_id, mu.member_role, mu.member_category,
- mu.member_status, mu.show_messages, mu.invited_by, mu.local_display_name, mu.contact_id, mu.contact_profile_id, pu.contact_profile_id,
+ mu.group_member_id, mu.group_id, mu.member_id, mu.peer_chat_min_version, mu.peer_chat_max_version, mu.member_role, mu.member_category,
+ mu.member_status, mu.show_messages, mu.invited_by, mu.invited_by_group_member_id, mu.local_display_name, mu.contact_id, mu.contact_profile_id, pu.contact_profile_id,
-- GroupInfo {membership = GroupMember {memberProfile}}
pu.display_name, pu.full_name, pu.image, pu.contact_link, pu.local_alias, pu.preferences,
-- via GroupMember
- m.group_member_id, m.group_id, m.member_id, m.member_role, m.member_category, m.member_status, m.show_messages,
- m.invited_by, m.local_display_name, m.contact_id, m.contact_profile_id, p.contact_profile_id, p.display_name, p.full_name, p.image, p.contact_link, p.local_alias, p.preferences,
+ m.group_member_id, m.group_id, m.member_id, m.peer_chat_min_version, m.peer_chat_max_version, m.member_role, m.member_category, m.member_status, m.show_messages,
+ m.invited_by, m.invited_by_group_member_id, m.local_display_name, m.contact_id, m.contact_profile_id, p.contact_profile_id, p.display_name, p.full_name, p.image, p.contact_link, p.local_alias, p.preferences,
c.connection_id, c.agent_conn_id, c.conn_level, c.via_contact, c.via_user_contact_link, c.via_group_link, c.group_link_id, c.custom_user_profile_id,
c.conn_status, c.conn_type, c.contact_conn_initiated, c.local_alias, c.contact_id, c.group_member_id, c.snd_file_id, c.rcv_file_id, c.user_contact_link_id, c.created_at, c.security_code, c.security_code_verified_at, c.auth_err_counter,
c.peer_chat_min_version, c.peer_chat_max_version
@@ -1204,8 +1299,8 @@ getGroupInfo db User {userId, userContactId} groupId =
-- GroupInfo
g.group_id, g.local_display_name, gp.display_name, gp.full_name, gp.description, gp.image, g.host_conn_custom_user_profile_id, g.enable_ntfs, g.send_rcpts, g.favorite, gp.preferences, g.created_at, g.updated_at, g.chat_ts,
-- GroupMember - membership
- mu.group_member_id, mu.group_id, mu.member_id, mu.member_role, mu.member_category,
- mu.member_status, mu.show_messages, mu.invited_by, mu.local_display_name, mu.contact_id, mu.contact_profile_id, pu.contact_profile_id,
+ mu.group_member_id, mu.group_id, mu.member_id, mu.peer_chat_min_version, mu.peer_chat_max_version, mu.member_role, mu.member_category,
+ mu.member_status, mu.show_messages, mu.invited_by, mu.invited_by_group_member_id, mu.local_display_name, mu.contact_id, mu.contact_profile_id, pu.contact_profile_id,
pu.display_name, pu.full_name, pu.image, pu.contact_link, pu.local_alias, pu.preferences
FROM groups g
JOIN group_profiles gp ON gp.group_profile_id = g.group_profile_id
diff --git a/src/Simplex/Chat/Store/Messages.hs b/src/Simplex/Chat/Store/Messages.hs
index e9b01a754b..f2e9138128 100644
--- a/src/Simplex/Chat/Store/Messages.hs
+++ b/src/Simplex/Chat/Store/Messages.hs
@@ -21,6 +21,7 @@ module Simplex.Chat.Store.Messages
createNewSndMessage,
createSndMsgDelivery,
createNewMessageAndRcvMsgDelivery,
+ createNewRcvMessage,
createSndMsgDeliveryEvent,
createRcvMsgDeliveryEvent,
createPendingGroupMessage,
@@ -181,25 +182,53 @@ createSndMsgDelivery db sndMsgDelivery messageId = do
createMsgDeliveryEvent_ db msgDeliveryId MDSSndAgent currentTs
pure msgDeliveryId
-createNewMessageAndRcvMsgDelivery :: forall e. MsgEncodingI e => DB.Connection -> ConnOrGroupId -> NewMessage e -> Maybe SharedMsgId -> RcvMsgDelivery -> IO RcvMessage
-createNewMessageAndRcvMsgDelivery db connOrGroupId NewMessage {chatMsgEvent, msgBody} sharedMsgId_ RcvMsgDelivery {connId, agentMsgId, agentMsgMeta, agentAckCmdId} = do
- currentTs <- getCurrentTime
- DB.execute
- db
- "INSERT INTO messages (msg_sent, chat_msg_event, msg_body, created_at, updated_at, connection_id, group_id, shared_msg_id) VALUES (?,?,?,?,?,?,?,?)"
- (MDRcv, toCMEventTag chatMsgEvent, msgBody, currentTs, currentTs, connId_, groupId_, sharedMsgId_)
- msgId <- insertedRowId db
- DB.execute
- db
- "INSERT INTO msg_deliveries (message_id, connection_id, agent_msg_id, agent_msg_meta, agent_ack_cmd_id, chat_ts, created_at, updated_at) VALUES (?,?,?,?,?,?,?,?)"
- (msgId, connId, agentMsgId, msgMetaJson agentMsgMeta, agentAckCmdId, snd $ broker agentMsgMeta, currentTs, currentTs)
- msgDeliveryId <- insertedRowId db
- createMsgDeliveryEvent_ db msgDeliveryId MDSRcvAgent currentTs
- pure RcvMessage {msgId, chatMsgEvent = ACME (encoding @e) chatMsgEvent, sharedMsgId_, msgBody}
- where
- (connId_, groupId_) = case connOrGroupId of
- ConnectionId connId' -> (Just connId', Nothing)
- GroupId groupId -> (Nothing, Just groupId)
+createNewMessageAndRcvMsgDelivery :: forall e. MsgEncodingI e => DB.Connection -> ConnOrGroupId -> NewMessage e -> Maybe SharedMsgId -> RcvMsgDelivery -> Maybe GroupMemberId -> ExceptT StoreError IO RcvMessage
+createNewMessageAndRcvMsgDelivery db connOrGroupId newMessage sharedMsgId_ RcvMsgDelivery {connId, agentMsgId, agentMsgMeta, agentAckCmdId} authorGroupMemberId_ = do
+ msg@RcvMessage {msgId} <- createNewRcvMessage db connOrGroupId newMessage sharedMsgId_ authorGroupMemberId_ Nothing
+ liftIO $ do
+ currentTs <- getCurrentTime
+ DB.execute
+ db
+ "INSERT INTO msg_deliveries (message_id, connection_id, agent_msg_id, agent_msg_meta, agent_ack_cmd_id, chat_ts, created_at, updated_at) VALUES (?,?,?,?,?,?,?,?)"
+ (msgId, connId, agentMsgId, msgMetaJson agentMsgMeta, agentAckCmdId, snd $ broker agentMsgMeta, currentTs, currentTs)
+ msgDeliveryId <- insertedRowId db
+ createMsgDeliveryEvent_ db msgDeliveryId MDSRcvAgent currentTs
+ pure msg
+
+createNewRcvMessage :: forall e. (MsgEncodingI e) => DB.Connection -> ConnOrGroupId -> NewMessage e -> Maybe SharedMsgId -> Maybe GroupMemberId -> Maybe GroupMemberId -> ExceptT StoreError IO RcvMessage
+createNewRcvMessage db connOrGroupId NewMessage{chatMsgEvent, msgBody} sharedMsgId_ authorGroupMemberId forwardedByGroupMemberId =
+ case connOrGroupId of
+ ConnectionId connId -> liftIO $ insertRcvMsg (Just connId) Nothing
+ GroupId groupId -> case sharedMsgId_ of
+ Just sharedMsgId -> liftIO (duplicateGroupMsgMemberIds groupId sharedMsgId) >>= \case
+ Just (duplAuthorId, duplFwdMemberId) ->
+ throwError $ SEDuplicateGroupMessage groupId sharedMsgId duplAuthorId duplFwdMemberId
+ Nothing -> liftIO $ insertRcvMsg Nothing $ Just groupId
+ Nothing -> liftIO $ insertRcvMsg Nothing $ Just groupId
+ where
+ duplicateGroupMsgMemberIds :: Int64 -> SharedMsgId -> IO (Maybe (Maybe GroupMemberId, Maybe GroupMemberId))
+ duplicateGroupMsgMemberIds groupId sharedMsgId =
+ maybeFirstRow id
+ $ DB.query
+ db
+ [sql|
+ SELECT author_group_member_id, forwarded_by_group_member_id
+ FROM messages
+ WHERE group_id = ? AND shared_msg_id = ? LIMIT 1
+ |]
+ (groupId, sharedMsgId)
+ insertRcvMsg connId_ groupId_ = do
+ currentTs <- getCurrentTime
+ DB.execute
+ db
+ [sql|
+ INSERT INTO messages
+ (msg_sent, chat_msg_event, msg_body, created_at, updated_at, connection_id, group_id, shared_msg_id, author_group_member_id, forwarded_by_group_member_id)
+ VALUES (?,?,?,?,?,?,?,?,?,?)
+ |]
+ (MDRcv, toCMEventTag chatMsgEvent, msgBody, currentTs, currentTs, connId_, groupId_, sharedMsgId_, authorGroupMemberId, forwardedByGroupMemberId)
+ msgId <- insertedRowId db
+ pure RcvMessage{msgId, chatMsgEvent = ACME (encoding @e) chatMsgEvent, sharedMsgId_, msgBody, authorGroupMemberId, forwardedByGroupMemberId}
createSndMsgDeliveryEvent :: DB.Connection -> Int64 -> AgentMsgId -> MsgDeliveryStatus 'MDSnd -> ExceptT StoreError IO ()
createSndMsgDeliveryEvent db connId agentMsgId sndMsgDeliveryStatus = do
@@ -318,7 +347,7 @@ updateChatTs db User {userId} chatDirection chatTs = case toChatInfo chatDirecti
createNewSndChatItem :: DB.Connection -> User -> ChatDirection c 'MDSnd -> SndMessage -> CIContent 'MDSnd -> Maybe (CIQuote c) -> Maybe CITimed -> Bool -> UTCTime -> IO ChatItemId
createNewSndChatItem db user chatDirection SndMessage {msgId, sharedMsgId} ciContent quotedItem timed live createdAt =
- createNewChatItem_ db user chatDirection createdByMsgId (Just sharedMsgId) ciContent quoteRow timed live createdAt createdAt
+ createNewChatItem_ db user chatDirection createdByMsgId (Just sharedMsgId) ciContent quoteRow timed live createdAt Nothing createdAt
where
createdByMsgId = if msgId == 0 then Nothing else Just msgId
quoteRow :: NewQuoteRow
@@ -333,8 +362,8 @@ createNewSndChatItem db user chatDirection SndMessage {msgId, sharedMsgId} ciCon
CIQGroupRcv Nothing -> (Just False, Nothing)
createNewRcvChatItem :: DB.Connection -> User -> ChatDirection c 'MDRcv -> RcvMessage -> Maybe SharedMsgId -> CIContent 'MDRcv -> Maybe CITimed -> Bool -> UTCTime -> UTCTime -> IO (ChatItemId, Maybe (CIQuote c))
-createNewRcvChatItem db user chatDirection RcvMessage {msgId, chatMsgEvent} sharedMsgId_ ciContent timed live itemTs createdAt = do
- ciId <- createNewChatItem_ db user chatDirection (Just msgId) sharedMsgId_ ciContent quoteRow timed live itemTs createdAt
+createNewRcvChatItem db user chatDirection RcvMessage {msgId, chatMsgEvent, forwardedByGroupMemberId} sharedMsgId_ ciContent timed live itemTs createdAt = do
+ ciId <- createNewChatItem_ db user chatDirection (Just msgId) sharedMsgId_ ciContent quoteRow timed live itemTs forwardedByGroupMemberId createdAt
quotedItem <- mapM (getChatItemQuote_ db user chatDirection) quotedMsg
pure (ciId, quotedItem)
where
@@ -349,14 +378,14 @@ createNewRcvChatItem db user chatDirection RcvMessage {msgId, chatMsgEvent} shar
(Just $ Just userMemberId == memberId, memberId)
createNewChatItemNoMsg :: forall c d. MsgDirectionI d => DB.Connection -> User -> ChatDirection c d -> CIContent d -> UTCTime -> UTCTime -> IO ChatItemId
-createNewChatItemNoMsg db user chatDirection ciContent =
- createNewChatItem_ db user chatDirection Nothing Nothing ciContent quoteRow Nothing False
+createNewChatItemNoMsg db user chatDirection ciContent itemTs =
+ createNewChatItem_ db user chatDirection Nothing Nothing ciContent quoteRow Nothing False itemTs Nothing
where
quoteRow :: NewQuoteRow
quoteRow = (Nothing, Nothing, Nothing, Nothing, Nothing)
-createNewChatItem_ :: forall c d. MsgDirectionI d => DB.Connection -> User -> ChatDirection c d -> Maybe MessageId -> Maybe SharedMsgId -> CIContent d -> NewQuoteRow -> Maybe CITimed -> Bool -> UTCTime -> UTCTime -> IO ChatItemId
-createNewChatItem_ db User {userId} chatDirection msgId_ sharedMsgId ciContent quoteRow timed live itemTs createdAt = do
+createNewChatItem_ :: forall c d. MsgDirectionI d => DB.Connection -> User -> ChatDirection c d -> Maybe MessageId -> Maybe SharedMsgId -> CIContent d -> NewQuoteRow -> Maybe CITimed -> Bool -> UTCTime -> Maybe GroupMemberId -> UTCTime -> IO ChatItemId
+createNewChatItem_ db User {userId} chatDirection msgId_ sharedMsgId ciContent quoteRow timed live itemTs forwardedByGroupMemberId createdAt = do
DB.execute
db
[sql|
@@ -364,18 +393,18 @@ createNewChatItem_ db User {userId} chatDirection msgId_ sharedMsgId ciContent q
-- user and IDs
user_id, created_by_msg_id, contact_id, group_id, group_member_id,
-- meta
- item_sent, item_ts, item_content, item_text, item_status, shared_msg_id, created_at, updated_at, item_live, timed_ttl, timed_delete_at,
+ item_sent, item_ts, item_content, item_text, item_status, shared_msg_id, forwarded_by_group_member_id, created_at, updated_at, item_live, timed_ttl, timed_delete_at,
-- quote
quoted_shared_msg_id, quoted_sent_at, quoted_content, quoted_sent, quoted_member_id
- ) VALUES (?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?)
+ ) VALUES (?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?)
|]
((userId, msgId_) :. idsRow :. itemRow :. quoteRow)
ciId <- insertedRowId db
forM_ msgId_ $ \msgId -> insertChatItemMessage_ db ciId msgId createdAt
pure ciId
where
- itemRow :: (SMsgDirection d, UTCTime, CIContent d, Text, CIStatus d, Maybe SharedMsgId) :. (UTCTime, UTCTime, Maybe Bool) :. (Maybe Int, Maybe UTCTime)
- itemRow = (msgDirection @d, itemTs, ciContent, ciContentToText ciContent, ciCreateStatus ciContent, sharedMsgId) :. (createdAt, createdAt, justTrue live) :. ciTimedRow timed
+ itemRow :: (SMsgDirection d, UTCTime, CIContent d, Text, CIStatus d, Maybe SharedMsgId, Maybe GroupMemberId) :. (UTCTime, UTCTime, Maybe Bool) :. (Maybe Int, Maybe UTCTime)
+ itemRow = (msgDirection @d, itemTs, ciContent, ciContentToText ciContent, ciCreateStatus ciContent, sharedMsgId, forwardedByGroupMemberId) :. (createdAt, createdAt, justTrue live) :. ciTimedRow timed
idsRow :: (Maybe Int64, Maybe Int64, Maybe Int64)
idsRow = case chatDirection of
CDDirectRcv Contact {contactId} -> (Just contactId, Nothing, Nothing)
@@ -436,8 +465,8 @@ getChatItemQuote_ db User {userId, userContactId} chatDirection QuotedMsg {msgRe
[sql|
SELECT i.chat_item_id,
-- GroupMember
- m.group_member_id, m.group_id, m.member_id, m.member_role, m.member_category,
- m.member_status, m.show_messages, m.invited_by, m.local_display_name, m.contact_id, m.contact_profile_id, p.contact_profile_id,
+ m.group_member_id, m.group_id, m.member_id, m.peer_chat_min_version, m.peer_chat_max_version, m.member_role, m.member_category,
+ m.member_status, m.show_messages, m.invited_by, m.invited_by_group_member_id, m.local_display_name, m.contact_id, m.contact_profile_id, p.contact_profile_id,
p.display_name, p.full_name, p.image, p.contact_link, p.local_alias, p.preferences
FROM group_members m
JOIN contact_profiles p ON p.contact_profile_id = COALESCE(m.member_profile_id, m.contact_profile_id)
@@ -552,8 +581,8 @@ getGroupChatPreviews_ db User {userId, userContactId} = do
-- GroupInfo
g.group_id, g.local_display_name, gp.display_name, gp.full_name, gp.description, gp.image, g.host_conn_custom_user_profile_id, g.enable_ntfs, g.send_rcpts, g.favorite, gp.preferences, g.created_at, g.updated_at, g.chat_ts,
-- GroupMember - membership
- mu.group_member_id, mu.group_id, mu.member_id, mu.member_role, mu.member_category,
- mu.member_status, mu.show_messages, mu.invited_by, mu.local_display_name, mu.contact_id, mu.contact_profile_id, pu.contact_profile_id,
+ mu.group_member_id, mu.group_id, mu.member_id, mu.peer_chat_min_version, mu.peer_chat_max_version, mu.member_role, mu.member_category,
+ mu.member_status, mu.show_messages, mu.invited_by, mu.invited_by_group_member_id, mu.local_display_name, mu.contact_id, mu.contact_profile_id, pu.contact_profile_id,
pu.display_name, pu.full_name, pu.image, pu.contact_link, pu.local_alias, pu.preferences,
-- ChatStats
COALESCE(ChatStats.UnreadCount, 0), COALESCE(ChatStats.MinUnread, 0), g.unread_chat,
@@ -561,19 +590,21 @@ getGroupChatPreviews_ db User {userId, userContactId} = do
i.chat_item_id, i.item_ts, i.item_sent, i.item_content, i.item_text, i.item_status, i.shared_msg_id, i.item_deleted, i.item_deleted_ts, i.item_edited, i.created_at, i.updated_at, i.timed_ttl, i.timed_delete_at, i.item_live,
-- CIFile
f.file_id, f.file_name, f.file_size, f.file_path, f.file_crypto_key, f.file_crypto_nonce, f.ci_file_status, f.protocol,
+ -- CIMeta forwardedByGroupMemberId
+ i.forwarded_by_group_member_id,
-- Maybe GroupMember - sender
- m.group_member_id, m.group_id, m.member_id, m.member_role, m.member_category,
- m.member_status, m.show_messages, m.invited_by, m.local_display_name, m.contact_id, m.contact_profile_id, p.contact_profile_id,
+ m.group_member_id, m.group_id, m.member_id, m.peer_chat_min_version, m.peer_chat_max_version, m.member_role, m.member_category,
+ m.member_status, m.show_messages, m.invited_by, m.invited_by_group_member_id, m.local_display_name, m.contact_id, m.contact_profile_id, p.contact_profile_id,
p.display_name, p.full_name, p.image, p.contact_link, p.local_alias, p.preferences,
-- quoted ChatItem
ri.chat_item_id, i.quoted_shared_msg_id, i.quoted_sent_at, i.quoted_content, i.quoted_sent,
-- quoted GroupMember
- rm.group_member_id, rm.group_id, rm.member_id, rm.member_role, rm.member_category,
- rm.member_status, rm.show_messages, rm.invited_by, rm.local_display_name, rm.contact_id, rm.contact_profile_id, rp.contact_profile_id,
+ rm.group_member_id, rm.group_id, rm.member_id, rm.peer_chat_min_version, rm.peer_chat_max_version, rm.member_role, rm.member_category,
+ rm.member_status, rm.show_messages, rm.invited_by, rm.invited_by_group_member_id, rm.local_display_name, rm.contact_id, rm.contact_profile_id, rp.contact_profile_id,
rp.display_name, rp.full_name, rp.image, rp.contact_link, rp.local_alias, rp.preferences,
-- deleted by GroupMember
- dbm.group_member_id, dbm.group_id, dbm.member_id, dbm.member_role, dbm.member_category,
- dbm.member_status, dbm.show_messages, dbm.invited_by, dbm.local_display_name, dbm.contact_id, dbm.contact_profile_id, dbp.contact_profile_id,
+ dbm.group_member_id, dbm.group_id, dbm.member_id, dbm.peer_chat_min_version, dbm.peer_chat_max_version, dbm.member_role, dbm.member_category,
+ dbm.member_status, dbm.show_messages, dbm.invited_by, dbm.invited_by_group_member_id, dbm.local_display_name, dbm.contact_id, dbm.contact_profile_id, dbp.contact_profile_id,
dbp.display_name, dbp.full_name, dbp.image, dbp.contact_link, dbp.local_alias, dbp.preferences
FROM groups g
JOIN group_profiles gp ON gp.group_profile_id = g.group_profile_id
@@ -1016,7 +1047,7 @@ toDirectChatItem currentTs (((itemId, itemTs, AMsgDirection msgDir, itemContentT
DBCINotDeleted -> Nothing
_ -> Just (CIDeleted @'CTDirect deletedTs)
itemEdited' = fromMaybe False itemEdited
- in mkCIMeta itemId content itemText status sharedMsgId itemDeleted' itemEdited' ciTimed itemLive currentTs itemTs createdAt updatedAt
+ in mkCIMeta itemId content itemText status sharedMsgId itemDeleted' itemEdited' ciTimed itemLive currentTs itemTs Nothing createdAt updatedAt
ciTimed :: Maybe CITimed
ciTimed = timedTTL >>= \ttl -> Just CITimed {ttl, deleteAt = timedDeleteAt}
@@ -1027,7 +1058,7 @@ toDirectChatItemList _ _ = []
type GroupQuoteRow = QuoteRow :. MaybeGroupMemberRow
-type MaybeGroupChatItemRow = MaybeChatItemRow :. MaybeGroupMemberRow :. GroupQuoteRow :. MaybeGroupMemberRow
+type MaybeGroupChatItemRow = MaybeChatItemRow :. Only (Maybe GroupMemberId) :. MaybeGroupMemberRow :. GroupQuoteRow :. MaybeGroupMemberRow
toGroupQuote :: QuoteRow -> Maybe GroupMember -> Maybe (CIQuote 'CTGroup)
toGroupQuote qr@(_, _, _, _, quotedSent) quotedMember_ = toQuote qr $ direction quotedSent quotedMember_
@@ -1038,8 +1069,8 @@ toGroupQuote qr@(_, _, _, _, quotedSent) quotedMember_ = toQuote qr $ direction
direction _ _ = Nothing
-- this function can be changed so it never fails, not only avoid failure on invalid json
-toGroupChatItem :: UTCTime -> Int64 -> ChatItemRow :. MaybeGroupMemberRow :. GroupQuoteRow :. MaybeGroupMemberRow -> Either StoreError (CChatItem 'CTGroup)
-toGroupChatItem currentTs userContactId (((itemId, itemTs, AMsgDirection msgDir, itemContentText, itemText, itemStatus, sharedMsgId) :. (itemDeleted, deletedTs, itemEdited, createdAt, updatedAt) :. (timedTTL, timedDeleteAt, itemLive) :. (fileId_, fileName_, fileSize_, filePath, fileKey, fileNonce, fileStatus_, fileProtocol_)) :. memberRow_ :. (quoteRow :. quotedMemberRow_) :. deletedByGroupMemberRow_) = do
+toGroupChatItem :: UTCTime -> Int64 -> ChatItemRow :. Only (Maybe GroupMemberId) :. MaybeGroupMemberRow :. GroupQuoteRow :. MaybeGroupMemberRow -> Either StoreError (CChatItem 'CTGroup)
+toGroupChatItem currentTs userContactId (((itemId, itemTs, AMsgDirection msgDir, itemContentText, itemText, itemStatus, sharedMsgId) :. (itemDeleted, deletedTs, itemEdited, createdAt, updatedAt) :. (timedTTL, timedDeleteAt, itemLive) :. (fileId_, fileName_, fileSize_, filePath, fileKey, fileNonce, fileStatus_, fileProtocol_)) :. Only forwardedByGroupMemberId :. memberRow_ :. (quoteRow :. quotedMemberRow_) :. deletedByGroupMemberRow_) = do
chatItem $ fromRight invalid $ dbParseACIContent itemContentText
where
member_ = toMaybeGroupMember userContactId memberRow_
@@ -1075,13 +1106,13 @@ toGroupChatItem currentTs userContactId (((itemId, itemTs, AMsgDirection msgDir,
DBCIBlocked -> Just (CIBlocked @'CTGroup deletedTs)
_ -> Just (maybe (CIDeleted @'CTGroup deletedTs) (CIModerated deletedTs) deletedByGroupMember_)
itemEdited' = fromMaybe False itemEdited
- in mkCIMeta itemId content itemText status sharedMsgId itemDeleted' itemEdited' ciTimed itemLive currentTs itemTs createdAt updatedAt
+ in mkCIMeta itemId content itemText status sharedMsgId itemDeleted' itemEdited' ciTimed itemLive currentTs itemTs forwardedByGroupMemberId createdAt updatedAt
ciTimed :: Maybe CITimed
ciTimed = timedTTL >>= \ttl -> Just CITimed {ttl, deleteAt = timedDeleteAt}
toGroupChatItemList :: UTCTime -> Int64 -> MaybeGroupChatItemRow -> [CChatItem 'CTGroup]
-toGroupChatItemList currentTs userContactId (((Just itemId, Just itemTs, Just msgDir, Just itemContent, Just itemText, Just itemStatus, sharedMsgId) :. (Just itemDeleted, deletedTs, itemEdited, Just createdAt, Just updatedAt) :. (timedTTL, timedDeleteAt, itemLive) :. fileRow) :. memberRow_ :. (quoteRow :. quotedMemberRow_) :. deletedByGroupMemberRow_) =
- either (const []) (: []) $ toGroupChatItem currentTs userContactId (((itemId, itemTs, msgDir, itemContent, itemText, itemStatus, sharedMsgId) :. (itemDeleted, deletedTs, itemEdited, createdAt, updatedAt) :. (timedTTL, timedDeleteAt, itemLive) :. fileRow) :. memberRow_ :. (quoteRow :. quotedMemberRow_) :. deletedByGroupMemberRow_)
+toGroupChatItemList currentTs userContactId (((Just itemId, Just itemTs, Just msgDir, Just itemContent, Just itemText, Just itemStatus, sharedMsgId) :. (Just itemDeleted, deletedTs, itemEdited, Just createdAt, Just updatedAt) :. (timedTTL, timedDeleteAt, itemLive) :. fileRow) :. forwardedByGroupMemberId :. memberRow_ :. (quoteRow :. quotedMemberRow_) :. deletedByGroupMemberRow_) =
+ either (const []) (: []) $ toGroupChatItem currentTs userContactId (((itemId, itemTs, msgDir, itemContent, itemText, itemStatus, sharedMsgId) :. (itemDeleted, deletedTs, itemEdited, createdAt, updatedAt) :. (timedTTL, timedDeleteAt, itemLive) :. fileRow) :. forwardedByGroupMemberId :. memberRow_ :. (quoteRow :. quotedMemberRow_) :. deletedByGroupMemberRow_)
toGroupChatItemList _ _ _ = []
getAllChatItems :: DB.Connection -> User -> ChatPagination -> Maybe String -> ExceptT StoreError IO [AChatItem]
@@ -1525,19 +1556,21 @@ getGroupChatItem db User {userId, userContactId} groupId itemId = ExceptT $ do
i.chat_item_id, i.item_ts, i.item_sent, i.item_content, i.item_text, i.item_status, i.shared_msg_id, i.item_deleted, i.item_deleted_ts, i.item_edited, i.created_at, i.updated_at, i.timed_ttl, i.timed_delete_at, i.item_live,
-- CIFile
f.file_id, f.file_name, f.file_size, f.file_path, f.file_crypto_key, f.file_crypto_nonce, f.ci_file_status, f.protocol,
+ -- CIMeta forwardedByGroupMemberId
+ i.forwarded_by_group_member_id,
-- GroupMember
- m.group_member_id, m.group_id, m.member_id, m.member_role, m.member_category,
- m.member_status, m.show_messages, m.invited_by, m.local_display_name, m.contact_id, m.contact_profile_id, p.contact_profile_id,
+ m.group_member_id, m.group_id, m.member_id, m.peer_chat_min_version, m.peer_chat_max_version, m.member_role, m.member_category,
+ m.member_status, m.show_messages, m.invited_by, m.invited_by_group_member_id, m.local_display_name, m.contact_id, m.contact_profile_id, p.contact_profile_id,
p.display_name, p.full_name, p.image, p.contact_link, p.local_alias, p.preferences,
-- quoted ChatItem
ri.chat_item_id, i.quoted_shared_msg_id, i.quoted_sent_at, i.quoted_content, i.quoted_sent,
-- quoted GroupMember
- rm.group_member_id, rm.group_id, rm.member_id, rm.member_role, rm.member_category,
- rm.member_status, rm.show_messages, rm.invited_by, rm.local_display_name, rm.contact_id, rm.contact_profile_id, rp.contact_profile_id,
+ rm.group_member_id, rm.group_id, rm.member_id, rm.peer_chat_min_version, rm.peer_chat_max_version, rm.member_role, rm.member_category,
+ rm.member_status, rm.show_messages, rm.invited_by, rm.invited_by_group_member_id, rm.local_display_name, rm.contact_id, rm.contact_profile_id, rp.contact_profile_id,
rp.display_name, rp.full_name, rp.image, rp.contact_link, rp.local_alias, rp.preferences,
-- deleted by GroupMember
- dbm.group_member_id, dbm.group_id, dbm.member_id, dbm.member_role, dbm.member_category,
- dbm.member_status, dbm.show_messages, dbm.invited_by, dbm.local_display_name, dbm.contact_id, dbm.contact_profile_id, dbp.contact_profile_id,
+ dbm.group_member_id, dbm.group_id, dbm.member_id, dbm.peer_chat_min_version, dbm.peer_chat_max_version, dbm.member_role, dbm.member_category,
+ dbm.member_status, dbm.show_messages, dbm.invited_by, dbm.invited_by_group_member_id, dbm.local_display_name, dbm.contact_id, dbm.contact_profile_id, dbp.contact_profile_id,
dbp.display_name, dbp.full_name, dbp.image, dbp.contact_link, dbp.local_alias, dbp.preferences
FROM chat_items i
LEFT JOIN files f ON f.chat_item_id = i.chat_item_id
diff --git a/src/Simplex/Chat/Store/Migrations.hs b/src/Simplex/Chat/Store/Migrations.hs
index f5a4426204..e261d97e2a 100644
--- a/src/Simplex/Chat/Store/Migrations.hs
+++ b/src/Simplex/Chat/Store/Migrations.hs
@@ -88,6 +88,7 @@ import Simplex.Chat.Migrations.M20231010_member_settings
import Simplex.Chat.Migrations.M20231019_indexes
import Simplex.Chat.Migrations.M20231030_xgrplinkmem_received
import Simplex.Chat.Migrations.M20231107_indexes
+import Simplex.Chat.Migrations.M20231113_group_forward
import Simplex.Messaging.Agent.Store.SQLite.Migrations (Migration (..))
schemaMigrations :: [(String, Query, Maybe Query)]
@@ -175,7 +176,8 @@ schemaMigrations =
("20231010_member_settings", m20231010_member_settings, Just down_m20231010_member_settings),
("20231019_indexes", m20231019_indexes, Just down_m20231019_indexes),
("20231030_xgrplinkmem_received", m20231030_xgrplinkmem_received, Just down_m20231030_xgrplinkmem_received),
- ("20231107_indexes", m20231107_indexes, Just down_m20231107_indexes)
+ ("20231107_indexes", m20231107_indexes, Just down_m20231107_indexes),
+ ("20231113_group_forward", m20231113_group_forward, Just down_m20231113_group_forward)
]
-- | The list of migrations in ascending order by date
diff --git a/src/Simplex/Chat/Store/Shared.hs b/src/Simplex/Chat/Store/Shared.hs
index bf34ad54cd..0de75e7186 100644
--- a/src/Simplex/Chat/Store/Shared.hs
+++ b/src/Simplex/Chat/Store/Shared.hs
@@ -98,6 +98,7 @@ data StoreError
| SEHostMemberIdNotFound {groupId :: Int64}
| SEContactNotFoundByFileId {fileId :: FileTransferId}
| SENoGroupSndStatus {itemId :: ChatItemId, groupMemberId :: GroupMemberId}
+ | SEDuplicateGroupMessage {groupId :: Int64, sharedMsgId :: SharedMsgId, authorGroupMemberId :: Maybe GroupMemberId, forwardedByGroupMemberId :: Maybe GroupMemberId}
deriving (Show, Exception, Generic)
instance ToJSON StoreError where
@@ -204,6 +205,17 @@ setPeerChatVRange db connId (VersionRange minVer maxVer) =
|]
(minVer, maxVer, connId)
+setMemberChatVRange :: DB.Connection -> GroupMemberId -> VersionRange -> IO ()
+setMemberChatVRange db mId (VersionRange minVer maxVer) =
+ DB.execute
+ db
+ [sql|
+ UPDATE group_members
+ SET peer_chat_min_version = ?, peer_chat_max_version = ?
+ WHERE group_member_id = ?
+ |]
+ (minVer, maxVer, mId)
+
setCommandConnId :: DB.Connection -> User -> CommandId -> Int64 -> IO ()
setCommandConnId db User {userId} cmdId connId = do
updatedAt <- getCurrentTime
diff --git a/src/Simplex/Chat/Types.hs b/src/Simplex/Chat/Types.hs
index 2ba382515b..d4968a9382 100644
--- a/src/Simplex/Chat/Types.hs
+++ b/src/Simplex/Chat/Types.hs
@@ -666,9 +666,9 @@ instance ToJSON MemberInfo where
memberInfo :: GroupMember -> MemberInfo
memberInfo GroupMember {memberId, memberRole, memberProfile, activeConn} =
- MemberInfo memberId memberRole memberChatVRange (fromLocalProfile memberProfile)
+ MemberInfo memberId memberRole cvr (fromLocalProfile memberProfile)
where
- memberChatVRange = ChatVersionRange . fromJVersionRange . peerChatVRange <$> activeConn
+ cvr = ChatVersionRange . fromJVersionRange . peerChatVRange <$> activeConn
data ReceivedGroupInvitation = ReceivedGroupInvitation
{ fromMember :: GroupMember,
@@ -690,6 +690,7 @@ data GroupMember = GroupMember
memberStatus :: GroupMemberStatus,
memberSettings :: GroupMemberSettings,
invitedBy :: InvitedBy,
+ invitedByGroupMemberId :: Maybe GroupMemberId,
localDisplayName :: ContactName,
-- for membership, memberProfile can be either user's profile or incognito profile, based on memberIncognito test.
-- for other members it's whatever profile the local user can see (there is no info about whether it's main or incognito profile for remote users).
@@ -699,7 +700,10 @@ data GroupMember = GroupMember
-- for membership it would always point to user's contact
-- it is used to test for incognito status by comparing with ID in memberProfile
memberContactProfileId :: ProfileId,
- activeConn :: Maybe Connection
+ activeConn :: Maybe Connection,
+ -- member chat protocol version range; if member has active connection, its version range is preferred;
+ -- for membership current supportedChatVRange is set, it's not updated on protocol version increase
+ memberChatVRange :: JVersionRange
}
deriving (Eq, Show, Generic)
@@ -717,11 +721,17 @@ groupMemberRef GroupMember {groupMemberId, memberProfile = p} =
GroupMemberRef {groupMemberId, profile = fromLocalProfile p}
memberConn :: GroupMember -> Maybe Connection
-memberConn = activeConn
+memberConn GroupMember {activeConn} = activeConn
memberConnId :: GroupMember -> Maybe ConnId
memberConnId GroupMember {activeConn} = aConnId <$> activeConn
+memberChatVRange' :: GroupMember -> VersionRange
+memberChatVRange' GroupMember {activeConn, memberChatVRange} =
+ fromJVersionRange $ case activeConn of
+ Just Connection {peerChatVRange} -> peerChatVRange
+ Nothing -> memberChatVRange
+
groupMemberId' :: GroupMember -> GroupMemberId
groupMemberId' GroupMember {groupMemberId} = groupMemberId
@@ -745,6 +755,7 @@ data NewGroupMember = NewGroupMember
memCategory :: GroupMemberCategory,
memStatus :: GroupMemberStatus,
memInvitedBy :: InvitedBy,
+ memInvitedByGroupMemberId :: Maybe GroupMemberId,
localDisplayName :: ContactName,
memProfileId :: Int64,
memContactId :: Maybe Int64
@@ -1469,7 +1480,7 @@ data GroupMemberIntroStatus
| GMIntroReConnected
| GMIntroToConnected
| GMIntroConnected
- deriving (Show)
+ deriving (Eq, Show)
instance FromField GroupMemberIntroStatus where fromField = fromTextField_ introStatusT
diff --git a/src/Simplex/Chat/View.hs b/src/Simplex/Chat/View.hs
index b74151cb81..7918d68ffc 100644
--- a/src/Simplex/Chat/View.hs
+++ b/src/Simplex/Chat/View.hs
@@ -449,7 +449,7 @@ viewChats ts tz = concatMap chatPreview . reverse
viewChatItem :: forall c d. MsgDirectionI d => ChatInfo c -> ChatItem c d -> Bool -> CurrentTime -> TimeZone -> [StyledString]
viewChatItem chat ci@ChatItem {chatDir, meta = meta, content, quotedItem, file} doShow ts tz =
- withItemDeleted <$> case chat of
+ withGroupMsgForwarded . withItemDeleted <$> (case chat of
DirectChat c -> case chatDir of
CIDirectSnd -> case content of
CISndMsgContent mc -> hideLive meta $ withSndFile to $ sndMsg to quote mc
@@ -483,11 +483,14 @@ viewChatItem chat ci@ChatItem {chatDir, meta = meta, content, quotedItem, file}
from = ttyFromGroup g m
where
quote = maybe [] (groupQuote g) quotedItem
- _ -> []
+ _ -> [])
where
withItemDeleted item = case chatItemDeletedText ci (chatInfoMembership chat) of
Nothing -> item
Just t -> item <> styled (colored Red) (" [" <> t <> "]")
+ withGroupMsgForwarded item = case meta.forwardedByGroupMemberId of
+ Nothing -> item
+ Just _ -> item <> styled (colored Yellow) (" [>>]" :: String)
withSndFile = withFile viewSentFileInvitation
withRcvFile = withFile viewReceivedFileInvitation
withFile view dir l = maybe l (\f -> l <> view dir f ts tz meta) file
diff --git a/tests/ChatClient.hs b/tests/ChatClient.hs
index b255c69094..761d4af205 100644
--- a/tests/ChatClient.hs
+++ b/tests/ChatClient.hs
@@ -68,7 +68,8 @@ testOpts =
logServerHosts = False,
logAgent = Nothing,
logFile = Nothing,
- tbqSize = 16
+ tbqSize = 16,
+ highlyAvailable = False
},
chatCmd = "",
chatCmdDelay = 3,
diff --git a/tests/ChatTests/Groups.hs b/tests/ChatTests/Groups.hs
index 36b5cf4ea5..edf3d2fab8 100644
--- a/tests/ChatTests/Groups.hs
+++ b/tests/ChatTests/Groups.hs
@@ -7,12 +7,14 @@ import ChatClient
import ChatTests.Utils
import Control.Concurrent (threadDelay)
import Control.Concurrent.Async (concurrently_)
-import Control.Monad (when)
+import Control.Monad (when, void)
+import qualified Data.ByteString as B
import qualified Data.Text as T
-import Simplex.Chat.Controller (ChatConfig (..))
+import Simplex.Chat.Controller (ChatConfig (..), XFTPFileConfig (..))
import Simplex.Chat.Protocol (supportedChatVRange)
import Simplex.Chat.Store (agentStoreFile, chatStoreFile)
import Simplex.Chat.Types (GroupMemberRole (..))
+import qualified Simplex.Messaging.Agent.Store.SQLite.DB as DB
import Simplex.Messaging.Version
import System.Directory (copyFile)
import System.FilePath ((>))
@@ -103,6 +105,8 @@ chatGroupTests = do
it "invited member replaces member contact reference if it already exists" testMemberContactInvitedConnectionReplaced
it "share incognito profile" testMemberContactIncognito
it "sends and updates profile when creating contact" testMemberContactProfileUpdate
+ describe "forwarding messages" $ do
+ it "admin should forward messages between invitee and introduced" testGroupMsgForward
where
_0 = supportedChatVRange -- don't create direct connections
_1 = groupCreateDirectVRange
@@ -1522,6 +1526,13 @@ testGroupDelayedModeration tmp = do
cath <## "#team: you joined the group"
]
threadDelay 1000000
+
+ -- imitate not implemented group forwarding
+ -- (real client wouldn't have forwarding code, but tests use "current code" with configured version,
+ -- and forwarding client doesn't check compatibility)
+ void $ withCCTransaction alice $ \db ->
+ DB.execute_ db "UPDATE group_member_intros SET intro_status='con'"
+
cath #> "#team hi" -- message is pending for bob
alice <# "#team cath> hi"
alice ##> "\\\\ #team @cath hi"
@@ -1561,6 +1572,13 @@ testGroupDelayedModerationFullDelete tmp = do
cath <## "#team: you joined the group"
]
threadDelay 1000000
+
+ -- imitate not implemented group forwarding
+ -- (real client wouldn't have forwarding code, but tests use "current code" with configured version,
+ -- and forwarding client doesn't check compatibility)
+ void $ withCCTransaction alice $ \db ->
+ DB.execute_ db "UPDATE group_member_intros SET intro_status='con'"
+
cath #> "#team hi" -- message is pending for bob
alice <# "#team cath> hi"
alice ##> "\\\\ #team @cath hi"
@@ -3644,9 +3662,9 @@ testMemberContactProhibitedRepeatInv =
testMemberContactInvitedConnectionReplaced :: HasCallStack => FilePath -> IO ()
testMemberContactInvitedConnectionReplaced tmp = do
- withNewTestChat tmp "alice" aliceProfile $ \a -> withTestOutput a $ \alice -> do
- withNewTestChat tmp "bob" bobProfile $ \b -> withTestOutput b $ \bob -> do
- withNewTestChat tmp "cath" cathProfile $ \c -> withTestOutput c $ \cath -> do
+ withNewTestChat tmp "alice" aliceProfile $ \alice -> do
+ withNewTestChat tmp "bob" bobProfile $ \bob -> do
+ withNewTestChat tmp "cath" cathProfile $ \cath -> do
createGroup3 "team" alice bob cath
alice ##> "/d bob"
@@ -3881,3 +3899,109 @@ testMemberContactProfileUpdate =
cath #> "#team hello there"
alice <# "#team kate> hello there"
bob <# "#team kate> hello there" -- updated profile
+
+testGroupMsgForward :: HasCallStack => FilePath -> IO ()
+testGroupMsgForward =
+ testChatCfg4 cfg aliceProfile bobProfile cathProfile danProfile $
+ \alice bob cath dan -> withXFTPServer $ do
+ createGroup3 "team" alice bob cath
+
+ threadDelay 1000000 -- delay so intro_status doesn't get overwritten to connected
+
+ void $ withCCTransaction bob $ \db ->
+ DB.execute_ db "UPDATE connections SET conn_status='deleted' WHERE group_member_id = 3"
+ void $ withCCTransaction cath $ \db ->
+ DB.execute_ db "UPDATE connections SET conn_status='deleted' WHERE group_member_id = 3"
+ void $ withCCTransaction alice $ \db ->
+ DB.execute_ db "UPDATE group_member_intros SET intro_status='fwd'"
+
+ bob #> "#team hi there"
+ alice <# "#team bob> hi there"
+ cath <# "#team bob> hi there [>>]"
+
+ threadDelay 1000000
+
+ cath #> "#team hey team"
+ alice <# "#team cath> hey team"
+ bob <# "#team cath> hey team [>>]"
+
+ alice ##> "/tail #team 2"
+ alice <# "#team bob> hi there"
+ alice <# "#team cath> hey team"
+
+ bob ##> "/tail #team 2"
+ bob <# "#team hi there"
+ bob <# "#team cath> hey team [>>]"
+
+ cath ##> "/tail #team 2"
+ cath <# "#team bob> hi there [>>]"
+ cath <# "#team hey team"
+
+ bob ##> "! #team hello there"
+ bob <# "#team [edited] hello there"
+ alice <# "#team bob> [edited] hello there"
+ cath <# "#team bob> [edited] hello there" -- TODO show as forwarded
+
+ cath ##> "+1 #team hello there"
+ cath <## "added 👍"
+ alice <# "#team cath> > bob hello there"
+ alice <## " + 👍"
+ bob <# "#team cath> > bob hello there"
+ bob <## " + 👍"
+
+ bob ##> "\\ #team hello there"
+ bob <## "message marked deleted"
+ alice <# "#team bob> [marked deleted] hello there"
+ cath <# "#team bob> [marked deleted] hello there" -- TODO show as forwarded
+
+ bob #> "/f #team ./tests/fixtures/test.jpg"
+ bob <## "use /fc 1 to cancel sending"
+ bob <## "completed uploading file 1 (test.jpg) for #team"
+ concurrentlyN_
+ [ do
+ alice <# "#team bob> sends file test.jpg (136.5 KiB / 139737 bytes)"
+ alice <## "use /fr 1 [/ | ] to receive it",
+ do
+ cath <# "#team bob> sends file test.jpg (136.5 KiB / 139737 bytes) [>>]"
+ cath <## "use /fr 1 [/ | ] to receive it [>>]"
+ ]
+ cath ##> "/fr 1 ./tests/tmp"
+ cath <## "saving file 1 from bob to ./tests/tmp/test.jpg"
+ cath <## "started receiving file 1 (test.jpg) from bob"
+ cath <## "completed receiving file 1 (test.jpg) from bob"
+ src <- B.readFile "./tests/fixtures/test.jpg"
+ dest <- B.readFile "./tests/tmp/test.jpg"
+ dest `shouldBe` src
+
+ cath ##> "/mr #team bob member"
+ cath <## "#team: you changed the role of bob from admin to member"
+ alice <## "#team: cath changed the role of bob from admin to member"
+ bob <## "#team: cath changed your role from admin to member" -- TODO show as forwarded
+
+ connectUsers cath dan
+ cath ##> "/a #team dan"
+ cath <## "invitation to join the group #team sent to dan"
+ dan <## "#team: cath invites you to join the group as member"
+ dan <## "use /j team to accept"
+ dan ##> "/j #team"
+ dan <## "#team: you joined the group"
+ concurrentlyN_
+ [ cath <## "#team: dan joined the group",
+ do
+ alice <## "#team: cath added dan (Daniel) to the group (connecting...)"
+ alice <## "#team: new member dan is connected",
+ -- bob will not connect to dan, as introductions are not forwarded (yet?)
+ bob <## "#team: cath added dan (Daniel) to the group (connecting...)", -- TODO show as forwarded
+ dan <## "#team: member alice (Alice) is connected"
+ ]
+ dan #> "#team hello all"
+ alice <# "#team dan> hello all"
+ -- bob <# "#team dan> hello all [>>]"
+ cath <# "#team dan> hello all"
+
+ bob #> "#team hi all"
+ alice <# "#team bob> hi all"
+ cath <# "#team bob> hi all [>>]"
+ -- dan <# "#team bob> hi all"
+ where
+ cfg = testCfg {xftpFileConfig = Just $ XFTPFileConfig {minFileSize = 0}, tempDir = Just "./tests/tmp"}
diff --git a/tests/ProtocolTests.hs b/tests/ProtocolTests.hs
index f5c1bf8560..884d2b873e 100644
--- a/tests/ProtocolTests.hs
+++ b/tests/ProtocolTests.hs
@@ -122,7 +122,7 @@ decodeChatMessageTest = describe "Chat message encoding/decoding" $ do
"{\"v\":\"1\",\"msgId\":\"AQIDBA==\",\"event\":\"x.msg.new\",\"params\":{\"content\":{\"text\":\"hello\",\"type\":\"text\"}}}"
##==## ChatMessage chatInitialVRange (Just $ SharedMsgId "\1\2\3\4") (XMsgNew (MCSimple (extMsgContent (MCText "hello") Nothing)))
it "x.msg.new chat message with chat version range" $
- "{\"v\":\"1-3\",\"msgId\":\"AQIDBA==\",\"event\":\"x.msg.new\",\"params\":{\"content\":{\"text\":\"hello\",\"type\":\"text\"}}}"
+ "{\"v\":\"1-4\",\"msgId\":\"AQIDBA==\",\"event\":\"x.msg.new\",\"params\":{\"content\":{\"text\":\"hello\",\"type\":\"text\"}}}"
##==## ChatMessage supportedChatVRange (Just $ SharedMsgId "\1\2\3\4") (XMsgNew (MCSimple (extMsgContent (MCText "hello") Nothing)))
it "x.msg.new quote" $
"{\"v\":\"1\",\"msgId\":\"AQIDBA==\",\"event\":\"x.msg.new\",\"params\":{\"content\":{\"text\":\"hello to you too\",\"type\":\"text\"},\"quote\":{\"content\":{\"text\":\"hello there!\",\"type\":\"text\"},\"msgRef\":{\"msgId\":\"BQYHCA==\",\"sent\":true,\"sentAt\":\"1970-01-01T00:00:01.000000001Z\"}}}}"
@@ -232,13 +232,13 @@ decodeChatMessageTest = describe "Chat message encoding/decoding" $ do
"{\"v\":\"1\",\"event\":\"x.grp.mem.new\",\"params\":{\"memberInfo\":{\"memberRole\":\"admin\",\"memberId\":\"AQIDBA==\",\"profile\":{\"fullName\":\"Alice\",\"displayName\":\"alice\",\"image\":\"data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAAgAAAAIAQMAAAD+wSzIAAAABlBMVEX///+/v7+jQ3Y5AAAADklEQVQI12P4AIX8EAgALgAD/aNpbtEAAAAASUVORK5CYII=\",\"preferences\":{\"reactions\":{\"allow\":\"yes\"},\"voice\":{\"allow\":\"yes\"}}}}}}"
#==# XGrpMemNew MemberInfo {memberId = MemberId "\1\2\3\4", memberRole = GRAdmin, v = Nothing, profile = testProfile}
it "x.grp.mem.new with member chat version range" $
- "{\"v\":\"1\",\"event\":\"x.grp.mem.new\",\"params\":{\"memberInfo\":{\"memberRole\":\"admin\",\"memberId\":\"AQIDBA==\",\"v\":\"1-3\",\"profile\":{\"fullName\":\"Alice\",\"displayName\":\"alice\",\"image\":\"data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAAgAAAAIAQMAAAD+wSzIAAAABlBMVEX///+/v7+jQ3Y5AAAADklEQVQI12P4AIX8EAgALgAD/aNpbtEAAAAASUVORK5CYII=\",\"preferences\":{\"reactions\":{\"allow\":\"yes\"},\"voice\":{\"allow\":\"yes\"}}}}}}"
+ "{\"v\":\"1\",\"event\":\"x.grp.mem.new\",\"params\":{\"memberInfo\":{\"memberRole\":\"admin\",\"memberId\":\"AQIDBA==\",\"v\":\"1-4\",\"profile\":{\"fullName\":\"Alice\",\"displayName\":\"alice\",\"image\":\"data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAAgAAAAIAQMAAAD+wSzIAAAABlBMVEX///+/v7+jQ3Y5AAAADklEQVQI12P4AIX8EAgALgAD/aNpbtEAAAAASUVORK5CYII=\",\"preferences\":{\"reactions\":{\"allow\":\"yes\"},\"voice\":{\"allow\":\"yes\"}}}}}}"
#==# XGrpMemNew MemberInfo {memberId = MemberId "\1\2\3\4", memberRole = GRAdmin, v = Just $ ChatVersionRange supportedChatVRange, profile = testProfile}
it "x.grp.mem.intro" $
"{\"v\":\"1\",\"event\":\"x.grp.mem.intro\",\"params\":{\"memberInfo\":{\"memberRole\":\"admin\",\"memberId\":\"AQIDBA==\",\"profile\":{\"fullName\":\"Alice\",\"displayName\":\"alice\",\"image\":\"data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAAgAAAAIAQMAAAD+wSzIAAAABlBMVEX///+/v7+jQ3Y5AAAADklEQVQI12P4AIX8EAgALgAD/aNpbtEAAAAASUVORK5CYII=\",\"preferences\":{\"reactions\":{\"allow\":\"yes\"},\"voice\":{\"allow\":\"yes\"}}}}}}"
#==# XGrpMemIntro MemberInfo {memberId = MemberId "\1\2\3\4", memberRole = GRAdmin, v = Nothing, profile = testProfile}
it "x.grp.mem.intro with member chat version range" $
- "{\"v\":\"1\",\"event\":\"x.grp.mem.intro\",\"params\":{\"memberInfo\":{\"memberRole\":\"admin\",\"memberId\":\"AQIDBA==\",\"v\":\"1-3\",\"profile\":{\"fullName\":\"Alice\",\"displayName\":\"alice\",\"image\":\"data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAAgAAAAIAQMAAAD+wSzIAAAABlBMVEX///+/v7+jQ3Y5AAAADklEQVQI12P4AIX8EAgALgAD/aNpbtEAAAAASUVORK5CYII=\",\"preferences\":{\"reactions\":{\"allow\":\"yes\"},\"voice\":{\"allow\":\"yes\"}}}}}}"
+ "{\"v\":\"1\",\"event\":\"x.grp.mem.intro\",\"params\":{\"memberInfo\":{\"memberRole\":\"admin\",\"memberId\":\"AQIDBA==\",\"v\":\"1-4\",\"profile\":{\"fullName\":\"Alice\",\"displayName\":\"alice\",\"image\":\"data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAAgAAAAIAQMAAAD+wSzIAAAABlBMVEX///+/v7+jQ3Y5AAAADklEQVQI12P4AIX8EAgALgAD/aNpbtEAAAAASUVORK5CYII=\",\"preferences\":{\"reactions\":{\"allow\":\"yes\"},\"voice\":{\"allow\":\"yes\"}}}}}}"
#==# XGrpMemIntro MemberInfo {memberId = MemberId "\1\2\3\4", memberRole = GRAdmin, v = Just $ ChatVersionRange supportedChatVRange, profile = testProfile}
it "x.grp.mem.inv" $
"{\"v\":\"1\",\"event\":\"x.grp.mem.inv\",\"params\":{\"memberId\":\"AQIDBA==\",\"memberIntro\":{\"directConnReq\":\"simplex:/invitation#/?v=1&smp=smp%3A%2F%2F1234-w%3D%3D%40smp.simplex.im%3A5223%2F3456-w%3D%3D%23%2F%3Fv%3D1-2%26dh%3DMCowBQYDK2VuAyEAjiswwI3O_NlS8Fk3HJUW870EY2bAwmttMBsvRB9eV3o%253D&e2e=v%3D1-2%26x3dh%3DMEIwBQYDK2VvAzkAmKuSYeQ_m0SixPDS8Wq8VBaTS1cW-Lp0n0h4Diu-kUpR-qXx4SDJ32YGEFoGFGSbGPry5Ychr6U%3D%2CMEIwBQYDK2VvAzkAmKuSYeQ_m0SixPDS8Wq8VBaTS1cW-Lp0n0h4Diu-kUpR-qXx4SDJ32YGEFoGFGSbGPry5Ychr6U%3D\",\"groupConnReq\":\"simplex:/invitation#/?v=1&smp=smp%3A%2F%2F1234-w%3D%3D%40smp.simplex.im%3A5223%2F3456-w%3D%3D%23%2F%3Fv%3D1-2%26dh%3DMCowBQYDK2VuAyEAjiswwI3O_NlS8Fk3HJUW870EY2bAwmttMBsvRB9eV3o%253D&e2e=v%3D1-2%26x3dh%3DMEIwBQYDK2VvAzkAmKuSYeQ_m0SixPDS8Wq8VBaTS1cW-Lp0n0h4Diu-kUpR-qXx4SDJ32YGEFoGFGSbGPry5Ychr6U%3D%2CMEIwBQYDK2VvAzkAmKuSYeQ_m0SixPDS8Wq8VBaTS1cW-Lp0n0h4Diu-kUpR-qXx4SDJ32YGEFoGFGSbGPry5Ychr6U%3D\"}}}"
@@ -250,7 +250,7 @@ decodeChatMessageTest = describe "Chat message encoding/decoding" $ do
"{\"v\":\"1\",\"event\":\"x.grp.mem.fwd\",\"params\":{\"memberIntro\":{\"directConnReq\":\"simplex:/invitation#/?v=1&smp=smp%3A%2F%2F1234-w%3D%3D%40smp.simplex.im%3A5223%2F3456-w%3D%3D%23%2F%3Fv%3D1-2%26dh%3DMCowBQYDK2VuAyEAjiswwI3O_NlS8Fk3HJUW870EY2bAwmttMBsvRB9eV3o%253D&e2e=v%3D1-2%26x3dh%3DMEIwBQYDK2VvAzkAmKuSYeQ_m0SixPDS8Wq8VBaTS1cW-Lp0n0h4Diu-kUpR-qXx4SDJ32YGEFoGFGSbGPry5Ychr6U%3D%2CMEIwBQYDK2VvAzkAmKuSYeQ_m0SixPDS8Wq8VBaTS1cW-Lp0n0h4Diu-kUpR-qXx4SDJ32YGEFoGFGSbGPry5Ychr6U%3D\",\"groupConnReq\":\"simplex:/invitation#/?v=1&smp=smp%3A%2F%2F1234-w%3D%3D%40smp.simplex.im%3A5223%2F3456-w%3D%3D%23%2F%3Fv%3D1-2%26dh%3DMCowBQYDK2VuAyEAjiswwI3O_NlS8Fk3HJUW870EY2bAwmttMBsvRB9eV3o%253D&e2e=v%3D1-2%26x3dh%3DMEIwBQYDK2VvAzkAmKuSYeQ_m0SixPDS8Wq8VBaTS1cW-Lp0n0h4Diu-kUpR-qXx4SDJ32YGEFoGFGSbGPry5Ychr6U%3D%2CMEIwBQYDK2VvAzkAmKuSYeQ_m0SixPDS8Wq8VBaTS1cW-Lp0n0h4Diu-kUpR-qXx4SDJ32YGEFoGFGSbGPry5Ychr6U%3D\"},\"memberInfo\":{\"memberRole\":\"admin\",\"memberId\":\"AQIDBA==\",\"profile\":{\"fullName\":\"Alice\",\"displayName\":\"alice\",\"image\":\"data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAAgAAAAIAQMAAAD+wSzIAAAABlBMVEX///+/v7+jQ3Y5AAAADklEQVQI12P4AIX8EAgALgAD/aNpbtEAAAAASUVORK5CYII=\",\"preferences\":{\"reactions\":{\"allow\":\"yes\"},\"voice\":{\"allow\":\"yes\"}}}}}}"
#==# XGrpMemFwd MemberInfo {memberId = MemberId "\1\2\3\4", memberRole = GRAdmin, v = Nothing, profile = testProfile} IntroInvitation {groupConnReq = testConnReq, directConnReq = Just testConnReq}
it "x.grp.mem.fwd with member chat version range and w/t directConnReq" $
- "{\"v\":\"1\",\"event\":\"x.grp.mem.fwd\",\"params\":{\"memberIntro\":{\"groupConnReq\":\"simplex:/invitation#/?v=1&smp=smp%3A%2F%2F1234-w%3D%3D%40smp.simplex.im%3A5223%2F3456-w%3D%3D%23%2F%3Fv%3D1-2%26dh%3DMCowBQYDK2VuAyEAjiswwI3O_NlS8Fk3HJUW870EY2bAwmttMBsvRB9eV3o%253D&e2e=v%3D1-2%26x3dh%3DMEIwBQYDK2VvAzkAmKuSYeQ_m0SixPDS8Wq8VBaTS1cW-Lp0n0h4Diu-kUpR-qXx4SDJ32YGEFoGFGSbGPry5Ychr6U%3D%2CMEIwBQYDK2VvAzkAmKuSYeQ_m0SixPDS8Wq8VBaTS1cW-Lp0n0h4Diu-kUpR-qXx4SDJ32YGEFoGFGSbGPry5Ychr6U%3D\"},\"memberInfo\":{\"memberRole\":\"admin\",\"memberId\":\"AQIDBA==\",\"v\":\"1-3\",\"profile\":{\"fullName\":\"Alice\",\"displayName\":\"alice\",\"image\":\"data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAAgAAAAIAQMAAAD+wSzIAAAABlBMVEX///+/v7+jQ3Y5AAAADklEQVQI12P4AIX8EAgALgAD/aNpbtEAAAAASUVORK5CYII=\",\"preferences\":{\"reactions\":{\"allow\":\"yes\"},\"voice\":{\"allow\":\"yes\"}}}}}}"
+ "{\"v\":\"1\",\"event\":\"x.grp.mem.fwd\",\"params\":{\"memberIntro\":{\"groupConnReq\":\"simplex:/invitation#/?v=1&smp=smp%3A%2F%2F1234-w%3D%3D%40smp.simplex.im%3A5223%2F3456-w%3D%3D%23%2F%3Fv%3D1-2%26dh%3DMCowBQYDK2VuAyEAjiswwI3O_NlS8Fk3HJUW870EY2bAwmttMBsvRB9eV3o%253D&e2e=v%3D1-2%26x3dh%3DMEIwBQYDK2VvAzkAmKuSYeQ_m0SixPDS8Wq8VBaTS1cW-Lp0n0h4Diu-kUpR-qXx4SDJ32YGEFoGFGSbGPry5Ychr6U%3D%2CMEIwBQYDK2VvAzkAmKuSYeQ_m0SixPDS8Wq8VBaTS1cW-Lp0n0h4Diu-kUpR-qXx4SDJ32YGEFoGFGSbGPry5Ychr6U%3D\"},\"memberInfo\":{\"memberRole\":\"admin\",\"memberId\":\"AQIDBA==\",\"v\":\"1-4\",\"profile\":{\"fullName\":\"Alice\",\"displayName\":\"alice\",\"image\":\"data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAAgAAAAIAQMAAAD+wSzIAAAABlBMVEX///+/v7+jQ3Y5AAAADklEQVQI12P4AIX8EAgALgAD/aNpbtEAAAAASUVORK5CYII=\",\"preferences\":{\"reactions\":{\"allow\":\"yes\"},\"voice\":{\"allow\":\"yes\"}}}}}}"
#==# XGrpMemFwd MemberInfo {memberId = MemberId "\1\2\3\4", memberRole = GRAdmin, v = Just $ ChatVersionRange supportedChatVRange, profile = testProfile} IntroInvitation {groupConnReq = testConnReq, directConnReq = Nothing}
it "x.grp.mem.info" $
"{\"v\":\"1\",\"event\":\"x.grp.mem.info\",\"params\":{\"memberId\":\"AQIDBA==\",\"profile\":{\"fullName\":\"Alice\",\"displayName\":\"alice\",\"image\":\"data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAAgAAAAIAQMAAAD+wSzIAAAABlBMVEX///+/v7+jQ3Y5AAAADklEQVQI12P4AIX8EAgALgAD/aNpbtEAAAAASUVORK5CYII=\",\"preferences\":{\"reactions\":{\"allow\":\"yes\"},\"voice\":{\"allow\":\"yes\"}}}}}"
@@ -276,6 +276,12 @@ decodeChatMessageTest = describe "Chat message encoding/decoding" $ do
it "x.grp.direct.inv without content" $
"{\"v\":\"1\",\"event\":\"x.grp.direct.inv\",\"params\":{\"connReq\":\"simplex:/invitation#/?v=1&smp=smp%3A%2F%2F1234-w%3D%3D%40smp.simplex.im%3A5223%2F3456-w%3D%3D%23%2F%3Fv%3D1-2%26dh%3DMCowBQYDK2VuAyEAjiswwI3O_NlS8Fk3HJUW870EY2bAwmttMBsvRB9eV3o%253D&e2e=v%3D1-2%26x3dh%3DMEIwBQYDK2VvAzkAmKuSYeQ_m0SixPDS8Wq8VBaTS1cW-Lp0n0h4Diu-kUpR-qXx4SDJ32YGEFoGFGSbGPry5Ychr6U%3D%2CMEIwBQYDK2VvAzkAmKuSYeQ_m0SixPDS8Wq8VBaTS1cW-Lp0n0h4Diu-kUpR-qXx4SDJ32YGEFoGFGSbGPry5Ychr6U%3D\"}}"
#==# XGrpDirectInv testConnReq Nothing
+ -- it "x.grp.msg.forward"
+ -- $ "{\"v\":\"1\",\"event\":\"x.grp.msg.forward\",\"params\":{\"msgForward\":{\"memberId\":\"AQIDBA==\",\"msg\":\"{\"v\":\"1\",\"event\":\"x.msg.new\",\"params\":{\"content\":{\"text\":\"hello\",\"type\":\"text\"}}}\",\"msgTs\":\"1970-01-01T00:00:01.000000001Z\"}}}"
+ -- #==# XGrpMsgForward
+ -- (MemberId "\1\2\3\4")
+ -- (ChatMessage chatInitialVRange (Just $ SharedMsgId "\1\2\3\4") (XMsgNew (MCSimple (extMsgContent (MCText "hello") Nothing))))
+ -- (systemToUTCTime $ MkSystemTime 1 1)
it "x.info.probe" $
"{\"v\":\"1\",\"event\":\"x.info.probe\",\"params\":{\"probe\":\"AQIDBA==\"}}"
#==# XInfoProbe (Probe "\1\2\3\4")
diff --git a/website/langs/fr.json b/website/langs/fr.json
index b4d0ea17af..304630a091 100644
--- a/website/langs/fr.json
+++ b/website/langs/fr.json
@@ -35,12 +35,12 @@
"hero-overlay-1-title": "Comment fonctionne SimpleX ?",
"hero-overlay-2-title": "Pourquoi les IDs d'utilisateur sont nuisibles pour votre vie privée ?",
"feature-1-title": "Messages chiffrés E2E avec édition et format markdown",
- "feature-2-title": "Images et fichiers chiffrés E2E",
- "feature-3-title": "Groupes secrets décentralisés — seuls ses membres connaissent leur existence",
+ "feature-2-title": "Images, vidéos et fichiers chiffrés E2E",
+ "feature-3-title": "Groupes chiffrés E2E décentralisés — où seuls les membres en connaissent l'existence",
"feature-4-title": "Messages vocaux chiffrés E2E",
"feature-5-title": "Conversations secrètes éphémères",
"feature-6-title": "Appels audio et vidéo chiffrés E2E",
- "feature-7-title": "Base de données chiffrée portable — déplacez votre profil vers un autre appareil",
+ "feature-7-title": "Stockage de l'app portable chiffré — déplacez votre profil vers un autre appareil",
"feature-8-title": "Mode incognito — unique à SimpleX Chat",
"simplex-network-overlay-1-title": "Comparaison avec des protocoles de messagerie P2P",
"simplex-private-1-title": "2 couches de chiffrement de bout en bout",
diff --git a/website/langs/it.json b/website/langs/it.json
index b8a83aee52..1f3ad7fdda 100644
--- a/website/langs/it.json
+++ b/website/langs/it.json
@@ -154,12 +154,12 @@
"home": "Home",
"developers": "Sviluppatori",
"simplex-explained-tab-1-p-1": "Puoi creare contatti e gruppi, ed avere conversazioni bidirezionali, come in qualsiasi altro messenger.",
- "feature-2-title": "Immagini e file crittografati E2E",
+ "feature-2-title": "Immagini, video e file crittografati E2E",
"simplex-private-card-4-point-2": "Per usare SimpleX tramite Tor, installa l'app Orbot e attiva il proxy SOCKS5 (o VPN su iOS ).",
"reference": "Riferimenti",
"simplex-explained-tab-3-text": "3. Cosa vedono i server",
"chat-bot-example": "Esempio di chat bot",
- "feature-3-title": "Gruppi segreti decentralizzati — solo gli utenti sanno che esistono",
+ "feature-3-title": "Gruppi decentralizzati crittografati E2E — solo gli utenti sanno che esistono",
"blog": "Blog",
"simplex-explained-tab-2-p-1": "Per ogni connessione usi due code di messaggi distinte per inviare e ricevere i messaggi attraverso server diversi.",
"simplex-explained-tab-2-p-2": "I server passano i messaggi solo in una direzione, senza avere il quadro completo della conversazione dell'utente o delle connessioni.",
@@ -169,7 +169,7 @@
"feature-5-title": "Conversazioni segrete a tempo",
"feature-6-title": "Chiamate audio e video crittografate E2E",
"simplex-private-6-title": "Scambio di chiavi fuori banda",
- "feature-7-title": "Database crittografato trasferibile — sposta il tuo profilo su un altro dispositivo",
+ "feature-7-title": "Archiviazione dell'app crittografata e trasferibile — sposta il profilo su un altro dispositivo",
"simplex-private-card-1-point-2": "Cryptobox NaCL in ogni coda per evitare correlazioni di traffico tra code di messaggi se il TLS è compromesso.",
"simplex-private-card-3-point-3": "Ripresa della connessione disattivata per evitare attacchi alla sessione.",
"hero-overlay-card-1-p-4": "Questo design impedisce la fuoriuscita di metadati degli utenti a livello di applicazione. Per aumentare ulteriormente la privacy e proteggere il tuo indirizzo IP puoi connetterti ai server di messaggistica tramite Tor.",
diff --git a/website/langs/nl.json b/website/langs/nl.json
index 0cb3428563..51c5855b51 100644
--- a/website/langs/nl.json
+++ b/website/langs/nl.json
@@ -28,8 +28,8 @@
"hero-overlay-1-title": "Hoe werkt SimpleX?",
"hero-overlay-2-title": "Waarom zijn gebruikers-ID's slecht voor de privacy?",
"feature-1-title": "E2E-versleutelde berichten met markdown en bewerking",
- "feature-2-title": "E2E-versleutelde afbeeldingen en bestanden",
- "feature-3-title": "Gedecentraliseerde geheime groepen - alleen gebruikers weten dat ze bestaan",
+ "feature-2-title": "E2E-versleutelde afbeeldingen,videos en bestanden",
+ "feature-3-title": "E2E-gecodeerde gedecentraliseerde groepen - alleen gebruikers weten dat ze bestaan",
"feature-4-title": "E2E-versleutelde spraakberichten",
"feature-5-title": "Verdwijnende geheime gesprekken",
"feature-6-title": "E2E-versleutelde audio- en videogesprekken",
@@ -60,7 +60,7 @@
"hero-p-1": "Andere apps hebben gebruikers-ID's: Signal, Matrix, Session, Briar, Jami, Cwtch, enz. SimpleX niet, zelfs geen willekeurige getallen . Dit verbetert uw privacy.",
"hero-2-header-desc": "De video laat zien hoe je verbinding maakt met een vriend via een persoonlijk of videolink gedeelde eenmalige QR-code. U kunt ook verbinding maken door een uitnodigingslink te delen.",
"hero-header": "Privacy opnieuw gedefinieerd",
- "feature-7-title": "Portable versleutelde database — verplaats je profiel naar een ander apparaat",
+ "feature-7-title": "Draagbare gecodeerde app-opslag - profiel naar een ander apparaat verplaatsen",
"simplex-private-card-1-point-1": "Protocol met double-ratchet - OTR-berichten met perfecte voorwaartse geheimhouding en inbraak herstel.",
"simplex-private-6-title": "Out-of-band sleuteluitwisseling",
"simplex-private-7-title": "Bericht integriteit verificatie",
diff --git a/website/langs/pl.json b/website/langs/pl.json
index f9b9936848..ba0c72da7e 100644
--- a/website/langs/pl.json
+++ b/website/langs/pl.json
@@ -27,11 +27,11 @@
"hero-overlay-1-title": "Jak działa SimpleX?",
"hero-overlay-2-title": "Dlaczego identyfikatory użytkowników (ID) są złe dla prywatności?",
"feature-1-title": "Wiadomości szyfrowane przez E2E ze składnią markdown i edycją",
- "feature-3-title": "Zdecentralizowane tajne grupy — tylko użytkownicy wiedzą, że te grupy istnieją",
+ "feature-3-title": "Zaszyfrowane E2E zdecentralizowane grupy — tylko użytkownicy wiedzą, że te grupy istnieją",
"feature-4-title": "Wiadomości głosowe zaszyfrowane przez E2E",
"feature-5-title": "Znikające wiadomości",
"feature-6-title": "Połączenia audio i wideo szyfrowane przez E2E",
- "feature-7-title": "Przenośna zaszyfrowana baza danych — przenieś swój profil na inne urządzenie",
+ "feature-7-title": "Przenośna zaszyfrowana pamięć aplikacji — przenieś profil na inne urządzenie",
"simplex-private-4-title": "Możliwy dostęp za pośrednictwem sieci Tor",
"simplex-private-5-title": "Wielowarstwowy padding treści",
"simplex-private-6-title": "Wymiana kluczy out-of-band",
@@ -49,7 +49,7 @@
"simplex-explained-tab-2-p-2": "Serwery przekazują wiadomości tylko w jedną stronę, nie mając pełnego obrazu konwersacji i połączeń użytkownika.",
"simplex-explained-tab-3-p-2": "Użytkownicy mogą jeszcze bardziej zwiększyć prywatność metadanych, używając Tor do uzyskania dostępu do serwerów, co zapobiega korelacji na podstawie adresu IP.",
"hero-p-1": "Inne aplikacje mają identyfikatory użytkowników (ID): Signal, Matrix, Session, Briar, Jami, Cwtch itp. SimpleX nie, nie ma nawet losowych numerów . To radykalnie poprawia Twoją prywatność.",
- "feature-2-title": "Obrazy i pliki zaszyfrowane przez E2E",
+ "feature-2-title": "Obrazy, wideo i pliki zaszyfrowane przez E2E",
"feature-8-title": "Tryb incognito — unikalny dla SimpleX Chat",
"simplex-network-overlay-1-title": "Porównanie z protokołami komunikacyjnymi P2P",
"simplex-private-1-title": "Dwie warstwy szyfrowania typu end-to-end",
@@ -243,5 +243,12 @@
"f-droid-org-repo": "Repo F-Droid.org",
"stable-versions-built-by-f-droid-org": "Wersje stabilne zbudowane przez F-Droid.org",
"releases-to-this-repo-are-done-1-2-days-later": "Wydania na tym repo są 1-2 dni później",
- "comparison-section-list-point-4a": "Przekaźniki SimpleX nie mogą skompromitować szyfrowania e2e. Zweryfikuj kody bezpieczeństwa aby złagodzić atak na kanał pozapasmowy"
+ "comparison-section-list-point-4a": "Przekaźniki SimpleX nie mogą skompromitować szyfrowania e2e. Zweryfikuj kody bezpieczeństwa aby złagodzić atak na kanał pozapasmowy",
+ "hero-overlay-3-title": "Ocena bezpieczeństwa",
+ "hero-overlay-card-3-p-2": "Trail of Bits przejrzał komponenty kryptograficzne i sieciowe platformy SimpleX w listopadzie 2022.",
+ "hero-overlay-card-3-p-3": "Przeczytaj więcej w ogłoszeniach .",
+ "jobs": "Dołącz do zespołu",
+ "hero-overlay-3-textlink": "Ocena bezpieczeństwa",
+ "hero-overlay-card-3-p-1": "Trail of Bits jest wiodącą firmą konsultingową w zakresie bezpieczeństwa i technologii, której klientami są duże firmy technologiczne, agencje rządowe i główne projekty blockchain.",
+ "docs-dropdown-9": "Pliki do pobrania"
}
diff --git a/website/langs/ru.json b/website/langs/ru.json
index 0967ef424b..73fba95480 100644
--- a/website/langs/ru.json
+++ b/website/langs/ru.json
@@ -1 +1,254 @@
-{}
+{
+ "copy-the-command-below-text": "скопируйте приведенную ниже команду и используйте ее в чате:",
+ "copyright-label": "© 2020-2023 SimpleX | Проект с открытым исходным кодом",
+ "chat-bot-example": "Пример Чат бота",
+ "simplex-private-card-9-point-1": "Каждая очередь сообщений передает сообщения в одном направлении с разными адресами отправки и получения.",
+ "simplex-private-card-1-point-2": "Криптобокс NaCL в каждой очереди для предотвращения корреляции трафика между очередями сообщений, в случае компрометации TLS.",
+ "contact-hero-p-1": "Открытые ключи и адрес очереди сообщений по этой ссылке НЕ отправляются по сети при просмотре этой страницы — они содержатся в хэш-фрагменте URL-адреса ссылки.",
+ "guide-dropdown-5": "Управление данными",
+ "scan-the-qr-code-with-the-simplex-chat-app": "Отсканируйте QR-код с помощью приложения SimpleX Chat",
+ "simplex-private-card-9-point-2": "Это уменьшает векторы атак и доступные метаданные, по сравнению с традиционными посредниками для доставки сообщений.",
+ "simplex-unique-card-3-p-2": "Сквозные зашифрованные сообщения временно хранятся на серверах SimpleX до получения, после чего они удаляются безвозвратно.",
+ "feature-7-title": "Портативное, зашифрованное хранилище в приложении — можно перенести профиль на другое устройство",
+ "no-federated": "Нет - федеративный",
+ "hero-2-header": "Как начать общаться приватно",
+ "simplex-unique-overlay-card-3-p-3": "В отличие от серверов федеративных сетей (электронной почты, XMPP или Matrix), серверы SimpleX не хранят учетные записи пользователей, они только ретранслируют сообщения, защищая конфиденциальность обеих сторон.",
+ "hero-subheader": "Первый мессенджер не нуждающийся в идентификаторах пользователя",
+ "privacy-matters-overlay-card-3-p-2": "Одна из самых шокирующих историй - это опыт Слахи, Мохаммеда Ульда , описанный в его мемуарах и показанный в фильме Мавританец . Он был помещен в лагерь Гуантанамо без суда и следствия и подвергался там пыткам в течение 15 лет после телефонного звонка своему родственнику в Афганистан под подозревается в причастности к терактам 11 сентября, хотя предыдущие 10 лет он жил в Германии.",
+ "signing-key-fingerprint": "Отпечаток ключа подписи (SHA-256)",
+ "simplex-network-2-desc": "Серверные узлы SimpleX НЕ хранят профили пользователей, контакты и доставленные сообщения, НЕ подключаются друг к другу, и НЕ имеют каталога серверов.",
+ "simplex-privacy": "Конфиденциальность SimpleX",
+ "docs-dropdown-5": "Свой XFTP Сервер",
+ "simplex-private-card-3-point-2": "Отпечаток сервера и привязка канала предотвращают MITM атаки и Атаки повторного воспроизведения.",
+ "docs-dropdown-3": "Доступ к в базе данных чата",
+ "installing-simplex-chat-to-terminal": "Установка SimpleX Chat в терминале",
+ "use-this-command": "Используйте эту команду:",
+ "simplex-explained": "Простое объяснение вкратце",
+ "to-make-a-connection": "Чтобы установить соединение:",
+ "comparison-section-list-point-6": "Хотя P2P распределены, они не являются федеративными, то есть P2P - работают как единая сеть",
+ "hero-overlay-2-textlink": "Как работает SimpleX?",
+ "simplex-chat-via-f-droid": "SimpleX Chat в F-Droid",
+ "privacy-matters-overlay-card-1-p-1": "Многие крупные компании используют информацию о том, с кем вы связаны, чтобы оценить ваш доход, продавать вам больше товаров, которые вам на самом деле не нужны, и определять из этой информации, выгодные для них цены.",
+ "privacy-matters-1-overlay-1-title": "Конфиденциальность экономит ваши деньги",
+ "simplex-private-card-5-point-1": "SimpleX использует заполнение содержимого для каждого уровня шифрования, чтобы предотвратить атаки на размер сообщения.",
+ "privacy-matters-2-overlay-1-linkText": "Конфиденциальность дает вам власть",
+ "hero-overlay-3-title": "Оценка безопасности",
+ "enter-your-email-address": "Email адрес",
+ "simplex-explained-tab-1-text": "1. Как это видят пользователи",
+ "tap-to-close": "Нажмите, чтобы закрыть",
+ "simplex-unique-card-4-p-2": "Вы можете использовать SimpleX со своими собственными серверами или с серверами, предоставленными нами — и при этом подключаться к любому пользователю SimpleX.",
+ "hero-overlay-card-3-p-2": "В ноябре 2022 года Trail of Bits провела обзор криптографии и сетевых компонентов SimpleX.",
+ "feature-1-title": "Сообщения зашифрованные E2E-шифрованием с поддержкой markdown и редактированием",
+ "comparison-point-4-text": "Единая или Централизованная сеть",
+ "guide-dropdown-9": "Установление соединений",
+ "simplex-unique-1-overlay-1-title": "Полная конфиденциальность вашей личности, профиля, контактов и других метаданных",
+ "hero-overlay-card-2-p-4": "SimpleX защищает от этих атак, поскольку в его конструкции нет никаких идентификаторов пользователей. И, если вы используете режим инкогнито, у вас будет другое отображаемое имя для каждого контакта, что позволит избежать какого-либо пересечения между ними.",
+ "privacy-matters-overlay-card-2-p-2": "Чтобы быть объективным и принимать независимые решения, вам необходимо контролировать свое информационное пространство. Это возможно только в том случае, если вы используете приватную, коммуникационную платформу, которая не имеет доступа к вашему социальному графу.",
+ "hero-overlay-card-2-p-1": "Когда у пользователей есть постоянные идентификаторы, даже если это просто случайное число, например идентификатор сеанса, существует риск того, что провайдер или злоумышленник могут наблюдайте за тем, как подключены пользователи и сколько сообщений они отправляют.",
+ "feature-3-title": "Децентрализованные группы — только их участники знают, что они существуют",
+ "glossary": "Глоссарий",
+ "simplex-network-overlay-1-title": "Сравнение с протоколами обмена сообщениями P2P",
+ "comparison-section-list-point-7": "Сети P2P либо имеют центральный орган управления, либо вся сеть может быть скомпрометирована",
+ "simplex-unique-overlay-card-4-p-1": "Вы можете использовать SimpleX со своими собственными серверами или предоставленными нами серверами, при этом имея возможность общаться с любым пользователем.",
+ "simplex-explained-tab-3-p-1": "Серверы имеют отдельные, Анонимные учётные данные для каждой очереди и не знают, к каким пользователям они принадлежат.",
+ "docs-dropdown-1": "Платформа SimpleX",
+ "hero-overlay-card-1-p-5": "Только клиентские устройства хранят профили пользователей, контакты и группы; сообщения отправляются с двухуровневым, Сквозным шифрованием.",
+ "simplex-chat-for-the-terminal": "SimpleX Chat для терминала",
+ "simplex-network-overlay-card-1-li-3": "P2P не решает проблему MITM-атаки (Атака посредника) , и большинство существующих реализаций не используют внеполосные сообщения для первоначального обмена ключами. SimpleX использует внеполосные сообщения или, в некоторых случаях, ранее существовавшие защищенные и доверенные соединения для первоначального обмена ключами.",
+ "the-instructions--source-code": "SimpleX Chat.",
+ "simplex-network-section-desc": "SimpleX Chat обеспечивает наилучшую конфиденциальность, сочетая преимущества P2P и федеративных сетей.",
+ "privacy-matters-section-subheader": "Сохранение конфиденциальности ваших метаданных — с кем вы общаетесь — защищает вас от:",
+ "if-you-already-installed": "Если вы уже установили",
+ "simplex-explained-tab-3-p-2": "Пользователи могут еще больше повысить свою конфиденциальность скрыв свой IP-адрес, например используя сеть Tor для доступа к серверам.",
+ "join": "Присоединяйся к",
+ "privacy-matters-section-header": "Почему приватность важна ",
+ "hero-overlay-1-textlink": "Почему идентификаторы пользователя - вредны для приватности?",
+ "on-this-page": "На этой странице",
+ "privacy-matters-overlay-card-1-p-2": "Интернет-магазины знают, что люди с более низкими доходами с большей вероятностью совершают срочные покупки, поэтому они могут устанавливать более высокие цены или отменять скидки.",
+ "simplex-unique-3-overlay-1-title": "Контроль и безопасность ваших данных",
+ "protocol-3-text": "Протоколы P2P",
+ "simplex-private-card-6-point-2": "Чтобы предотвратить это, приложения SimpleX передают одноразовые ключи внеполосно, когда вы делитесь адресом в виде ссылки или QR-кода.",
+ "no": "Нет",
+ "contact-hero-header": "Вы получили адрес для подключения в SimpleX Chat",
+ "feature-8-title": "Режим инкогнито — уникальный для SimpleX Chat",
+ "why-simplex": "Что делает SimpleX уникальным",
+ "simplex-private-card-4-point-2": "Чтобы использовать SimpleX через сеть Tor, пожалуйста, установите приложение Orbot и включите прокси (или режим VPN на iOS ).",
+ "contact-hero-subheader": "Отсканируйте QR-код с помощью приложения SimpleX Chat на вашем телефоне или планшете.",
+ "simplex-unique-2-overlay-1-title": "Лучшая защита от спама и злоупотреблений",
+ "simplex-private-6-title": "Внеполосный Обмен ключами",
+ "join-us-on-GitHub": "Присоединяйтесь к нам на GitHub",
+ "hero-overlay-card-3-p-3": "Подробнее читайте в анонсе .",
+ "comparison-section-header": "Сравнение с другими протоколами",
+ "invitation-hero-header": "Вы получили одноразовую ссылку для подключения в SimpleX Chat",
+ "no-secure": "Нет - безопасно",
+ "hero-overlay-card-1-p-2": "Для доставки сообщений вместо идентификаторов пользователей, используемых всеми другими платформами, SimpleX использует временные, анонимные, попарные идентификаторы очередей сообщений, отдельно для каждого из ваших контактов — нет долгосрочных идентификаторов.",
+ "simplex-explained-tab-1-p-2": "Как это может работать с однонаправленными очередями и без идентификаторов профиля пользователя?",
+ "simplex-network-1-header": "В отличие от P2P-сетей",
+ "jobs": "Присоединиться к команде",
+ "simplex-private-card-7-point-2": "Если какое-либо сообщение будет добавлено, удалено или изменено, получатель будет предупрежден об этом.",
+ "simplex-unique-3-title": "Только вы контролируете свои данные",
+ "guide-dropdown-3": "Секретные группы",
+ "no-resilient": "Нет - устойчив",
+ "hide-info": "Спрятать информацию",
+ "privacy-matters-overlay-card-3-p-4": "Недостаточно просто использовать мессенджер со сквозным шифрованием, мы все должны использовать мессенджеры, которые защищают конфиденциальность наших личных сетей — с какими людьми мы связаны.",
+ "releases-to-this-repo-are-done-1-2-days-later": "Выпуск новых версий в этом репозитории выходит с задержкой в 1-2 дня",
+ "comparison-point-1-text": "Требуется глобальный идентификатор",
+ "comparison-section-list-point-5": "Не защищает конфиденциальность пользовательских метаданных",
+ "hero-overlay-card-2-p-2": "Затем они могли бы сопоставить эту информацию с существующими общедоступными социальными сетями и определить некоторые реальные личности.",
+ "privacy-matters-overlay-card-1-p-3": "Некоторые финансовые и страховые компании используют социальные графики для определения процентных ставок и премий. Это часто заставляет людей с более низкими доходами платить больше — это известно как \"премия за бедность\" .",
+ "comparison-point-3-text": "Зависимость от DNS",
+ "yes": "Да",
+ "docs-dropdown-6": "Сервера WebRTC",
+ "newer-version-of-eng-msg": "Существует более новая версия этой страницы на английском языке.",
+ "install-simplex-app": "Установите приложение SimpleX",
+ "hero-overlay-3-textlink": "Оценка безопасности",
+ "comparison-point-2-text": "Возможность MITM",
+ "scan-the-qr-code-with-the-simplex-chat-app-description": "Открытые ключи и адрес очереди сообщений, указанные в этой ссылке, НЕ отправляются по сети при просмотре этой страницы — они содержатся в хэш-фрагменте URL-адреса ссылки.",
+ "guide-dropdown-8": "Настройки приложения",
+ "simplex-explained-tab-2-p-2": "Серверы передают сообщения только в одну сторону, не имея полной картины общения пользователя или подключений.",
+ "smp-protocol": "Протокол SMP",
+ "open-simplex-app": "Откройте приложение SimpleX",
+ "see-simplex-chat": "Инструкции по загрузке или компиляции SimpleX Chat из исходного кода приведены в",
+ "terminal-cli": "Приложение для терминала (CLI)",
+ "comparison-section-list-point-1": "Обычно требуется номера телефона, в некоторых случаях — имя пользователя",
+ "simplex-explained-tab-3-text": "3. Что видят сервера",
+ "github-repository": "репозиторий Github",
+ "feature-5-title": "Исчезающие сообщения",
+ "connect-in-app": "Подключитесь в приложении",
+ "menu": "Меню",
+ "simplex-private-card-4-point-1": "Чтобы защитить свой IP-адрес вы можете подключаться к серверам через сеть Tor или какую-либо другую транспортную оверлейную сеть.",
+ "privacy-matters-3-title": "Судебное преследование не виновных",
+ "comparison-point-5-text": "Атака на центральный компонент или другая сетевая атака",
+ "click-to-see": "Нажать здесь, чтобы увидеть",
+ "donate-here-to-help-us": "Пожертвуйте здесь, чтобы помочь нам",
+ "simplex-private-1-title": "2-уровневое сквозное шифрование",
+ "simplex-unique-card-1-p-2": "В отличие от любой другой существующей платформы обмена сообщениями, SimpleX не имеет идентификаторов пользователей — нету даже случайных цифр .",
+ "privacy-matters-2-overlay-1-title": "Конфиденциальность дает вам власть",
+ "simplex-unique-overlay-card-2-p-2": "Хоть злоумышленники и могут использовать постоянный адрес для отправки нежелательных запросов или спама, вы можете легко его изменить или просто удалить, не теряя связи с уже установленными контактами.",
+ "simplex-unique-4-overlay-1-title": "Полностью децентрализованная — пользователи владеют сетью SimpleX",
+ "guide-dropdown-2": "Отправка сообщений",
+ "simplex-network-overlay-card-1-li-5": "Все известные P2P-сети могут быть уязвимы для Атаки Сивиллы , поскольку каждый узел доступен для обнаружения, и сеть работает как единое целое. Известные меры по его смягчению требуют либо централизованного компонента, либо дорогостоящего Proof-of-work . Сеть SimpleX не имеет функционала по обмену серверами, она фрагментирована и работает как множество изолированных подсетей, из-за чего провести атаку по всей сети - невозможно.",
+ "simplex-private-2-title": "Дополнительный уровень шифрования сервера",
+ "hero-overlay-card-1-p-4": "Такая конструкция предотвращает утечку любых пользовательских метаданных на уровне приложения. Для дальнейшего улучшения конфиденциальности и защиты вашего IP-адреса вы можете подключиться к серверам обмена сообщениями через сеть Tor.",
+ "f-droid-org-repo": "Репозиторий F-Droid.org",
+ "guide-dropdown-4": "Профили чата",
+ "simplex-network-2-header": "В отличие от федеративных сетей",
+ "see-here": "подробней тут",
+ "simplex-private-3-title": "Безопасный аутентифицированный протокол TLS",
+ "comparison-section-list-point-3": "Открытый ключ или какой-либо другой глобально уникальный идентификатор",
+ "hero-overlay-card-2-p-3": "Даже в самых приватных приложениях, использующих скрытые сервисы Tor v3, если вы общаетесь с двумя разными контактами через один и тот же профиль, они могут доказать, что они являются связаны с одним и тем же человеком.",
+ "simplex-private-4-title": "Вариант доступа через сеть Tor",
+ "privacy-matters-1-title": "Реклама и ценовая дискриминация",
+ "simplex-unique-card-3-p-1": "SimpleX хранит все пользовательские данные на клиентских устройствах в портативном формате зашифрованной базы данных — их можно перенести на другое устройство.",
+ "hero-overlay-1-title": "Как работает SimpleX?",
+ "stable-versions-built-by-f-droid-org": "Стабильные версии, созданные F-Droid.org",
+ "contact-hero-p-3": "Воспользуйтесь ссылками ниже, чтобы загрузить приложение.",
+ "simplex-network": "Сеть SimpleX",
+ "privacy-matters-3-overlay-1-title": "Конфиденциальность защищает вашу свободу",
+ "docs-dropdown-7": "Перевести SimpleX Chat",
+ "back-to-top": "Вернуться к началу",
+ "simplex-network-1-desc": "Все сообщения отправляются через серверы, что обеспечивает лучшую конфиденциальность метаданных и надежную асинхронную доставку сообщений, избегая при этом многих",
+ "simplex-chat-repo": "Репозиторий SimpleX Chat",
+ "simplex-private-card-6-point-1": "Многие коммуникационные платформы уязвимы для MITM-атак со стороны серверов или сетевых провайдеров.",
+ "privacy-matters-3-overlay-1-linkText": "Конфиденциальность защищает вашу свободу",
+ "simplex-unique-overlay-card-1-p-2": "Для доставки сообщений SimpleX использует попарные, анонимные адреса однонаправленных очередей сообщений, раздельные для полученных и отправленных сообщений, обычно через разные серверы. Использование SimpleX это как иметь отдельный “одноразовый” адрес электронной почты или номер телефона для каждого контакта , при это, не обременяя вас управлять эти вручную.",
+ "simplex-unique-overlay-card-3-p-4": "Со стороны не видно разницу между отправлением или получением сообщений — если кто-то наблюдает за этим, он не cможет легко определить, кто с кем общается, даже если протокол TLS будет скомпрометирован.",
+ "docs-dropdown-2": "Доступ к файлам в версии для Android",
+ "get-simplex": "Скачать SimpleX для ПК ",
+ "privacy-matters-overlay-card-3-p-1": "Каждый должен заботиться о конфиденциальности и безопасности своих коммуникаций — безобидные разговоры могут подвергнуть вас опасности, например за ваши политические взгляды, даже если кажется, что вам \"нечего скрывать\".",
+ "simplex-unique-2-title": "Вы защищены от спама и злоупотреблений",
+ "simplex-unique-overlay-card-4-p-3": "Если вы рассматриваете возможность разработки платформе SimpleX, например, чат-бота для пользователей SimpleX или интеграции библиотеки SimpleX Chat в ваше мобильное приложение, пожалуйста, обращайтесь за любыми советами и поддержкой.",
+ "comparison-section-list-point-2": "Адреса на основе DNS",
+ "stable-and-beta-versions-built-by-developers": "Стабильные и бета-версии, созданные разработчиками",
+ "simplex-network-3-header": "Сеть SimpleX",
+ "simplex-unique-card-2-p-1": "Поскольку у вас нет идентификатора или фиксированного адреса на платформе SimpleX, никто не сможет связаться с вами, без вашего явного согласия, только если вы сами поделитесь адресом в виде QR-кода или ссылки.",
+ "hero-overlay-card-3-p-1": "Trail of Bits - ведущая консалтинговая компания в области безопасности и технологий, клиентами которой являются крупные технологические компании, правительственные агентства и крупные блокчейн проекты.",
+ "hero-header": "Иной взгляд на приватность",
+ "comparison-section-list-point-4": "Если операторы серверов скомпрометированы. В Signal, и некоторых других приложениях, есть возможность подтвердить код безопасности",
+ "simplex-private-card-2-point-1": "Дополнительный уровень серверного шифрования для доставки получателю, чтобы предотвратить корреляцию между полученным и отправленным трафиком сервера, если используемый протокол TLS скомпрометированный.",
+ "f-droid-page-simplex-chat-repo-section-text": "Чтобы добавить его в свой клиент F-Droid, отсканируйте QR-код или воспользуйтесь этим URL-адресом:",
+ "guide-dropdown-7": "Приватность и безопасность",
+ "join-the-REDDIT-community": "Присоединяйтесь к нам в сообществе REDDIT",
+ "simplex-private-card-10-point-2": "Это позволяет доставлять сообщения без идентификаторов профиля пользователя, обеспечивая лучшую конфиденциальность метаданных, чем другие альтернативы.",
+ "privacy-matters-2-title": "Манипулирование выборами",
+ "home": "Домашняя страница",
+ "chat-protocol": "Протокол чата",
+ "simplex-private-card-5-point-2": "Это позволяет сообщениям разного размера выглядеть одинаково для серверов и сетевых наблюдателей.",
+ "hero-overlay-card-1-p-1": "Многие спрашивают:Если у SimpleX нету никаких идентификаторов пользователя, то как приложение знает, куда доставлять сообщения? ",
+ "feature-6-title": "Зашифрованные E2E-шифрованием аудио и видео звонки",
+ "hero-p-1": "Другие приложения имеют ID своих пользователей: Signal, Matrix, Session, Briar, Jami, Cwtch и т. п. SimpleX не имет, нету даже случайных цифр . Это значительно повышает вашу приватность.",
+ "simplex-network-overlay-card-1-li-2": "В отличие от многих P2P сетей, SimpleX спроектирован так, чтобы не нуждаться в глобальных идентификаторов его пользователей, даже временных, используя только временные попарные идентификаторы, обеспечивая лучшую анонимность и защиту метаданных пользователя.",
+ "simplex-unique-4-title": "Только вы владеете сетью SimpleX",
+ "privacy-matters-overlay-card-3-p-3": "Обычных людей арестовывают за то, чем они делятся в Интернете, даже через свои \"анонимные\" аккаунты, даже в демократических странах .",
+ "simplex-unique-overlay-card-3-p-2": "Сквозные зашифрованные сообщения временно хранятся на серверах SimpleX до получения, после чего они удаляются безвозвратно.",
+ "blog": "Новости",
+ "simplex-private-card-7-point-1": "Чтобы гарантировать целостность, сообщения последовательно нумеруются и включают в себя хэш предыдущего сообщения.",
+ "simplex-unique-overlay-card-4-p-2": "Платформа SimpleX использует открытый протокол и предоставляет SDK для создания чат-ботов , позволяя внедрять сервисы, с которыми пользователи могут взаимодействовать через приложение SimpleX Chat — мы с нетерпением ждем возможности увидеть, какие сервисы SimpleX вы сможете создать.",
+ "simplex-explained-tab-1-p-1": "Вы можете создавать контакты и группы, а также вести двусторонние беседы, как и в любом другом мессенджере.",
+ "contact-hero-p-2": "Еще не скачали SimpleX Chat?",
+ "why-simplex-is": "Почему SimpleX",
+ "simplex-network-section-header": "Сеть SimpleX",
+ "simplex-private-10-title": "Временные анонимные парные идентификаторы",
+ "privacy-matters-1-overlay-1-linkText": "Конфиденциальность экономит ваши деньги",
+ "tap-the-connect-button-in-the-app": "Нажмите на кнопку ’подключиться’ в приложении",
+ "comparison-section-list-point-4a": "Сервера SimpleX не могут скомпрометировать сквозное шифрование",
+ "unique": "уникальный",
+ "simplex-network-1-overlay-linktext": "проблем P2P сетей",
+ "no-private": "Нет - приватно",
+ "simplex-unique-1-title": "У вас есть полная конфиденциальность",
+ "protocol-2-text": "XMPP, Matrix",
+ "guide": "Руководство",
+ "simplex-network-overlay-card-1-li-4": "Реализации P2P могут быть заблокированы некоторыми интернет-провайдерами (например, BitTorrent ). SimpleX не зависит от транспорта - он может работать по стандартным веб-протоколам, например WebSockets.",
+ "hero-overlay-2-title": "Почему идентификаторы пользователя - вредны для приватности?",
+ "simplex-explained-tab-2-text": "2. Как это работает",
+ "docs-dropdown-4": "Свой SMP Сервер",
+ "feature-4-title": "Зашифрованные E2E-шифрованием голосовые сообщения",
+ "privacy-matters-overlay-card-2-p-1": "Не так давно мы наблюдали, как авторитетная консалтинговая компания манипулировала крупными выборами, используя наши социальные графики для искажения нашего мнения из реального мира и манипулируют нашими голосами.",
+ "privacy-matters-overlay-card-2-p-3": "SimpleX - это первая платформа, которая по своей конструкции не имеет никаких идентификаторов пользователей, таким образом защищая график ваших контактов лучше, чем любая известная альтернатива.",
+ "learn-more": "Учить больше",
+ "donate": "Пожертвовать",
+ "simplex-private-8-title": "Смешивание сообщений для уменьшения корреляции",
+ "scan-qr-code-from-mobile-app": "Отсканируйте QR-код в мобильном приложении",
+ "simplex-private-card-3-point-3": "Возобновление соединения отключено для предотвращения сеансовых атак.",
+ "simplex-private-card-10-point-1": "SimpleX использует временные анонимные попарные адреса и учетные данные для каждого контакта пользователя или члена группы.",
+ "guide-dropdown-6": "Аудио и видео Звонки",
+ "more-info": "Больше информации",
+ "no-decentralized": "Нет - децентрализованный",
+ "protocol-1-text": "Signal, большие платформы",
+ "hero-2-header-desc": "В видео показано, как подключиться к своему другу с помощью его одноразового QR-кода, лично или по видеосвязи. Вы также можете подключиться, поделившись ссылкой-приглашением.",
+ "simplex-network-overlay-card-1-li-6": "Сети P2P могут быть уязвимы для DRDoS атаки , когда клиенты могут ретранслировать и усиливать/увеличивать объём трафика, что приводит к отказу в обслуживании по всей сети. Клиенты SimpleX ретранслируют трафик только из известного соединения и не могут быть использованы злоумышленником для нагрузки трафика всей сети.",
+ "if-you-already-installed-simplex-chat-for-the-terminal": "Если вы уже установили SimpleX Chat для терминала",
+ "docs-dropdown-8": "Служба Каталогов SimpleX",
+ "simplex-private-card-1-point-1": "Протокол с двойным храповым механизмом — обмен сообщениями OTR с идеальной секретностью пересылки и восстановлением после взлома.",
+ "simplex-private-card-8-point-1": "Серверы SimpleX действуют как узлы-миксеры с низкой задержкой — входящие и исходящие сообщения имеют разный порядок.",
+ "simplex-unique-overlay-card-2-p-1": "Поскольку у вас нет идентификатора на платформе SimpleX, никто не сможет связаться с вами, если вы сами не предоставите одноразовый или временный адрес в виде QR-кода или ссылки.",
+ "sign-up-to-receive-our-updates": "Введите ваш Email, чтобы получать рассылку обновлений от нас",
+ "guide-dropdown-1": "Быстрый старт",
+ "simplex-explained-tab-2-p-1": "Для каждого подключения вы используете две отдельные очереди обмена сообщениями, то есть отправка и получения сообщений происходит через разные серверы.",
+ "simplex-private-section-header": "Что делает SimpleX приватным ",
+ "we-invite-you-to-join-the-conversation": "Мы приглашаем вас присоединиться к беседе",
+ "feature-2-title": "Изображения, видео и файлы зашифрованные E2E-шифрованием",
+ "simplex-private-9-title": "Однонаправленные очереди сообщений",
+ "simplex-unique-overlay-card-1-p-3": "Этот дизайн защищает конфиденциальность того, с кем вы общаетесь, скрывая это от серверов SimpleX и от любых наблюдателей из вне. Чтобы скрыть свой IP-адрес от серверов, вы можете подключиться к серверам SimpleX через сеть Tor .",
+ "developers": "Разработчики",
+ "simplex-private-7-title": "Проверка целостности сообщения",
+ "privacy-matters-overlay-card-1-p-4": "Платформа SimpleX защищает конфиденциальность ваших контактов лучше, чем любая другая альтернатива, полностью предотвращая доступ к вашему социальному графику каким-либо компаниям или организациям. Даже когда люди используют серверы, предоставляемые SimpleX Chat, мы не знаем точное количество пользователей или с кем они общаются.",
+ "hero-overlay-card-1-p-6": "Подробнее читайте в техническом документе SimpleX .",
+ "simplex-network-overlay-card-1-p-1": "Протоколы и приложения для обмена сообщениями P2P имеют различные проблемы, которые делают их менее надежными, чем SimpleX, более сложными для анализа и уязвимыми для нескольких типов атак.",
+ "terms-and-privacy-policy": "Условия & Политика Конфиденциальности",
+ "simplex-network-overlay-card-1-li-1": "Сети P2P полагаются на тот или иной вариант DHT для маршрутизации сообщений. Проекты DHT должны обеспечивать баланс между гарантией доставки и задержкой. SimpleX имеет как лучшую гарантию доставки, так и меньшую задержку, чем P2P. В сетях P2P сообщение передается через нескольких узлов, последовательно, кол-во узлов-посредников будет расти параллельно размеру сети - O(log N) .",
+ "privacy-matters-section-label": "Убедитесь, что ваш мессенджер не может получить доступ к вашим данным!",
+ "simplex-unique-overlay-card-3-p-1": "SimpleX Chat хранит все пользовательские данные на клиентских устройствах в портативном формате зашифрованной базы данных которую можно перенести на другое устройство.",
+ "simplex-network-3-desc": "серверы предоставляют однонаправленные очереди для подключения пользователей, но у них нет видимости графика сетевых подключений — это делают только пользователи.",
+ "simplex-unique-card-1-p-1": "SimpleX защищает конфиденциальность вашего профиля, контактов и метаданных, скрывая их от серверов платформы SimpleX и любых наблюдателей.",
+ "simplex-private-card-3-point-1": "Для соединений клиент-сервер используется только протокол TLS 1.2/1.3 с надежными алгоритмами.",
+ "simplex-unique-card-4-p-1": "Сеть SimpleX полностью децентрализована и независима от любой криптовалюты/блокчейна или любой другой платформы, кроме Интернета.",
+ "features": "Возможности",
+ "hero-overlay-card-1-p-3": "Вы определяете, какие серверы будете использовать для получения сообщений, а ваши контакты — серверы, которые вы используете для отправки им сообщений. Каждый новый чат, скорее всего, будет вестись на двух разных серверах.",
+ "docs-dropdown-9": "Скачать",
+ "simplex-chat-protocol": "Протокол SimpleX Chat",
+ "simplex-unique-overlay-card-1-p-1": "В отличие от других платформ обмена сообщениями, SimpleX не имеет идентификаторов, присвоенных пользователям . Он не полагается на номера телефонов, доменные адреса (например, электронную почту или XMPP), имена пользователей, открытые ключи или даже случайные числа для идентификации своих пользователей — мы не знаем, сколько людей пользуются нашими SimpleX серверами.",
+ "reference": "Ссылки",
+ "f-droid-page-f-droid-org-repo-section-text": "Приложение SimpleX Chat от разработчиков и от репозитория F-Droid.org имеют разные ключи подписи. Если вы хотите сменить одно на другое, вам сначала нужно будет экспортировать базу данных и только потом скачать другое приложение.",
+ "simplex-private-5-title": "Многоуровневое Заполнения содержимого"
+}
diff --git a/website/langs/zh_Hans.json b/website/langs/zh_Hans.json
index 78e55a1f72..8fb012963a 100644
--- a/website/langs/zh_Hans.json
+++ b/website/langs/zh_Hans.json
@@ -65,13 +65,13 @@
"hero-overlay-2-textlink": "SimpleX 是如何工作的?",
"hero-2-header-desc": "右侧的视频向您展示了如何通过一次性二维码、面对面交流或通过视频交换链接来连接到您的朋友。您同样可以通过共享邀请链接来进行连接。",
"hero-overlay-1-title": "SimpleX 是如何工作的?",
- "feature-2-title": "端到端加密的图像和文件",
+ "feature-2-title": "端到端加密的 图片、视频和文件",
"feature-1-title": "端到端加密的文字消息,支持markdown和编辑",
"feature-4-title": "端到端加密的语音消息",
- "feature-3-title": "去中心化的秘密群组——只有用户知道它们的存在",
+ "feature-3-title": "端到端加密的去中心化的秘密群组 —只有用户知道它们的存在",
"feature-5-title": "支持消息自动销毁",
"simplex-network-overlay-1-title": "与 P2P 通讯协议的比较",
- "feature-7-title": "随身的加密数据库——可将您的个人资料移至另一台设备",
+ "feature-7-title": "便携的加密应用存储— 可将您的个人资料移至另一台设备",
"simplex-private-10-title": "临时匿名成对标识符",
"simplex-private-8-title": "通过消息混合减少相关性",
"simplex-private-card-1-point-2": "每个队列中的网络与密码学库加密盒(NaCL cryptobox)可防止 TLS 受到威胁时消息队列之间的流量关联。",