diff --git a/apps/ios/SimpleX Localizations/ar.xcloc/Localized Contents/ar.xliff b/apps/ios/SimpleX Localizations/ar.xcloc/Localized Contents/ar.xliff index 980e51142..e0477899b 100644 --- a/apps/ios/SimpleX Localizations/ar.xcloc/Localized Contents/ar.xliff +++ b/apps/ios/SimpleX Localizations/ar.xcloc/Localized Contents/ar.xliff @@ -3655,6 +3655,26 @@ SimpleX servers cannot see your profile. %1$@ في %2$@: copied message info, <sender> at <time> + + # %@ + # %@ + copied message info title, # <title> + + + ## History + ## السجل + copied message info + + + ## In reply to + ## ردًا على + copied message info + + + %@ and %@ connected + %@ و %@ متصل + No comment provided by engineer. + 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 517682933..bef40f5ae 100644 --- a/apps/ios/SimpleX Localizations/cs.xcloc/Localized Contents/cs.xliff +++ b/apps/ios/SimpleX Localizations/cs.xcloc/Localized Contents/cs.xliff @@ -44,14 +44,17 @@ # %@ + # %@ copied message info title, # <title> ## History + ## Historie copied message info ## In reply to + ## Odpovídáno copied message info @@ -86,6 +89,7 @@ %@ and %@ connected + %@ a %@ připojen No comment provided by engineer. @@ -120,6 +124,7 @@ %@, %@ and %lld other members connected + %@, %@ a %lld ostatní členové připojeni No comment provided by engineer. @@ -477,7 +482,7 @@ Accept connection request? - Přijmout kontakt + Přijmout kontakt? No comment provided by engineer. @@ -1063,15 +1068,17 @@ Connect directly + Připojit přímo No comment provided by engineer. Connect incognito + Spojit se inkognito No comment provided by engineer. Connect via contact link - Připojit se přes kontaktní odkaz? + Připojit se přes odkaz No comment provided by engineer. @@ -1091,7 +1098,7 @@ Connect via one-time link - Připojit se jednorázovým odkazem? + Připojit se jednorázovým odkazem No comment provided by engineer. @@ -1569,6 +1576,7 @@ Delivery + Doručenka No comment provided by engineer. @@ -2583,6 +2591,7 @@ Incognito mode protects your privacy by using a new random profile for each contact. + Režim inkognito chrání vaše soukromí používáním nového náhodného profilu pro každý kontakt. No comment provided by engineer. @@ -2659,6 +2668,7 @@ Invalid status + Neplatný status item status text @@ -2744,12 +2754,12 @@ Join incognito - Připojte se inkognito + Připojit se inkognito No comment provided by engineer. Joining group - Připojení ke skupině + Připojování ke skupině No comment provided by engineer. @@ -3009,6 +3019,7 @@ Most likely this connection is deleted. + Pravděpodobně je toto spojení smazáno. item status description @@ -4623,7 +4634,7 @@ Může se to stát kvůli nějaké chybě, nebo pokud je spojení kompromitován They can be overridden in contact and group settings. - Mohou být přepsány v nastavení kontaktů + Mohou být přepsány v nastavení kontaktů. No comment provided by engineer. diff --git a/apps/ios/SimpleX Localizations/cs.xcloc/Source Contents/SimpleX NSE/en.lproj/Localizable.strings b/apps/ios/SimpleX Localizations/cs.xcloc/Source Contents/SimpleX NSE/en.lproj/Localizable.strings deleted file mode 100644 index 8b1378917..000000000 --- a/apps/ios/SimpleX Localizations/cs.xcloc/Source Contents/SimpleX NSE/en.lproj/Localizable.strings +++ /dev/null @@ -1 +0,0 @@ - diff --git a/apps/ios/SimpleX Localizations/de.xcloc/Source Contents/SimpleX NSE/en.lproj/Localizable.strings b/apps/ios/SimpleX Localizations/de.xcloc/Source Contents/SimpleX NSE/en.lproj/Localizable.strings deleted file mode 100644 index 8b1378917..000000000 --- a/apps/ios/SimpleX Localizations/de.xcloc/Source Contents/SimpleX NSE/en.lproj/Localizable.strings +++ /dev/null @@ -1 +0,0 @@ - diff --git a/apps/ios/SimpleX Localizations/en.xcloc/Source Contents/SimpleX NSE/en.lproj/Localizable.strings b/apps/ios/SimpleX Localizations/en.xcloc/Source Contents/SimpleX NSE/en.lproj/Localizable.strings deleted file mode 100644 index 8b1378917..000000000 --- a/apps/ios/SimpleX Localizations/en.xcloc/Source Contents/SimpleX NSE/en.lproj/Localizable.strings +++ /dev/null @@ -1 +0,0 @@ - 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 5df42bc48..174261590 100644 --- a/apps/ios/SimpleX Localizations/es.xcloc/Localized Contents/es.xliff +++ b/apps/ios/SimpleX Localizations/es.xcloc/Localized Contents/es.xliff @@ -466,7 +466,7 @@ About SimpleX address - Acerca de dirección SimpleX + Acerca de la dirección SimpleX No comment provided by engineer. @@ -1208,12 +1208,12 @@ Create SimpleX address - Crear dirección SimpleX + Crear tu dirección SimpleX No comment provided by engineer. Create an address to let people connect with you. - Crear una dirección para que otras personas se puedan conectar contigo. + Crea una dirección para que otras personas puedan conectar contigo. No comment provided by engineer. @@ -1248,7 +1248,7 @@ Create your profile - Crear tu perfil + Crea tu perfil No comment provided by engineer. @@ -1381,7 +1381,7 @@ Decentralized - Descentralizado + Descentralizada No comment provided by engineer. @@ -1706,7 +1706,7 @@ Don't create address - No crear dirección + No crear dirección SimpleX No comment provided by engineer. @@ -2899,7 +2899,7 @@ Markdown in messages - Sintaxis markdown en los mensajes + Sintaxis Markdown No comment provided by engineer. @@ -3663,7 +3663,7 @@ Receiving address will be changed to a different server. Address change will complete after sender comes online. - La dirección de recepción se cambiará. El cambio se completará cuando el remitente esté en línea. + La dirección de recepción pasará a otro servidor. El cambio se completará cuando el remitente esté en línea. No comment provided by engineer. @@ -4383,7 +4383,7 @@ Stop chat to enable database actions - Para habilitar las acciones sobre la base de datos, previamente debes detener Chat + Detén SimpleX para habilitar las acciones sobre la base de datos No comment provided by engineer. @@ -4590,7 +4590,7 @@ Puede ocurrir por algún bug o cuando la conexión está comprometida. The next generation of private messaging - La próxima generación de mensajería privada + La nueva generación de mensajería privada No comment provided by engineer. @@ -5130,7 +5130,7 @@ Para conectarte, pide a tu contacto que cree otro enlace de conexión y comprueb You can create it later - Puedes crearlo más tarde + Puedes crearla más tarde No comment provided by engineer. @@ -5330,7 +5330,7 @@ Para conectarte, pide a tu contacto que cree otro enlace de conexión y comprueb Your chat database - Base de datos Chat + Base de datos No comment provided by engineer. @@ -5411,7 +5411,7 @@ Los servidores de SimpleX no pueden ver tu perfil. Your profile, contacts and delivered messages are stored on your device. - Tu perfil, contactos y mensajes entregados se almacenan en tu dispositivo. + Tu perfil, contactos y mensajes se almacenan en tu dispositivo. No comment provided by engineer. diff --git a/apps/ios/SimpleX Localizations/es.xcloc/Source Contents/SimpleX NSE/en.lproj/Localizable.strings b/apps/ios/SimpleX Localizations/es.xcloc/Source Contents/SimpleX NSE/en.lproj/Localizable.strings deleted file mode 100644 index 8b1378917..000000000 --- a/apps/ios/SimpleX Localizations/es.xcloc/Source Contents/SimpleX NSE/en.lproj/Localizable.strings +++ /dev/null @@ -1 +0,0 @@ - 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 5a66f06cc..a03c47876 100644 --- a/apps/ios/SimpleX Localizations/fi.xcloc/Localized Contents/fi.xliff +++ b/apps/ios/SimpleX Localizations/fi.xcloc/Localized Contents/fi.xliff @@ -953,792 +953,989 @@ Nykyinen tunnuslause… No comment provided by engineer. - + Currently maximum supported file size is %@. + Nykyinen tuettu enimmäistiedostokoko on %@. No comment provided by engineer. - + Dark + Tumma No comment provided by engineer. - + Database ID + Tietokannan tunnus No comment provided by engineer. - + Database encrypted! + Tietokanta salattu! No comment provided by engineer. - + Database encryption passphrase will be updated and stored in the keychain. + Tietokannan salaustunnuslause päivitetään ja tallennetaan avainnippuun. + No comment provided by engineer. - + Database encryption passphrase will be updated. + Tietokannan salauksen tunnuslause päivitetään. + No comment provided by engineer. - + Database error + Tietokantavirhe No comment provided by engineer. - + Database is encrypted using a random passphrase, you can change it. + Tietokanta on salattu satunnaisella tunnuslauseella, voit muuttaa sitä. No comment provided by engineer. - + Database is encrypted using a random passphrase. Please change it before exporting. + Tietokanta on salattu satunnaisella tunnuslauseella. Vaihda se ennen vientiä. No comment provided by engineer. - + Database passphrase + Tietokannan tunnuslause No comment provided by engineer. - + Database passphrase & export + Tietokannan tunnuslause ja vienti No comment provided by engineer. - + Database passphrase is different from saved in the keychain. + Tietokannan tunnuslause eroaa avainnippuun tallennetusta. No comment provided by engineer. - + Database passphrase is required to open chat. + Keskustelun avaamiseen tarvitaan tietokannan tunnuslause. No comment provided by engineer. - + Database will be encrypted and the passphrase stored in the keychain. + Tietokanta salataan ja tunnuslause tallennetaan avainnippuun. + No comment provided by engineer. - + Database will be encrypted. + Tietokanta salataan. + No comment provided by engineer. - + Database will be migrated when the app restarts + Tietokanta siirretään, kun sovellus käynnistyy uudelleen No comment provided by engineer. - + Decentralized + Hajautettu No comment provided by engineer. - + Delete + Poista chat item action - + Delete Contact + Poista kontakti No comment provided by engineer. - + Delete address + Poista osoite No comment provided by engineer. - + Delete address? + Poista osoite? No comment provided by engineer. - + Delete after + Poista jälkeen No comment provided by engineer. - + Delete all files + Poista kaikki tiedostot No comment provided by engineer. - + Delete archive + Poista arkisto No comment provided by engineer. - + Delete chat archive? + Poista keskusteluarkisto? No comment provided by engineer. - + Delete chat profile? + Poista keskusteluprofiili? No comment provided by engineer. - + Delete connection + Poista yhteys No comment provided by engineer. - + Delete contact + Poista kontakti No comment provided by engineer. - + Delete contact? + Poista kontakti? No comment provided by engineer. - + Delete database + Poista tietokanta No comment provided by engineer. - + Delete files and media? + Poista tiedostot ja media? No comment provided by engineer. - + Delete files for all chat profiles + Poista tiedostot kaikista keskusteluprofiileista No comment provided by engineer. - + Delete for everyone + Poista kaikilta chat feature - + Delete for me + Poista minulta No comment provided by engineer. - + Delete group + Poista ryhmä No comment provided by engineer. - + Delete group? + Poista ryhmä? No comment provided by engineer. - + Delete invitation + Poista kutsu No comment provided by engineer. - + Delete link + Poista linkki No comment provided by engineer. - + Delete link? + Poista linkki? No comment provided by engineer. - + Delete member message? + Poista jäsenviesti? No comment provided by engineer. - + Delete message? + Poista viesti? No comment provided by engineer. - + Delete messages + Poista viestit No comment provided by engineer. - + Delete messages after + Poista viestit tämän jälkeen No comment provided by engineer. - + Delete old database + Poista vanha tietokanta No comment provided by engineer. - + Delete old database? + Poista vanha tietokanta? No comment provided by engineer. - + Delete pending connection + Poista vireillä oleva yhteys No comment provided by engineer. - + Delete pending connection? + Poistetaanko odottava yhteys? No comment provided by engineer. - + Delete queue + Poista jono server test step - + Delete user profile? + Poista käyttäjäprofiili? No comment provided by engineer. - + Description + Kuvaus No comment provided by engineer. - + Develop + Kehitä No comment provided by engineer. - + Developer tools + Kehittäjätyökalut No comment provided by engineer. - + Device + Laite No comment provided by engineer. - + Device authentication is disabled. Turning off SimpleX Lock. + Laitteen todennus on poistettu käytöstä. SimpleX Lock kytketään pois päältä. No comment provided by engineer. - + Device authentication is not enabled. You can turn on SimpleX Lock via Settings, once you enable device authentication. + Laitteen todennus ei ole käytössä. Voit ottaa SimpleX Lockin käyttöön Asetuksista, kun olet ottanut laitteen todennuksen käyttöön. No comment provided by engineer. - + Different names, avatars and transport isolation. + Eri nimet, avatarit ja kuljetuseristys. No comment provided by engineer. - + Direct messages + Yksityisviestit chat feature - + Direct messages between members are prohibited in this group. + Yksityisviestit jäsenten välillä ovat kiellettyjä tässä ryhmässä. No comment provided by engineer. - + Disable SimpleX Lock + Poista SimpleX Lock käytöstä authentication reason - + Disappearing messages + Tuhoutuvat viestit chat feature - + Disappearing messages are prohibited in this chat. + Katoavat viestit ovat kiellettyjä tässä keskustelussa. No comment provided by engineer. - + Disappearing messages are prohibited in this group. + Katoavat viestit ovat kiellettyjä tässä ryhmässä. No comment provided by engineer. - + Disconnect + Katkaise server test step - + Display name + Näyttönimi No comment provided by engineer. - + Display name: + Näyttönimi: No comment provided by engineer. - + Do NOT use SimpleX for emergency calls. + Älä käytä SimpleX-sovellusta hätäpuheluihin. No comment provided by engineer. - + Do it later + Tee myöhemmin No comment provided by engineer. - + Don't show again + Älä näytä uudelleen No comment provided by engineer. - + Duplicate display name! + Päällekkäinen näyttönimi! No comment provided by engineer. - + Edit + Muokkaa chat item action - + Edit group profile + Muokkaa ryhmäprofiilia No comment provided by engineer. - + Enable + Salli No comment provided by engineer. - + Enable SimpleX Lock + Ota SimpleX Lock käyttöön authentication reason - + Enable TCP keep-alive + Ota TCP-säilytys käyttöön No comment provided by engineer. - + Enable automatic message deletion? + Ota automaattinen viestien poisto käyttöön? No comment provided by engineer. - + Enable instant notifications? + Salli välittömät ilmoitukset? No comment provided by engineer. - + Enable notifications + Salli ilmoitukset No comment provided by engineer. - + Enable periodic notifications? + Salli säännölliset ilmoitukset? No comment provided by engineer. - + Encrypt + Salaa No comment provided by engineer. - + Encrypt database? + Salaa tietokanta? No comment provided by engineer. - + Encrypted database + Salattu tietokanta No comment provided by engineer. - + Encrypted message or another event + Salattu viesti tai muu tapahtuma notification - + Encrypted message: database error + Salattu viesti: tietokantavirhe notification - + Encrypted message: keychain error + Salattu viesti: avainnipun virhe notification - + Encrypted message: no passphrase + Salattu viesti: ei tunnuslausetta notification - + Encrypted message: unexpected error + Salattu viesti: odottamaton virhe notification - + Enter correct passphrase. + Anna oikea tunnuslause. No comment provided by engineer. - + Enter passphrase… + Syötä tunnuslause… No comment provided by engineer. - + Enter password above to show! + Kirjoita yllä oleva salasana näyttääksesi! No comment provided by engineer. - + Enter server manually + Syötä palvelin manuaalisesti No comment provided by engineer. - + Error + Virhe No comment provided by engineer. - + Error accepting contact request + Virhe kontaktipyynnön hyväksymisessä No comment provided by engineer. - + Error accessing database file + Virhe tietokantatiedoston käyttämisessä No comment provided by engineer. - + Error adding member(s) + Virhe lisättäessä jäseniä No comment provided by engineer. - + Error changing address + Virhe osoitteenvaihdossa No comment provided by engineer. - + Error changing role + Virhe roolin vaihdossa No comment provided by engineer. - + Error changing setting + Virhe asetuksen muuttamisessa No comment provided by engineer. - + Error creating address + Virhe osoitteen luomisessa No comment provided by engineer. - + Error creating group + Virhe ryhmän luomisessa No comment provided by engineer. - + Error creating group link + Virhe ryhmälinkin luomisessa No comment provided by engineer. - + Error creating profile! + Virhe profiilin luomisessa! No comment provided by engineer. - + Error deleting chat database + Virhe keskustelujen tietokannan poistamisessa No comment provided by engineer. - + Error deleting chat! + Virhe keskutelun poistamisessa! No comment provided by engineer. - + Error deleting connection + Virhe yhteyden poistamisessa No comment provided by engineer. - + Error deleting contact + Virhe kontaktin poistamisessa No comment provided by engineer. - + Error deleting database + Virhe tietokannan poistamisessa No comment provided by engineer. - + Error deleting old database + Virhe vanhan tietokannan poistamisessa No comment provided by engineer. - + Error deleting token + Virhe tokenin poistamisessa No comment provided by engineer. - + Error deleting user profile + Virhe käyttäjäprofiilin poistamisessa No comment provided by engineer. - + Error enabling notifications + Virhe ilmoitusten käyttöönotossa No comment provided by engineer. - + Error encrypting database + Virhe tietokannan salauksessa No comment provided by engineer. - + Error exporting chat database + Virhe vietäessä keskustelujen tietokantaa No comment provided by engineer. - + Error importing chat database + Virhe keskustelujen tietokannan tuonnissa No comment provided by engineer. - + Error joining group + Virhe ryhmään liittymisessä No comment provided by engineer. - + Error receiving file + Virhe tiedoston vastaanottamisessa No comment provided by engineer. - + Error removing member + Virhe poistettaessa jäsentä No comment provided by engineer. - + Error saving ICE servers + Virhe ICE-palvelimien tallentamisessa No comment provided by engineer. Error saving SMP servers No comment provided by engineer. - + Error saving group profile + Virhe ryhmäprofiilin tallentamisessa No comment provided by engineer. - + Error saving passphrase to keychain + Virhe tunnuslauseen tallentamisessa avainnippuun No comment provided by engineer. - + Error saving user password + Virhe käyttäjän salasanan tallentamisessa No comment provided by engineer. - + Error sending message + Virhe viestin lähettämisessä No comment provided by engineer. - + Error starting chat + Virhe käynnistettäessä keskustelua No comment provided by engineer. - + Error stopping chat + Virhe keskustelun lopettamisessa No comment provided by engineer. - + Error switching profile! + Virhe profiilin vaihdossa! No comment provided by engineer. - + Error updating group link + Virhe ryhmälinkin päivittämisessä No comment provided by engineer. - + Error updating message + Virhe viestin päivityksessä No comment provided by engineer. - + Error updating settings + Virhe asetusten päivittämisessä No comment provided by engineer. - + Error updating user privacy + Virhe päivitettäessä käyttäjän tietosuojaa No comment provided by engineer. - + Error: %@ + Virhe: %@ No comment provided by engineer. - + Error: URL is invalid + Virhe: URL on virheellinen No comment provided by engineer. - + Error: no database file + Virhe: ei tietokantatiedostoa No comment provided by engineer. - + Exit without saving + Poistu tallentamatta No comment provided by engineer. - + Export database + Vie tietokanta No comment provided by engineer. - + Export error: + Vientivirhe: No comment provided by engineer. - + Exported database archive. + Viety tietokanta-arkisto. No comment provided by engineer. Exporting database archive... No comment provided by engineer. - + Failed to remove passphrase + Tunnuslauseen poisto epäonnistui No comment provided by engineer. - + File will be received when your contact is online, please wait or check later! + Tiedosto vastaanotetaan, kun kontakti on online-tilassa, odota tai tarkista myöhemmin! No comment provided by engineer. - + File: %@ + Tiedosto: %@ No comment provided by engineer. - + Files & media + Tiedostot & media No comment provided by engineer. - + For console + Konsoliin No comment provided by engineer. - + French interface + Ranskalainen käyttöliittymä No comment provided by engineer. - + Full link + Koko linkki No comment provided by engineer. - + Full name (optional) + Koko nimi (valinnainen) No comment provided by engineer. - + Full name: + Koko nimi: No comment provided by engineer. - + Fully re-implemented - work in background! + Täysin uudistettu - toimii taustalla! No comment provided by engineer. - + Further reduced battery usage + Entistä pienempi akun käyttö No comment provided by engineer. - + GIFs and stickers + GIFit ja tarrat No comment provided by engineer. - + Group + Ryhmä No comment provided by engineer. - + Group display name + Ryhmän näyttönimi No comment provided by engineer. - + Group full name (optional) + Ryhmän näyttönimi (valinnainen) No comment provided by engineer. - + Group image + Ryhmäkuva No comment provided by engineer. - + Group invitation + Ryhmän kutsu No comment provided by engineer. - + Group invitation expired + Vanhentunut ryhmäkutsu No comment provided by engineer. - + Group invitation is no longer valid, it was removed by sender. + Ryhmäkutsu ei ole enää voimassa, lähettäjä poisti sen. No comment provided by engineer. - + Group link + Ryhmälinkki No comment provided by engineer. - + Group links + Ryhmälinkit No comment provided by engineer. - + Group members can irreversibly delete sent messages. + Ryhmän jäsenet voivat poistaa lähetetyt viestit peruuttamattomasti. No comment provided by engineer. - + Group members can send direct messages. + Ryhmän jäsenet voivat lähettää suoraviestejä. No comment provided by engineer. - + Group members can send disappearing messages. + Ryhmän jäsenet voivat lähettää katoavia viestejä. No comment provided by engineer. - + Group members can send voice messages. + Ryhmän jäsenet voivat lähettää ääniviestejä. No comment provided by engineer. - + Group message: + Ryhmäviesti: notification - + Group moderation + Ryhmän moderointi No comment provided by engineer. - + Group preferences + Ryhmän asetukset No comment provided by engineer. - + Group profile + Ryhmäprofiili No comment provided by engineer. - + Group profile is stored on members' devices, not on the servers. + Ryhmäprofiili tallennetaan jäsenten laitteille, ei palvelimille. No comment provided by engineer. - + Group welcome message + Ryhmän tervetuloviesti No comment provided by engineer. - + Group will be deleted for all members - this cannot be undone! + Ryhmä poistetaan kaikilta jäseniltä - tätä ei voi kumota! No comment provided by engineer. - + Group will be deleted for you - this cannot be undone! + Ryhmä poistetaan sinulta - tätä ei voi perua! No comment provided by engineer. - + Help + Apua No comment provided by engineer. - + Hidden + Piilotettu No comment provided by engineer. - + Hidden chat profiles + Piilotetut keskusteluprofiilit No comment provided by engineer. - + Hidden profile password + Piilotettu profiilin salasana No comment provided by engineer. - + Hide + Piilota chat item action - + Hide app screen in the recent apps. + Piilota sovellusnäyttö viimeisimmissä sovelluksissa. No comment provided by engineer. - + Hide profile + Piilota profiili No comment provided by engineer. - + How SimpleX works + Miten SimpleX toimii No comment provided by engineer. - + How it works + Kuinka se toimii No comment provided by engineer. - + How to + Miten No comment provided by engineer. - + How to use it + Kuinka sitä käytetään No comment provided by engineer. - + How to use your servers + Miten käytät palvelimiasi No comment provided by engineer. - + ICE servers (one per line) + ICE-palvelimet (yksi per rivi) No comment provided by engineer. If you can't meet in person, **show QR code in the video call**, or share the link. No comment provided by engineer. - + If you cannot meet in person, you can **scan QR code in the video call**, or your contact can share an invitation link. + Jos et voi tavata henkilökohtaisesti, voit **skannata QR-koodin videopuhelussa** tai kontaktisi voi jakaa kutsulinkin. No comment provided by engineer. - + If you need to use the chat now tap **Do it later** below (you will be offered to migrate the database when you restart the app). + Jos haluat käyttää keskustelua nyt, napauta **Tee se myöhemmin** alla (sinulle tarjotaan tietokannan siirtämistä, kun käynnistät sovelluksen uudelleen). No comment provided by engineer. - + Ignore + Sivuuta No comment provided by engineer. - + Image will be received when your contact is online, please wait or check later! + Kuva vastaanotetaan, kun kontaktisi on verkossa, odota tai tarkista myöhemmin! No comment provided by engineer. - + Immune to spam and abuse + Immuuni roskapostille ja väärinkäytöksille No comment provided by engineer. - + Import + Tuo No comment provided by engineer. - + Import chat database? + Tuo keskustelujen-tietokanta? No comment provided by engineer. - + Import database + Tuo tietokanta No comment provided by engineer. - + Improved privacy and security + Parannettu yksityisyys ja turvallisuus No comment provided by engineer. - + Improved server configuration + Parannettu palvelimen kokoonpano No comment provided by engineer. - + Incognito + Incognito No comment provided by engineer. - + Incognito mode + Incognito-tila No comment provided by engineer. @@ -1749,73 +1946,91 @@ Incognito mode protects the privacy of your main profile name and image — for each new contact a new random profile is created. No comment provided by engineer. - + Incoming audio call + Saapuva äänipuhelu notification - + Incoming call + Saapuva puhelu notification - + Incoming video call + Saapuva videopuhelu notification - + Incorrect security code! + Väärä turvakoodi! No comment provided by engineer. - + Install [SimpleX Chat for terminal](https://github.com/simplex-chat/simplex-chat) + Asenna [SimpleX Chat terminaalille](https://github.com/simplex-chat/simplex-chat) No comment provided by engineer. - + Instant push notifications will be hidden! + Välittömät push-ilmoitukset ovat piilossa! + No comment provided by engineer. - + Instantly + Heti No comment provided by engineer. - + Interface + Käyttöliittymä No comment provided by engineer. - + Invalid connection link + Virheellinen yhteyslinkki No comment provided by engineer. - + Invalid server address! + Virheellinen palvelinosoite! No comment provided by engineer. - + Invitation expired! + Vanhentunut kutsu! No comment provided by engineer. - + Invite members + Kutsu jäseniä No comment provided by engineer. - + Invite to group + Kutsu ryhmään No comment provided by engineer. - + Irreversible message deletion + Peruuttamaton viestin poisto No comment provided by engineer. - + Irreversible message deletion is prohibited in this chat. + Viestien peruuttamaton poisto on kielletty tässä keskustelussa. No comment provided by engineer. - + Irreversible message deletion is prohibited in this group. + Viestien peruuttamaton poisto on kielletty tässä ryhmässä. No comment provided by engineer. - + It allows having many anonymous connections without any shared data between them in a single chat profile. + Se mahdollistaa useiden nimettömien yhteyksien muodostamisen yhdessä keskusteluprofiilissa ilman, että niiden välillä on jaettuja tietoja. No comment provided by engineer. @@ -1827,1400 +2042,1744 @@ Please connect to the developers via Settings to receive the updates about the s We will be adding server redundancy to prevent lost messages. 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 (%@). + Näyttäisi, että olet jo yhteydessä tämän linkin kautta. Jos näin ei ole, tapahtui virhe (%@). No comment provided by engineer. - + Italian interface + Italialainen käyttöliittymä No comment provided by engineer. - + Join + Liity No comment provided by engineer. - + Join group + Liity ryhmään No comment provided by engineer. - + Join incognito + Liity incognito-tilassa No comment provided by engineer. - + Joining group + Liittyy ryhmään No comment provided by engineer. - + Keychain error + Avainnipun virhe No comment provided by engineer. - + LIVE + LIVE No comment provided by engineer. - + Large file! + Suuri tiedosto! No comment provided by engineer. - + Leave + Poistu No comment provided by engineer. - + Leave group + Poistu ryhmästä No comment provided by engineer. - + Leave group? + Poistu ryhmästä? No comment provided by engineer. - + Light + Vaalea No comment provided by engineer. - + Limitations + Rajoitukset No comment provided by engineer. - + Live message! + Live-viesti! No comment provided by engineer. - + Live messages + Live-viestit No comment provided by engineer. - + Local name + Paikallinen nimi No comment provided by engineer. - + Local profile data only + Vain paikalliset profiilitiedot No comment provided by engineer. - + Make a private connection + Luo yksityinen yhteys No comment provided by engineer. - + Make profile private! + Tee profiilista yksityinen! No comment provided by engineer. Make sure SMP server addresses are in correct format, line separated and are not duplicated (%@). No comment provided by engineer. - + Make sure WebRTC ICE server addresses are in correct format, line separated and are not duplicated. + Varmista, että WebRTC ICE -palvelinosoitteet ovat oikeassa muodossa, rivieroteltuina ja että ne eivät ole päällekkäisiä. No comment provided by engineer. - + Many people asked: *if SimpleX has no user identifiers, how can it deliver messages?* + Monet ihmiset kysyivät: *Jos SimpleX:llä ei ole käyttäjätunnuksia, miten se voi toimittaa viestejä?* No comment provided by engineer. - + Mark deleted for everyone + Merkitse poistetuksi kaikilta No comment provided by engineer. - + Mark read + Merkitse luetuksi No comment provided by engineer. - + Mark verified + Merkitse vahvistetuksi No comment provided by engineer. - + Markdown in messages + Markdown viesteissä No comment provided by engineer. - + Max 30 seconds, received instantly. + Enintään 30 sekuntia, vastaanotetaan välittömästi. No comment provided by engineer. - + Member + Jäsen No comment provided by engineer. - + Member role will be changed to "%@". All group members will be notified. + Jäsenen rooli muuttuu muotoon "%@". Kaikille ryhmän jäsenille ilmoitetaan asiasta. No comment provided by engineer. - + Member role will be changed to "%@". The member will receive a new invitation. + Jäsenen rooli muutetaan muotoon "%@". Jäsen saa uuden kutsun. No comment provided by engineer. - + Member will be removed from group - this cannot be undone! + Jäsen poistetaan ryhmästä - tätä ei voi perua! No comment provided by engineer. - + Message delivery error + Viestin toimitusvirhe No comment provided by engineer. - + Message draft + Viestiluonnos No comment provided by engineer. - + Message text + Viestin teksti No comment provided by engineer. - + Messages + Viestit No comment provided by engineer. Migrating database archive... No comment provided by engineer. - + Migration error: + Siirtovirhe: No comment provided by engineer. - + Migration failed. Tap **Skip** below to continue using the current database. Please report the issue to the app developers via chat or email [chat@simplex.chat](mailto:chat@simplex.chat). + Siirto epäonnistui. Jatka nykyisen tietokannan käyttöä napauttamalla alla **Poistu**. Ilmoita ongelmasta sovelluskehittäjille keskustelussa tai sähköpostitse [chat@simplex.chat](mailto:chat@simplex.chat). No comment provided by engineer. - + Migration is completed + Siirto on valmis No comment provided by engineer. - + Moderate + Moderoi chat item action - + More improvements are coming soon! + Lisää parannuksia on tulossa pian! No comment provided by engineer. - + Most likely this contact has deleted the connection with you. + Todennäköisesti tämä kontakti on poistanut yhteyden sinuun. No comment provided by engineer. - + Multiple chat profiles + Useita keskusteluprofiileja No comment provided by engineer. - + Mute + Mykistä No comment provided by engineer. - + Muted when inactive! + Mykistetty ei-aktiivisena! No comment provided by engineer. - + Name + Nimi No comment provided by engineer. - + Network & servers + Verkko ja palvelimet No comment provided by engineer. - + Network settings + Verkkoasetukset No comment provided by engineer. - + Network status + Verkon tila No comment provided by engineer. - + New contact request + Uusi kontaktipyyntö notification - + New contact: + Uusi kontakti: notification - + New database archive + Uusi tietokanta-arkisto No comment provided by engineer. - + New in %@ + Uutta %@ No comment provided by engineer. - + New member role + Uusi jäsenrooli No comment provided by engineer. - + New message + Uusi viesti notification - + New passphrase… + Uusi tunnuslause… No comment provided by engineer. - + No + Ei No comment provided by engineer. - + No contacts selected + Kontakteja ei ole valittu No comment provided by engineer. - + No contacts to add + Ei lisättäviä kontakteja No comment provided by engineer. - + No device token! + Ei laitetunnusta! No comment provided by engineer. - + Group not found! + Ryhmää ei löydy! No comment provided by engineer. - + No permission to record voice message + Ei lupaa ääniviestin tallentamiseen No comment provided by engineer. - + No received or sent files + Ei vastaanotettuja tai lähetettyjä tiedostoja No comment provided by engineer. - + Notifications + Ilmoitukset No comment provided by engineer. - + Notifications are disabled! + Ilmoitukset on poistettu käytöstä! No comment provided by engineer. - + Now admins can: - delete members' messages. - disable members ("observer" role) + Nyt järjestelmänvalvojat voivat: +- poistaa jäsenten viestit. +- poista jäsenet käytöstä ("tarkkailija" rooli) No comment provided by engineer. - + Off (Local) + Pois (Paikallinen) No comment provided by engineer. - + Ok + Ok No comment provided by engineer. - + Old database + Vanha tietokanta No comment provided by engineer. - + Old database archive + Vanha tietokanta-arkisto No comment provided by engineer. - + One-time invitation link + Kertakutsulinkki No comment provided by engineer. - + Onion hosts will be required for connection. Requires enabling VPN. + Yhteyden muodostamiseen tarvitaan Onion-isäntiä. Edellyttää VPN:n sallimista. No comment provided by engineer. - + Onion hosts will be used when available. Requires enabling VPN. + Onion-isäntiä käytetään, kun niitä on saatavilla. Edellyttää VPN:n sallimista. No comment provided by engineer. - + Onion hosts will not be used. + Onion-isäntiä ei käytetä. No comment provided by engineer. - + Only client devices store user profiles, contacts, groups, and messages sent with **2-layer end-to-end encryption**. + Vain asiakaslaitteet tallentavat käyttäjäprofiileja, yhteystietoja, ryhmiä ja viestejä, jotka on lähetetty **kaksinkertaisella päästä päähän -salauksella**. No comment provided by engineer. - + Only group owners can change group preferences. + Vain ryhmän omistajat voivat muuttaa ryhmän asetuksia. No comment provided by engineer. - + Only group owners can enable voice messages. + Vain ryhmän omistajat voivat ottaa ääniviestit käyttöön. No comment provided by engineer. - + Only you can irreversibly delete messages (your contact can mark them for deletion). + Vain sinä voit poistaa viestejä peruuttamattomasti (kontaktisi voi merkitä ne poistettavaksi). No comment provided by engineer. - + Only you can send disappearing messages. + Vain sinä voit lähettää katoavia viestejä. No comment provided by engineer. - + Only you can send voice messages. + Vain sinä voit lähettää ääniviestejä. No comment provided by engineer. - + Only your contact can irreversibly delete messages (you can mark them for deletion). + Vain kontaktisi voi poistaa viestejä peruuttamattomasti (voit merkitä ne poistettavaksi). No comment provided by engineer. - + Only your contact can send disappearing messages. + Vain kontaktisi voi lähettää katoavia viestejä. No comment provided by engineer. - + Only your contact can send voice messages. + Vain kontaktisi voi lähettää ääniviestejä. No comment provided by engineer. - + Open Settings + Avaa Asetukset No comment provided by engineer. - + Open chat + Avaa keskustelu No comment provided by engineer. - + Open chat console + Avaa keskustelukonsoli authentication reason - + Open user profiles + Avaa käyttäjäprofiilit authentication reason - + Open-source protocol and code – anybody can run the servers. + Avoimen lähdekoodin protokolla ja koodi - kuka tahansa voi käyttää palvelimia. No comment provided by engineer. - + Opening the link in the browser may reduce connection privacy and security. Untrusted SimpleX links will be red. + Linkin avaaminen selaimessa voi heikentää yhteyden yksityisyyttä ja turvallisuutta. Epäluotetut SimpleX-linkit näkyvät punaisina. No comment provided by engineer. - + PING count + PING-määrä No comment provided by engineer. - + PING interval + PING-väli No comment provided by engineer. - + Password to show + Salasana näytettäväksi No comment provided by engineer. - + Paste + Liitä No comment provided by engineer. - + Paste image + Liitä kuva No comment provided by engineer. - + Paste received link + Liitä vastaanotettu linkki No comment provided by engineer. Paste the link you received into the box below to connect with your contact. No comment provided by engineer. - + People can connect to you only via the links you share. + Ihmiset voivat ottaa sinuun yhteyttä vain jakamiesi linkkien kautta. No comment provided by engineer. - + Periodically + Ajoittain No comment provided by engineer. - + Please ask your contact to enable sending voice messages. + Pyydä kontaktiasi sallimaan ääniviestien lähettäminen. No comment provided by engineer. - + Please check that you used the correct link or ask your contact to send you another one. + Tarkista, että käytit oikeaa linkkiä tai pyydä kontaktiasi lähettämään sinulle uusi linkki. No comment provided by engineer. - + Please check your network connection with %@ and try again. + Tarkista verkkoyhteytesi %@:lla ja yritä uudelleen. No comment provided by engineer. - + Please check yours and your contact preferences. + Tarkista omasi ja kontaktin asetukset. No comment provided by engineer. - + Please contact group admin. + Ota yhteyttä ryhmän ylläpitäjään. No comment provided by engineer. - + Please enter correct current passphrase. + Anna oikea nykyinen tunnuslause. No comment provided by engineer. - + Please enter the previous password after restoring database backup. This action can not be undone. + Anna edellinen salasana tietokannan varmuuskopion palauttamisen jälkeen. Tätä toimintoa ei voi kumota. No comment provided by engineer. - + Please restart the app and migrate the database to enable push notifications. + Käynnistä sovellus uudelleen ja siirrä tietokanta push-ilmoitusten ottamiseksi käyttöön. No comment provided by engineer. - + Please store passphrase securely, you will NOT be able to access chat if you lose it. + Säilytä tunnuslause turvallisesti, ET pääse keskusteluihin, jos kadotat sen. No comment provided by engineer. - + Please store passphrase securely, you will NOT be able to change it if you lose it. + Säilytä tunnuslause turvallisesti, ET voi muuttaa sitä, jos kadotat sen. No comment provided by engineer. - + Possibly, certificate fingerprint in server address is incorrect + Palvelimen osoitteen varmenteen sormenjälki on mahdollisesti virheellinen server test error - + Preserve the last message draft, with attachments. + Säilytä viimeinen viestiluonnos liitteineen. No comment provided by engineer. - + Preset server + Esiasetettu palvelin No comment provided by engineer. - + Preset server address + Esiasetettu palvelimen osoite No comment provided by engineer. - + Privacy & security + Yksityisyys ja turvallisuus No comment provided by engineer. - + Privacy redefined + Yksityisyys uudelleen määritettynä No comment provided by engineer. - + Private filenames + Yksityiset tiedostonimet No comment provided by engineer. - + Profile and server connections + Profiili- ja palvelinyhteydet No comment provided by engineer. - + Profile image + Profiilikuva No comment provided by engineer. - + Prohibit irreversible message deletion. + Estä peruuttamaton viestien poistaminen. No comment provided by engineer. - + Prohibit sending direct messages to members. + Estä suorien viestien lähettäminen jäsenille. No comment provided by engineer. - + Prohibit sending disappearing messages. + Estä katoavien viestien lähettäminen. No comment provided by engineer. - + Prohibit sending voice messages. + Estä ääniviestien lähettäminen. No comment provided by engineer. - + Protect app screen + Suojaa sovellusnäyttö No comment provided by engineer. - + Protect your chat profiles with a password! + Suojaa keskusteluprofiilisi salasanalla! No comment provided by engineer. - + Protocol timeout + Protokollan aikakatkaisu No comment provided by engineer. - + Push notifications + Push-ilmoitukset No comment provided by engineer. - + Rate the app + Arvioi sovellus No comment provided by engineer. - + Read + Lue No comment provided by engineer. - + Read more in our GitHub repository. + Lue lisää GitHub-tietovarastostamme. No comment provided by engineer. - + Read more in our [GitHub repository](https://github.com/simplex-chat/simplex-chat#readme). + Lue lisää [GitHub-arkistosta](https://github.com/simplex-chat/simplex-chat#readme). No comment provided by engineer. - + Received file event + Tiedoston vastaanottotapahtuma notification - + Receiving via + Vastaanotto kautta No comment provided by engineer. - + Recipients see updates as you type them. + Vastaanottajat näkevät päivitykset, kun kirjoitat niitä. No comment provided by engineer. - + Reduced battery usage + Pienempi akun käyttö No comment provided by engineer. - + Reject + Hylkää reject incoming call via notification Reject contact (sender NOT notified) No comment provided by engineer. - + Reject contact request + Hylkää yhteyspyyntö No comment provided by engineer. - + Relay server is only used if necessary. Another party can observe your IP address. + Välityspalvelinta käytetään vain tarvittaessa. Toinen osapuoli voi tarkkailla IP-osoitettasi. No comment provided by engineer. - + Relay server protects your IP address, but it can observe the duration of the call. + Välityspalvelin suojaa IP-osoitteesi, mutta se voi tarkkailla puhelun kestoa. No comment provided by engineer. - + Remove + Poista No comment provided by engineer. - + Remove member + Poista jäsen No comment provided by engineer. - + Remove member? + Poista jäsen? No comment provided by engineer. - + Remove passphrase from keychain? + Poista tunnuslause avainnipusta? No comment provided by engineer. - + Reply + Vastaa chat item action - + Required + Pakollinen No comment provided by engineer. - + Reset + Oletustilaan No comment provided by engineer. - + Reset colors + Oletusvärit No comment provided by engineer. - + Reset to defaults + Palauta oletusasetukset No comment provided by engineer. - + Restart the app to create a new chat profile + Käynnistä sovellus uudelleen uuden keskusteluprofiilin luomiseksi No comment provided by engineer. - + Restart the app to use imported chat database + Käynnistä sovellus uudelleen käyttääksesi tuotua keskustelujen-tietokantaa No comment provided by engineer. - + Restore + Palauta No comment provided by engineer. - + Restore database backup + Palauta tietokannan varmuuskopio No comment provided by engineer. - + Restore database backup? + Palauta tietokannan varmuuskopio? No comment provided by engineer. - + Restore database error + Virhe tietokannan palauttamisessa No comment provided by engineer. - + Reveal + Paljasta chat item action - + Revert + Palauta No comment provided by engineer. - + Role + Rooli No comment provided by engineer. - + Run chat + Käynnistä chat No comment provided by engineer. - + SMP servers + SMP-palvelimet No comment provided by engineer. - + Save + Tallenna chat item action - + Save (and notify contacts) + Tallenna (ja ilmoita kontakteille) No comment provided by engineer. - + Save and notify contact + Tallenna ja ilmoita kontaktille No comment provided by engineer. - + Save and notify group members + Tallenna ja ilmoita ryhmän jäsenille No comment provided by engineer. - + Save and update group profile + Tallenna ja päivitä ryhmäprofiili No comment provided by engineer. - + Save archive + Tallenna arkisto No comment provided by engineer. - + Save group profile + Tallenna ryhmäprofiili No comment provided by engineer. - + Save passphrase and open chat + Tallenna tunnuslause ja avaa keskustelu No comment provided by engineer. - + Save passphrase in Keychain + Tallenna tunnuslause Avainnippuun No comment provided by engineer. - + Save preferences? + Tallenna asetukset? No comment provided by engineer. - + Save profile password + Tallenna profiilin salasana No comment provided by engineer. - + Save servers + Tallenna palvelimet No comment provided by engineer. - + Save servers? + Tallenna palvelimet? No comment provided by engineer. - + Save welcome message? + Tallenna tervetuloviesti? No comment provided by engineer. - + Saved WebRTC ICE servers will be removed + Tallennetut WebRTC ICE -palvelimet poistetaan No comment provided by engineer. - + Scan QR code + Skannaa QR-koodi No comment provided by engineer. - + Scan code + Skannaa koodi No comment provided by engineer. - + Scan security code from your contact's app. + Skannaa turvakoodi kontaktisi sovelluksesta. No comment provided by engineer. - + Scan server QR code + Skannaa palvelimen QR-koodi No comment provided by engineer. - + Search + Haku No comment provided by engineer. - + Secure queue + Turvallinen jono server test step - + Security assessment + Turvallisuusarviointi No comment provided by engineer. - + Security code + Turvakoodi No comment provided by engineer. - + Send + Lähetä No comment provided by engineer. - + Send a live message - it will update for the recipient(s) as you type it + Lähetä live-viesti - se päivittyy vastaanottajille, kun kirjoitat sitä No comment provided by engineer. - + Send direct message + Lähetä yksityisviesti No comment provided by engineer. - + Send link previews + Lähetä linkkien esikatselu No comment provided by engineer. - + Send live message + Lähetä live-viesti No comment provided by engineer. - + Send notifications + Lähetys ilmoitukset No comment provided by engineer. - + Send notifications: + Lähetys ilmoitukset: No comment provided by engineer. - + Send questions and ideas + Lähetä kysymyksiä ja ideoita No comment provided by engineer. - + Send them from gallery or custom keyboards. + Lähetä ne galleriasta tai mukautetuista näppäimistöistä. No comment provided by engineer. - + Sender cancelled file transfer. + Lähettäjä peruutti tiedoston siirron. No comment provided by engineer. - + Sender may have deleted the connection request. + Lähettäjä on saattanut poistaa yhteyspyynnön. No comment provided by engineer. - + Sending via + Lähetetään kautta No comment provided by engineer. - + Sent file event + Lähetetty tiedosto tapahtuma notification - + Sent messages will be deleted after set time. + Lähetetyt viestit poistetaan asetetun ajan kuluttua. No comment provided by engineer. - + Server requires authorization to create queues, check password + Palvelin vaatii valtuutuksen jonojen luomiseen, tarkista salasana server test error - + Server test failed! + Palvelintesti epäonnistui! No comment provided by engineer. - + Servers + Palvelimet No comment provided by engineer. - + Set 1 day + Aseta 1 päivä No comment provided by engineer. - + Set contact name… + Aseta kontaktin nimi… No comment provided by engineer. - + Set group preferences + Aseta ryhmän asetukset No comment provided by engineer. - + Set passphrase to export + Aseta tunnuslause vientiä varten No comment provided by engineer. - + Set the message shown to new members! + Aseta uusille jäsenille näytettävä viesti! No comment provided by engineer. - + Set timeouts for proxy/VPN + Aseta aikakatkaisut välityspalvelimelle/VPN:lle No comment provided by engineer. - + Settings + Asetukset No comment provided by engineer. - + Share + Jaa chat item action Share invitation link No comment provided by engineer. - + Share link + Jaa linkki No comment provided by engineer. - + Share one-time invitation link + Jaa kertakutsulinkki No comment provided by engineer. Show QR code No comment provided by engineer. - + Show calls in phone history + Näytä puhelut puhelinhistoriassa No comment provided by engineer. - + Show preview + Näytä esikatselu No comment provided by engineer. - + SimpleX Chat security was audited by Trail of Bits. + Trail of Bits on tarkastanut SimpleX Chatin tietoturvan. No comment provided by engineer. - + SimpleX Lock + SimpleX Lock No comment provided by engineer. - + SimpleX Lock turned on + SimpleX Lock päällä No comment provided by engineer. - + SimpleX contact address + SimpleX-yhteystiedot simplex link type - + SimpleX encrypted message or connection event + SimpleX-salattu viesti tai yhteystapahtuma notification - + SimpleX group link + SimpleX-ryhmän linkki simplex link type - + SimpleX links + SimpleX-linkit No comment provided by engineer. - + SimpleX one-time invitation + SimpleX-kertakutsu simplex link type - + Skip + Ohita No comment provided by engineer. - + Skipped messages + Ohitetut viestit No comment provided by engineer. - + Somebody + Joku notification title - + Start a new chat + Aloita uusi keskustelu No comment provided by engineer. - + Start chat + Aloita keskustelu No comment provided by engineer. - + Start migration + Aloita siirto No comment provided by engineer. - + Stop + Lopeta No comment provided by engineer. - + Stop SimpleX + Lopeta SimpleX authentication reason - + Stop chat to enable database actions + Pysäytä keskustelu tietokantatoimien mahdollistamiseksi No comment provided by engineer. - + Stop chat to export, import or delete chat database. You will not be able to receive and send messages while the chat is stopped. + Pysäytä keskustelut viedäksesi, tuodaksesi tai poistaaksesi keskustelujen tietokannan. Et voi vastaanottaa ja lähettää viestejä, kun keskustelut on pysäytetty. No comment provided by engineer. - + Stop chat? + Lopeta keskustelu? No comment provided by engineer. - + Support SimpleX Chat + SimpleX Chat tuki No comment provided by engineer. - + System + Järjestelmä No comment provided by engineer. - + TCP connection timeout + TCP-yhteyden aikakatkaisu No comment provided by engineer. - + TCP_KEEPCNT + TCP_KEEPCNT No comment provided by engineer. - + TCP_KEEPIDLE + TCP_KEEPIDLE No comment provided by engineer. - + TCP_KEEPINTVL + TCP_KEEPINTVL No comment provided by engineer. - + Take picture + Ota kuva No comment provided by engineer. - + Tap button + Napauta painiketta No comment provided by engineer. - + Tap to activate profile. + Aktivoi profiili napauttamalla. No comment provided by engineer. - + Tap to join + Liity napauttamalla No comment provided by engineer. - + Tap to join incognito + Napauta liittyäksesi incognito-tilassa No comment provided by engineer. - + Tap to start a new chat + Aloita uusi keskustelu napauttamalla No comment provided by engineer. - + Test failed at step %@. + Testi epäonnistui vaiheessa %@. server test failure - + Test server + Testipalvelin No comment provided by engineer. - + Test servers + Testipalvelimet No comment provided by engineer. - + Tests failed! + Testit epäonnistuivat! No comment provided by engineer. - + Thank you for installing SimpleX Chat! + Kiitos SimpleX Chatin asentamisesta! No comment provided by engineer. - + Thanks to the users – [contribute via Weblate](https://github.com/simplex-chat/simplex-chat/tree/stable#help-translating-simplex-chat)! + Kiitos käyttäjille - [osallistu Weblaten avulla](https://github.com/simplex-chat/simplex-chat/tree/stable#help-translating-simplex-chat)! No comment provided by engineer. - + Thanks to the users – contribute via Weblate! + Kiitokset käyttäjille – osallistu Weblaten kautta! No comment provided by engineer. - + The 1st platform without any user identifiers – private by design. + Ensimmäinen alusta ilman käyttäjätunnisteita – suunniteltu yksityiseksi. No comment provided by engineer. - + The app can notify you when you receive messages or contact requests - please open settings to enable. + Sovellus voi ilmoittaa sinulle, kun saat viestejä tai yhteydenottopyyntöjä - avaa asetukset ottaaksesi ne käyttöön. No comment provided by engineer. - + The attempt to change database passphrase was not completed. + Tietokannan tunnuslauseen muuttamista ei suoritettu loppuun. No comment provided by engineer. - + The connection you accepted will be cancelled! + Hyväksymäsi yhteys peruuntuu! No comment provided by engineer. - + The contact you shared this link with will NOT be able to connect! + Kontakti, jolle jaoit tämän linkin, EI voi muodostaa yhteyttä! No comment provided by engineer. - + The created archive is available via app Settings / Database / Old database archive. + Luotu arkisto on käytettävissä sovelluksen Asetukset / Tietokanta / Vanha tietokanta-arkisto kautta. No comment provided by engineer. - + The group is fully decentralized – it is visible only to the members. + Ryhmä on täysin hajautettu - se näkyy vain jäsenille. No comment provided by engineer. - + The message will be deleted for all members. + Viesti poistetaan kaikilta jäseniltä. No comment provided by engineer. - + The message will be marked as moderated for all members. + Viesti merkitään moderoiduksi kaikille jäsenille. No comment provided by engineer. - + The next generation of private messaging + Seuraavan sukupolven yksityisviestit No comment provided by engineer. - + The old database was not removed during the migration, it can be deleted. + Vanhaa tietokantaa ei poistettu siirron aikana, se voidaan kuitenkin poistaa. No comment provided by engineer. - + The profile is only shared with your contacts. + Profiili jaetaan vain kontaktiesi kanssa. No comment provided by engineer. - + The sender will NOT be notified + Lähettäjälle EI ilmoiteta No comment provided by engineer. - + The servers for new connections of your current chat profile **%@**. + Palvelimet nykyisen keskusteluprofiilisi uusille yhteyksille **%@**. No comment provided by engineer. - + Theme + Teema No comment provided by engineer. - + There should be at least one user profile. + Käyttäjäprofiileja tulee olla vähintään yksi. No comment provided by engineer. - + There should be at least one visible user profile. + Näkyviä käyttäjäprofiileja tulee olla vähintään yksi. No comment provided by engineer. - + This action cannot be undone - all received and sent files and media will be deleted. Low resolution pictures will remain. + Tätä toimintoa ei voi kumota - kaikki vastaanotetut ja lähetetyt tiedostot ja media poistetaan. Matalan resoluution kuvat säilyvät. No comment provided by engineer. - + This action cannot be undone - the messages sent and received earlier than selected will be deleted. It may take several minutes. + Tätä toimintoa ei voi kumota - valittua aikaisemmin lähetetyt ja vastaanotetut viestit poistetaan. Tämä voi kestää useita minuutteja. No comment provided by engineer. - + This action cannot be undone - your profile, contacts, messages and files will be irreversibly lost. + Tätä toimintoa ei voi kumota - profiilisi, kontaktisi, viestisi ja tiedostosi poistuvat peruuttamattomasti. No comment provided by engineer. This feature is experimental! It will only work if the other client has version 4.2 installed. You should see the message in the conversation once the address change is completed – please check that you can still receive messages from this contact (or group member). No comment provided by engineer. - + This group no longer exists. + Tätä ryhmää ei enää ole olemassa. No comment provided by engineer. - + This setting applies to messages in your current chat profile **%@**. + Tämä asetus koskee nykyisen keskusteluprofiilisi viestejä *%@**. No comment provided by engineer. - + To ask any questions and to receive updates: + Voit esittää kysymyksiä ja saada päivityksiä: No comment provided by engineer. To find the profile used for an incognito connection, tap the contact or group name on top of the chat. No comment provided by engineer. - + To make a new connection + Uuden yhteyden luominen No comment provided by engineer. - + To protect privacy, instead of user IDs used by all other platforms, SimpleX has identifiers for message queues, separate for each of your contacts. + Yksityisyyden suojaamiseksi kaikkien muiden alustojen käyttämien käyttäjätunnusten sijaan SimpleX käyttää viestijonojen tunnisteita, jotka ovat kaikille kontakteille erillisiä. No comment provided by engineer. - + To protect timezone, image/voice files use UTC. + Aikavyöhykkeen suojaamiseksi kuva-/äänitiedostot käyttävät UTC:tä. No comment provided by engineer. - + To protect your information, turn on SimpleX Lock. You will be prompted to complete authentication before this feature is enabled. + Suojaa tietosi ottamalla SimpleX Lock käyttöön. +Sinua kehotetaan suorittamaan todennus loppuun, ennen kuin tämä ominaisuus otetaan käyttöön. No comment provided by engineer. - + To record voice message please grant permission to use Microphone. + Jos haluat nauhoittaa ääniviestin, anna lupa käyttää mikrofonia. No comment provided by engineer. - + To reveal your hidden profile, enter a full password into a search field in **Your chat profiles** page. + Voit paljastaa piilotetun profiilisi syöttämällä koko salasanan hakukenttään **Keskusteluprofiilisi** -sivulla. No comment provided by engineer. - + To support instant push notifications the chat database has to be migrated. + Keskustelujen-tietokanta on siirrettävä välittömien push-ilmoitusten tukemiseksi. No comment provided by engineer. - + To verify end-to-end encryption with your contact compare (or scan) the code on your devices. + Voit tarkistaa päästä päähän -salauksen kontaktisi kanssa vertaamalla (tai skannaamalla) laitteidenne koodia. No comment provided by engineer. - + Transport isolation + Kuljetuksen eristäminen No comment provided by engineer. - + Trying to connect to the server used to receive messages from this contact (error: %@). + Yritetään muodostaa yhteyttä palvelimeen, jota käytetään tämän kontaktin viestien vastaanottamiseen (virhe: %@). No comment provided by engineer. - + Trying to connect to the server used to receive messages from this contact. + Yritetään muodostaa yhteys palvelimeen, jota käytetään viestien vastaanottamiseen tältä kontaktilta. No comment provided by engineer. - + Turn off + Sammuta No comment provided by engineer. - + Turn off notifications? + Kytke ilmoitukset pois päältä? No comment provided by engineer. - + Turn on + Kytke päälle No comment provided by engineer. - + Unable to record voice message + Ääniviestiä ei voi tallentaa No comment provided by engineer. - + Unexpected error: %@ + Odottamaton virhe: %@ No comment provided by engineer. - + Unexpected migration state + Odottamaton siirtotila No comment provided by engineer. - + Unhide + Näytä No comment provided by engineer. - + Unknown caller + Tuntematon soittaja callkit banner - + Unknown database error: %@ + Tuntematon tietokantavirhe: %@ No comment provided by engineer. - + Unknown error + Tuntematon virhe No comment provided by engineer. - + Unless you use iOS call interface, enable Do Not Disturb mode to avoid interruptions. + Ellet käytä iOS:n puhelinkäyttöliittymää, ota Älä häiritse -tila käyttöön keskeytysten välttämiseksi. No comment provided by engineer. - + Unless your contact deleted the connection or this link was already used, it might be a bug - please report it. To connect, please ask your contact to create another connection link and check that you have a stable network connection. + Ellei yhteyshenkilösi poistanut yhteyttä tai tämä linkki oli jo käytössä, se voi olla virhe - ilmoita siitä. +Jos haluat muodostaa yhteyden, pyydä kontaktiasi luomaan toinen yhteyslinkki ja tarkista, että verkkoyhteytesi on vakaa. No comment provided by engineer. - + Unlock + Avaa authentication reason - + Unmute + Poista mykistys No comment provided by engineer. - + Unread + Lukematon No comment provided by engineer. - + Update + Päivitä No comment provided by engineer. - + Update .onion hosts setting? + Päivitä .onion-isäntien asetus? No comment provided by engineer. - + Update database passphrase + Päivitä tietokannan tunnuslause No comment provided by engineer. - + Update network settings? + Päivitä verkkoasetukset? No comment provided by engineer. - + Update transport isolation mode? + Päivitä kuljetuksen eristystila? No comment provided by engineer. - + Updating settings will re-connect the client to all servers. + Asetusten päivittäminen yhdistää asiakkaan uudelleen kaikkiin palvelimiin. No comment provided by engineer. - + Updating this setting will re-connect the client to all servers. + Tämän asetuksen päivittäminen yhdistää asiakkaan uudelleen kaikkiin palvelimiin. No comment provided by engineer. - + Use .onion hosts + Käytä .onion-isäntiä No comment provided by engineer. - + Use SimpleX Chat servers? + Käytä SimpleX Chat palvelimia? No comment provided by engineer. - + Use chat + Käytä chattia No comment provided by engineer. - + Use for new connections + Käytä uusiin yhteyksiin No comment provided by engineer. - + Use iOS call interface + Käytä iOS:n puhelujen käyttöliittymää No comment provided by engineer. - + Use server + Käytä palvelinta No comment provided by engineer. - + User profile + Käyttäjäprofiili No comment provided by engineer. - + Using .onion hosts requires compatible VPN provider. + .onion-isäntien käyttäminen vaatii yhteensopivan VPN-palveluntarjoajan. No comment provided by engineer. - + Using SimpleX Chat servers. + Käyttää SimpleX Chat -palvelimia. No comment provided by engineer. - + Verify connection security + Tarkista yhteyden suojaus No comment provided by engineer. - + Verify security code + Tarkista turvakoodi No comment provided by engineer. - + Via browser + Selaimella No comment provided by engineer. - + Video call + Videopuhelu No comment provided by engineer. - + View security code + Näytä turvakoodi No comment provided by engineer. - + Voice messages + Ääniviestit chat feature - + Voice messages are prohibited in this chat. + Ääniviestit ovat kiellettyjä tässä keskustelussa. No comment provided by engineer. - + Voice messages are prohibited in this group. + Ääniviestit ovat kiellettyjä tässä ryhmässä. No comment provided by engineer. - + Voice messages prohibited! + Ääniviestit kielletty! No comment provided by engineer. - + Voice message… + Ääniviesti… No comment provided by engineer. - + Waiting for file + Odottaa tiedostoa No comment provided by engineer. - + Waiting for image + Odottaa kuvaa No comment provided by engineer. - + WebRTC ICE servers + WebRTC ICE -palvelimet No comment provided by engineer. - + Welcome %@! + Tervetuloa %@! No comment provided by engineer. - + Welcome message + Tervetuloviesti No comment provided by engineer. - + What's new + Uusimmat No comment provided by engineer. - + When available + Kun saatavilla No comment provided by engineer. - + When you share an incognito profile with somebody, this profile will be used for the groups they invite you to. + Kun jaat inkognitoprofiilin jonkun kanssa, tätä profiilia käytetään ryhmissä, joihin tämä sinut kutsuu. No comment provided by engineer. - + With optional welcome message. + Valinnaisella tervetuloviestillä. No comment provided by engineer. - + Wrong database passphrase + Väärä tietokannan tunnuslause No comment provided by engineer. - + Wrong passphrase! + Väärä tunnuslause! No comment provided by engineer. - + You + Sinä No comment provided by engineer. - + You accepted connection + Hyväksyit yhteyden No comment provided by engineer. - + You allow + Sallit No comment provided by engineer. - + You already have a chat profile with the same display name. Please choose another name. + Sinulla on jo keskusteluprofiili samalla näyttönimellä. Valitse toinen nimi. No comment provided by engineer. - + You are already connected to %@. + Olet jo muodostanut yhteyden %@:n kanssa. No comment provided by engineer. - + You are connected to the server used to receive messages from this contact. + Olet yhteydessä palvelimeen, jota käytetään vastaanottamaan viestejä tältä kontaktilta. No comment provided by engineer. - + You are invited to group + Sinut on kutsuttu ryhmään No comment provided by engineer. - + You can accept calls from lock screen, without device and app authentication. + Voit vastaanottaa puheluita lukitusnäytöltä ilman laitteen ja sovelluksen todennusta. No comment provided by engineer. - + You can also connect by clicking the link. If it opens in the browser, click **Open in mobile app** button. + Voit myös muodostaa yhteyden klikkaamalla linkkiä. Jos se avautuu selaimessa, napsauta **Avaa mobiilisovelluksessa**-painiketta. No comment provided by engineer. @@ -3228,148 +3787,180 @@ To connect, please ask your contact to create another connection link and check SimpleX Lock must be enabled. No comment provided by engineer. - + You can now send messages to %@ + Voit nyt lähettää viestejä %@:lle notification body - + You can set lock screen notification preview via settings. + Voit määrittää lukitusnäytön ilmoituksen esikatselun asetuksista. No comment provided by engineer. - + You can share a link or a QR code - anybody will be able to join the group. You won't lose members of the group if you later delete it. + Voit jakaa linkin tai QR-koodin - kuka tahansa voi liittyä ryhmään. Et menetä ryhmän jäseniä, jos poistat sen myöhemmin. No comment provided by engineer. You can share your address as a link or as a QR code - anybody will be able to connect to you. You won't lose your contacts if you later delete it. No comment provided by engineer. - + You can start chat via app Settings / Database or by restarting the app + Voit aloittaa keskustelun sovelluksen Asetukset / Tietokanta kautta tai käynnistämällä sovelluksen uudelleen No comment provided by engineer. - + You can use markdown to format messages: + Voit käyttää markdownia viestien muotoiluun: No comment provided by engineer. - + You can't send messages! + Et voi lähettää viestejä! No comment provided by engineer. - + You control through which server(s) **to receive** the messages, your contacts – the servers you use to message them. + Sinä hallitset, minkä palvelim(i)en kautta **viestit vastaanotetaan**, kontaktisi - palvelimet, joita käytät viestien lähettämiseen niille. No comment provided by engineer. - + You could not be verified; please try again. + Sinua ei voitu todentaa; yritä uudelleen. No comment provided by engineer. - + You have no chats + Sinulla ei ole keskusteluja No comment provided by engineer. - + You have to enter passphrase every time the app starts - it is not stored on the device. + Sinun on annettava tunnuslause aina, kun sovellus käynnistyy - sitä ei tallenneta laitteeseen. No comment provided by engineer. You invited your contact No comment provided by engineer. - + You joined this group + Liityit tähän ryhmään No comment provided by engineer. - + You joined this group. Connecting to inviting group member. + Liityit tähän ryhmään. Muodostetaan yhteyttä ryhmän jäsenten kutsumiseksi. No comment provided by engineer. - + You must use the most recent version of your chat database on one device ONLY, otherwise you may stop receiving the messages from some contacts. + Sinun tulee käyttää keskustelujen-tietokannan uusinta versiota AINOSTAAN yhdessä laitteessa, muuten saatat lakata vastaanottamasta viestejä joiltakin kontakteilta. No comment provided by engineer. - + You need to allow your contact to send voice messages to be able to send them. + Sinun on sallittava kontaktiesi lähettää ääniviestejä, jotta voit lähettää niitä. No comment provided by engineer. - + You rejected group invitation + Hylkäsit ryhmäkutsun No comment provided by engineer. - + You sent group invitation + Lähetit ryhmäkutsun No comment provided by engineer. - + You will be connected to group when the group host's device is online, please wait or check later! + Sinut yhdistetään ryhmään, kun ryhmän isännän laite on online-tilassa, odota tai tarkista myöhemmin! No comment provided by engineer. - + You will be connected when your connection request is accepted, please wait or check later! + Sinut yhdistetään, kun yhteyspyyntösi on hyväksytty, odota tai tarkista myöhemmin! No comment provided by engineer. - + You will be connected when your contact's device is online, please wait or check later! + Sinut yhdistetään, kun kontaktisi laite on online-tilassa, odota tai tarkista myöhemmin! No comment provided by engineer. - + You will be required to authenticate when you start or resume the app after 30 seconds in background. + Sinun on tunnistauduttava, kun käynnistät sovelluksen tai jatkat sen käyttöä 30 sekunnin tauon jälkeen. No comment provided by engineer. - + You will join a group this link refers to and connect to its group members. + Liityt ryhmään, johon tämä linkki viittaa, ja muodostat yhteyden sen ryhmän jäseniin. No comment provided by engineer. - + You will still receive calls and notifications from muted profiles when they are active. + Saat edelleen puheluita ja ilmoituksia mykistetyiltä profiileilta, kun ne ovat aktiivisia. No comment provided by engineer. - + You will stop receiving messages from this group. Chat history will be preserved. + Et enää saa viestejä tästä ryhmästä. Keskusteluhistoria säilytetään. No comment provided by engineer. - + You're trying to invite contact with whom you've shared an incognito profile to the group in which you're using your main profile + Yrität kutsua kontaktia, jonka kanssa olet jakanut inkognito-profiilin, ryhmään, jossa käytät pääprofiiliasi No comment provided by engineer. - + You're using an incognito profile for this group - to prevent sharing your main profile inviting contacts is not allowed + Käytät tässä ryhmässä incognito-profiilia. Kontaktien kutsuminen ei ole sallittua, jotta pääprofiilisi ei tule jaetuksi No comment provided by engineer. - + Your ICE servers + ICE-palvelimesi No comment provided by engineer. - + Your SMP servers + SMP-palvelimesi No comment provided by engineer. Your SimpleX contact address No comment provided by engineer. - + Your calls + Puhelusi No comment provided by engineer. - + Your chat database + Keskustelut-tietokantasi No comment provided by engineer. - + Your chat database is not encrypted - set passphrase to encrypt it. + Keskustelut-tietokantasi ei ole salattu - aseta tunnuslause sen salaamiseksi. No comment provided by engineer. - + Your chat profile will be sent to group members + Keskusteluprofiilisi lähetetään ryhmän jäsenille No comment provided by engineer. Your chat profile will be sent to your contact No comment provided by engineer. - + Your chat profiles + Keskusteluprofiilisi No comment provided by engineer. @@ -3384,142 +3975,177 @@ SimpleX Lock must be enabled. Your contact can scan it from the app. No comment provided by engineer. - + Your contact needs to be online for the connection to complete. You can cancel this connection and remove the contact (and try later with a new link). + Kontaktin tulee olla online-tilassa, jotta yhteys voidaan muodostaa. +Voit peruuttaa tämän yhteyden ja poistaa kontaktin (ja yrittää myöhemmin uudella linkillä). No comment provided by engineer. - + Your contact sent a file that is larger than currently supported maximum size (%@). + Yhteyshenkilösi lähetti tiedoston, joka on suurempi kuin tällä hetkellä tuettu enimmäiskoko (%@). No comment provided by engineer. - + Your contacts can allow full message deletion. + Kontaktisi voivat sallia viestien täydellisen poistamisen. No comment provided by engineer. - + Your current chat database will be DELETED and REPLACED with the imported one. + Nykyinen keskustelut-tietokantasi poistetaan ja korvataan tuodulla tietokannalla. No comment provided by engineer. - + Your current profile + Nykyinen profiilisi No comment provided by engineer. - + Your preferences + Asetuksesi No comment provided by engineer. - + Your privacy + Yksityisyytesi No comment provided by engineer. - + Your profile is stored on your device and shared only with your contacts. SimpleX servers cannot see your profile. + Profiilisi tallennetaan laitteeseesi ja jaetaan vain yhteystietojesi kanssa. +SimpleX-palvelimet eivät näe profiiliasi. No comment provided by engineer. Your profile will be sent to the contact that you received this link from No comment provided by engineer. - + Your profile, contacts and delivered messages are stored on your device. + Profiilisi, kontaktisi ja toimitetut viestit tallennetaan laitteellesi. No comment provided by engineer. - + Your random profile + Satunnainen profiilisi No comment provided by engineer. - + Your server + Palvelimesi No comment provided by engineer. - + Your server address + Palvelimesi osoite No comment provided by engineer. - + Your settings + Asetuksesi No comment provided by engineer. - + [Contribute](https://github.com/simplex-chat/simplex-chat#contribute) + [Osallistu](https://github.com/simplex-chat/simplex-chat#contribute) No comment provided by engineer. - + [Send us email](mailto:chat@simplex.chat) + [Lähetä meille sähköpostia](mailto:chat@simplex.chat) No comment provided by engineer. - + [Star on GitHub](https://github.com/simplex-chat/simplex-chat) + [Tähti GitHubissa](https://github.com/simplex-chat/simplex-chat) No comment provided by engineer. - + \_italic_ + \_italic_ No comment provided by engineer. - + \`a + b` + \`a + b` No comment provided by engineer. - + above, then choose: + edellä, valitse sitten: No comment provided by engineer. - + accepted call + hyväksytty puhelu call status - + admin + ylläpitäjä member role - + always + aina pref value - + audio call (not e2e encrypted) + äänipuhelu (ei e2e-salattu) No comment provided by engineer. - + bad message ID + virheellinen viestin tunniste integrity error chat item - + bad message hash + virheellinen viestin tarkiste integrity error chat item - + bold + lihavoitu No comment provided by engineer. - + call error + soittovirhe call status - + call in progress + puhelu käynnissä call status - + calling… + soittaa… call status - + cancelled %@ + peruutettu %@ feature offered item - + changed address for you + muuttunut osoite sinulle chat item text - + changed role of %1$@ to %2$@ + %1$@:n roolin muuttui %2$@:ksi rcv group event chat item - + changed your role to %@ + roolisi muuttui %@:ksi rcv group event chat item @@ -3530,409 +4156,510 @@ SimpleX servers cannot see your profile. changing address... chat item text - + colored + värillinen No comment provided by engineer. - + complete + valmis No comment provided by engineer. - + connect to SimpleX Chat developers. + ole yhteydessä SimpleX Chat -kehittäjiin. No comment provided by engineer. - + connected + yhdistetty No comment provided by engineer. - + connecting + yhdistää No comment provided by engineer. - + connecting (accepted) + yhdistäminen (hyväksytty) No comment provided by engineer. - + connecting (announced) + yhdistäminen (ilmoitettu) No comment provided by engineer. - + connecting (introduced) + yhdistäminen (esitelty) No comment provided by engineer. - + connecting (introduction invitation) + yhdistäminen (esittelykutsu) No comment provided by engineer. - + connecting call… + yhdistää puhelun… call status - + connecting… + yhdistää… chat list item title - + connection established + yhteys luotu chat list item title (it should not be shown - + connection:%@ + yhteys:%@ connection information - + contact has e2e encryption + kontaktilla on e2e-salaus No comment provided by engineer. - + contact has no e2e encryption + kontaktilla ei ole e2e-salausta No comment provided by engineer. - + creator + luoja No comment provided by engineer. - + default (%@) + oletusarvo (%@) pref value - + deleted + poistettu deleted chat item - + deleted group + poistettu ryhmä rcv group event chat item - + direct + suora connection level description - + duplicate message + päällekkäinen viesti integrity error chat item - + e2e encrypted + e2e-salattu No comment provided by engineer. - + enabled + käytössä enabled status - + enabled for contact + käytössä kontaktille enabled status - + enabled for you + käytössä sinulle enabled status - + ended + päättyi No comment provided by engineer. - + ended call %@ + puhelu päättyi %@:lle call status - + error + virhe No comment provided by engineer. - + group deleted + ryhmä poistettu No comment provided by engineer. - + group profile updated + ryhmäprofiili päivitetty snd group event chat item - + iOS Keychain is used to securely store passphrase - it allows receiving push notifications. + iOS-Avainnippua käytetään tunnuslauseen turvalliseen tallentamiseen - se mahdollistaa push-ilmoitusten vastaanottamisen. No comment provided by engineer. - + iOS Keychain will be used to securely store passphrase after you restart the app or change passphrase - it will allow receiving push notifications. + iOS-Avainnippua käytetään tunnuslauseen turvalliseen tallentamiseen sen muuttamisen tai sovelluksen uudelleen käynnistämisen jälkeen - se mahdollistaa push-ilmoitusten vastaanottamisen. No comment provided by engineer. - + incognito via contact address link + incognito kontaktilinkin kautta chat list item description - + incognito via group link + incognito ryhmälinkin kautta chat list item description - + incognito via one-time link + incognito kertalinkillä chat list item description - + indirect (%d) + epäsuora (%d) connection level description - + invalid chat + virheellinen keskustelu invalid chat data - + invalid chat data + virheelliset keskustelu-tiedot No comment provided by engineer. - + invalid data + virheelliset tiedot invalid chat item - + invitation to group %@ + kutsu ryhmään %@ group name - + invited + kutsuttu No comment provided by engineer. - + invited %@ + kutsuttu %@ rcv group event chat item - + invited to connect + kutsuttu yhteydenpitoon chat list item title - + invited via your group link + kutsuttu ryhmäsi linkin kautta rcv group event chat item - + italic + kursivoitu No comment provided by engineer. - + join as %@ + Liity %@:nä No comment provided by engineer. - + left + poistunut rcv group event chat item - + marked deleted + merkitty poistetuksi marked deleted chat item preview text - + member + jäsen member role - + connected + yhdistetty rcv group event chat item - + message received + viesti vastaanotettu notification - + missed call + vastaamaton puhelu call status - + moderated + moderoitu moderated chat item - + moderated by %@ + %@ moderoi No comment provided by engineer. - + never + ei koskaan No comment provided by engineer. - + new message + uusi viesti notification - + no + ei pref value - + no e2e encryption + ei e2e-salausta No comment provided by engineer. - + observer + tarkkailija member role - + off + pois enabled status group pref value - + offered %@ + tarjottu %@ feature offered item - + offered %1$@: %2$@ + tarjottu %1$@: %2$@ feature offered item - + on + päällä group pref value - + or chat with the developers + tai keskustele kehittäjien kanssa No comment provided by engineer. - + owner + omistaja member role - + peer-to-peer + vertais No comment provided by engineer. - + received answer… + vastaus saatu… No comment provided by engineer. - + received confirmation… + vahvistus saatu… No comment provided by engineer. - + rejected call + hylätty puhelu call status - + removed + poistettu No comment provided by engineer. - + removed %@ + %@ poistettu rcv group event chat item - + removed you + poisti sinut rcv group event chat item - + sec + sek network option - + secret + salainen No comment provided by engineer. - + starting… + alkaa… No comment provided by engineer. - + strike + soita No comment provided by engineer. - + this contact + tämä kontakti notification title - + unknown + tuntematon connection info - + updated group profile + päivitetty ryhmäprofiili rcv group event chat item - + v%@ (%@) + v%@ (%@) No comment provided by engineer. - + via contact address link + kontaktiosoitelinkillä chat list item description - + via group link + ryhmälinkillä chat list item description - + via one-time link + kertalinkillä chat list item description - + via relay + releellä No comment provided by engineer. - + video call (not e2e encrypted) + videopuhelu (ei e2e-salattu) No comment provided by engineer. - + waiting for answer… + odottaa vastaamista… No comment provided by engineer. - + waiting for confirmation… + odottaa vahvistusta… No comment provided by engineer. - + wants to connect to you! + haluaa olla yhteydessä sinuun! No comment provided by engineer. - + yes + kyllä pref value - + you are invited to group + sinut on kutsuttu ryhmään No comment provided by engineer. - + you are observer + olet tarkkailija No comment provided by engineer. - + you changed address + muutit osoitetta chat item text - + you changed address for %@ + muutit osoitetta %@:ksi chat item text - + you changed role for yourself to %@ + vaihdoit roolin itsellesi %@:ksi snd group event chat item - + you changed role of %1$@ to %2$@ + olet vaihtanut %1$@:n roolin %2$@:ksi snd group event chat item - + you left + lähdit snd group event chat item - + you removed %@ + poistit %@ snd group event chat item - + you shared one-time link + jaoit kertalinkin chat list item description - + you shared one-time link incognito + jaoit kertalinkin incognito-tilassa chat list item description - + you: + sinä: No comment provided by engineer. - + \~strike~ + \~strike~ No comment provided by engineer. @@ -4117,7 +4844,7 @@ SimpleX servers cannot see your profile. 0s - 0s + 0s No comment provided by engineer. @@ -4246,6 +4973,1371 @@ SimpleX servers cannot see your profile. Kontaktit No comment provided by engineer. + + # %@ + # %@ + copied message info title, # <title> + + + ## History + ## Historia + copied message info + + + ## In reply to + ## vastauksena + copied message info + + + %@ and %@ connected + %@ ja %@ yhdistetty + No comment provided by engineer. + + + You can hide or mute a user profile - swipe it to the right. + Voit piilottaa tai mykistää käyttäjäprofiilin pyyhkäisemällä sitä oikealle. + No comment provided by engineer. + + + Database upgrade + Tietokannan päivitys + No comment provided by engineer. + + + Deleted at + Poistettu klo + No comment provided by engineer. + + + Deleted at: %@ + Poistettu klo: %@ + copied message info + + + Duration + Kesto + No comment provided by engineer. + + + Files and media are prohibited in this group. + Tiedostot ja media ovat tässä ryhmässä kiellettyjä. + No comment provided by engineer. + + + Incompatible database version + Yhteensopimaton tietokantaversio + No comment provided by engineer. + + + Moderated at: %@ + Moderoitu klo: %@ + copied message info + + + New display name + Uusi näyttönimi + No comment provided by engineer. + + + Only your contact can add message reactions. + Vain kontaktisi voi lisätä viestireaktioita. + No comment provided by engineer. + + + Only your contact can make calls. + Vain kontaktisi voi soittaa puheluita. + No comment provided by engineer. + + + Polish interface + Puolalainen käyttöliittymä + No comment provided by engineer. + + + Select + Valitse + No comment provided by engineer. + + + Sent at: %@ + Lähetetty klo: %@ + copied message info + + + Set passcode + Aseta pääsykoodi + No comment provided by engineer. + + + Share address + Jaa osoite + No comment provided by engineer. + + + Share with contacts + Jaa kontaktien kanssa + No comment provided by engineer. + + + no text + ei tekstiä + copied message info in history + + + seconds + sekuntia + time unit + + + weeks + viikkoa + time unit + + + Database IDs and Transport isolation option. + Tietokantatunnukset ja kuljetuseristysvaihtoehto. + No comment provided by engineer. + + + Database downgrade + Tietokannan alentaminen + No comment provided by engineer. + + + Downgrade and open chat + Alenna ja avaa keskustelu + No comment provided by engineer. + + + Enter Passcode + Syötä pääsykoodi + No comment provided by engineer. + + + File will be received when your contact completes uploading it. + Tiedosto vastaanotetaan, kun kontaktisi on ladannut sen. + No comment provided by engineer. + + + Image will be received when your contact completes uploading it. + Kuva vastaanotetaan, kun kontaktisi on ladannut sen. + No comment provided by engineer. + + + Immediately + Heti + No comment provided by engineer. + + + Incorrect passcode + Väärä pääsykoodi + PIN entry + + + KeyChain error + Avainnipun virhe + No comment provided by engineer. + + + Messages & files + Viestit ja tiedostot + No comment provided by engineer. + + + Migrations: %@ + Siirrot: %@ + No comment provided by engineer. + + + No app password + Ei sovelluksen salasanaa + Authentication unavailable + + + Passcode entry + Pääsykoodin syöttö + No comment provided by engineer. + + + Passcode not changed! + Pääsykoodia ei ole muutettu! + No comment provided by engineer. + + + Passcode set! + Pääsykoodi asetettu! + No comment provided by engineer. + + + Show developer options + Näytä kehittäjävaihtoehdot + No comment provided by engineer. + + + SimpleX Lock mode + SimpleX Lock -tila + No comment provided by engineer. + + + Upgrade and open chat + Päivitä ja avaa keskustelu + No comment provided by engineer. + + + Video will be received when your contact is online, please wait or check later! + Video vastaanotetaan, kun kontaktisi on online-tilassa, odota tai tarkista myöhemmin! + No comment provided by engineer. + + + Warning: you may lose some data! + Varoitus: saatat menettää joitain tietoja! + No comment provided by engineer. + + + XFTP servers + XFTP-palvelimet + No comment provided by engineer. + + + different migration in the app/database: %@ / %@ + eri siirtyminen sovelluksessa/tietokannassa: %@ / %@ + No comment provided by engineer. + + + A new random profile will be shared. + Uusi satunnainen profiili jaetaan. + No comment provided by engineer. + + + Accept connection request? + Hyväksy yhteyspyyntö? + No comment provided by engineer. + + + Connect directly + Yhdistä suoraan + No comment provided by engineer. + + + Connect incognito + Yhdistä Incognito + No comment provided by engineer. + + + Custom time + Mukautettu aika + No comment provided by engineer. + + + Don't create address + Älä luo osoitetta + No comment provided by engineer. + + + Encrypted message: database migration error + Salattu viesti: tietokannan siirtovirhe + notification + + + Fix connection + Korjaa yhteys + No comment provided by engineer. + + + Fix connection? + Korjaa yhteys? + No comment provided by engineer. + + + Fix not supported by contact + Kontakti ei tue korjausta + No comment provided by engineer. + + + Fix not supported by group member + Ryhmän jäsen ei tue korjausta + No comment provided by engineer. + + + Only you can add message reactions. + Vain sinä voit lisätä viestireaktioita. + No comment provided by engineer. + + + Only you can make calls. + Vain sinä voit soittaa puheluita. + No comment provided by engineer. + + + Paste the link you received to connect with your contact. + Liitä saamasi linkki, jonka avulla voit muodostaa yhteyden kontaktiisi. + placeholder + + + Please remember or store it securely - there is no way to recover a lost passcode! + Muista tai säilytä se turvallisesti - kadonnutta pääsykoodia ei voi palauttaa! + No comment provided by engineer. + + + Profile update will be sent to your contacts. + Profiilipäivitys lähetetään kontakteillesi. + No comment provided by engineer. + + + Prohibit sending files and media. + Estä tiedostojen ja median lähettäminen. + No comment provided by engineer. + + + Receipts are disabled + Kuittaukset pois käytöstä + No comment provided by engineer. + + + Record updated at: %@ + Tietue päivitetty klo: %@ + copied message info + + + Reject (sender NOT notified) + Hylkää (lähettäjälle EI ilmoiteta) + No comment provided by engineer. + + + Renegotiate encryption + Uudelleenneuvottele salaus + No comment provided by engineer. + + + Save settings? + Tallenna asetukset? + No comment provided by engineer. + + + Self-destruct + Itsetuho + No comment provided by engineer. + + + Send disappearing message + Lähetä katoava viesti + No comment provided by engineer. + + + Send receipts + Lähetä kuittaukset + No comment provided by engineer. + + + Sending receipts is disabled for %lld groups + Kuittien lähettäminen ei ole käytössä %lld ryhmille + No comment provided by engineer. + + + Show: + Näytä: + No comment provided by engineer. + + + SimpleX address + SimpleX-osoite + No comment provided by engineer. + + + Some non-fatal errors occurred during import - you may see Chat console for more details. + Tuonnin aikana tapahtui joitakin ei-vakavia virheitä – saatat nähdä Chat-konsolissa lisätietoja. + No comment provided by engineer. + + + They can be overridden in contact and group settings. + Ne voidaan ohittaa kontakti- ja ryhmäasetuksissa. + No comment provided by engineer. + + + This group has over %lld members, delivery receipts are not sent. + Tässä ryhmässä on yli %lld jäsentä, lähetyskuittauksia ei lähetetä. + No comment provided by engineer. + + + Use new incognito profile + Käytä uutta incognito-profiilia + No comment provided by engineer. + + + Waiting for video + Odottaa videota + No comment provided by engineer. + + + You invited a contact + Kutsuit kontaktin + No comment provided by engineer. + + + agreeing encryption… + hyväksyy salausta… + chat item text + + + disabled + ei käytössä + No comment provided by engineer. + + + encryption ok for %@ + salaus ok %@:lle + chat item text + + + encryption re-negotiation allowed + salauksen uudelleenneuvottelu sallittu + chat item text + + + minutes + minuuttia + time unit + + + Initial role + Alkuperäinen rooli + No comment provided by engineer. + + + Don't enable + Älä salli + No comment provided by engineer. + + + Enable lock + Ota lukitus käyttöön + No comment provided by engineer. + + + Enable self-destruct + Ota itsetuho käyttöön + No comment provided by engineer. + + + Error enabling delivery receipts! + Virhe toimituskuittauksien sallimisessa! + No comment provided by engineer. + + + Error setting delivery receipts! + Virhe toimituskuittauksien asettamisessa! + No comment provided by engineer. + + + Sent message + Lähetetty viesti + message info title + + + Server requires authorization to upload, check password + Palvelin vaatii valtuutuksen tiedoston lataamiseksi, tarkista salasana + server test error + + + Set it instead of system authentication. + Aseta se järjestelmän todennuksen sijaan. + No comment provided by engineer. + + + Share address with contacts? + Jaa osoite kontakteille? + No comment provided by engineer. + + + Share 1-time link + Jaa kertakäyttölinkki + No comment provided by engineer. + + + Show last messages + Näytä viimeiset viestit + No comment provided by engineer. + + + Stop receiving file? + Lopeta tiedoston vastaanottaminen? + No comment provided by engineer. + + + SimpleX Lock not enabled! + SimpleX Lock ei ole käytössä! + No comment provided by engineer. + + + Small groups (max 20) + Pienryhmät (max 20) + No comment provided by engineer. + + + Stop sending file? + Lopeta tiedoston lähettäminen? + No comment provided by engineer. + + + Submit + Lähetä + No comment provided by engineer. + + + System authentication + Järjestelmän todennus + No comment provided by engineer. + + + These settings are for your current profile **%@**. + Nämä asetukset koskevat nykyistä profiiliasi **%@**. + No comment provided by engineer. + + + Passcode + Pääsykoodi + No comment provided by engineer. + + + Please report it to the developers. + Ilmoita siitä kehittäjille. + No comment provided by engineer. + + + Profile password + Profiilin salasana + No comment provided by engineer. + + + Prohibit audio/video calls. + Estä ääni- ja videopuhelut. + No comment provided by engineer. + + + Prohibit message reactions. + Estä viestireaktiot. + No comment provided by engineer. + + + Prohibit messages reactions. + Estä viestireaktiot. + No comment provided by engineer. + + + React… + Reagoi… + chat item menu + + + Read more + Lue lisää + No comment provided by engineer. + + + Read more in [User Guide](https://simplex.chat/docs/guide/app-settings.html#your-simplex-contact-address). + Lue lisää [Käyttöoppaasta](https://simplex.chat/docs/guide/app-settings.html#your-simplex-contact-address). + No comment provided by engineer. + + + Received message + Vastaanotettu viesti + message info title + + + Invite friends + Kutsu ystäviä + No comment provided by engineer. + + + Invalid status + Virheellinen tila + item status text + + + Files and media + Tiedostot ja media + chat feature + + + Files and media prohibited! + Tiedostot ja media kielletty! + No comment provided by engineer. + + + Finally, we have them! 🚀 + Vihdoinkin meillä! 🚀 + No comment provided by engineer. + + + Filter unread and favorite chats. + Suodata lukemattomia- ja suosikkikeskusteluja. + No comment provided by engineer. + + + Fix + Korjaa + No comment provided by engineer. + + + Find chats faster + Löydä keskustelut nopeammin + No comment provided by engineer. + + + Group members can add message reactions. + Ryhmän jäsenet voivat lisätä viestireaktioita. + No comment provided by engineer. + + + If you enter your self-destruct passcode while opening the app: + Jos syötät itsetuhoutuvan pääsykoodin sovellusta avattaessa: + No comment provided by engineer. + + + Japanese interface + Japanilainen käyttöliittymä + No comment provided by engineer. + + + Make one message disappear + Hävitä yksi viesti + No comment provided by engineer. + + + Message reactions are prohibited in this group. + Viestireaktiot ovat kiellettyjä tässä ryhmässä. + No comment provided by engineer. + + + Sending delivery receipts will be enabled for all contacts in all visible chat profiles. + Toimituskuittauksien lähettäminen otetaan käyttöön kaikille kontakteille näkyvissä keskusteluprofiileissa. + No comment provided by engineer. + + + Sending delivery receipts will be enabled for all contacts. + Toimituskuittauksien lähettäminen otetaan käyttöön kaikille kontakteille. + No comment provided by engineer. + + + Sending receipts is disabled for %lld contacts + Kuittauksien lähettäminen ei ole käytössä %lld kontakteille + No comment provided by engineer. + + + Sent at + Lähetetty klo + No comment provided by engineer. + + + Unhide chat profile + Näytä keskusteluprofiili + No comment provided by engineer. + + + Upload file + Lataa tiedosto + server test step + + + Use current profile + Käytä nykyistä profiilia + No comment provided by engineer. + + + You can share your address as a link or QR code - anybody can connect to you. + Voit jakaa osoitteesi linkkinä tai QR-koodina - kuka tahansa voi muodostaa yhteyden sinuun. + No comment provided by engineer. + + + You can turn on SimpleX Lock via Settings. + Voit ottaa SimpleX Lockin käyttöön Asetusten kautta. + No comment provided by engineer. + + + Your contacts will remain connected. + Kontaktisi pysyvät yhdistettyinä. + No comment provided by engineer. + + + Decryption error + Salauksen purkuvirhe + message decrypt error item + + + Delete chat profile + Poista keskusteluprofiili + No comment provided by engineer. + + + Let's talk in SimpleX Chat + Jutellaan SimpleX Chatissa + email subject + + + Your SimpleX address + SimpleX-osoitteesi + No comment provided by engineer. + + + Unit + Yksikkö + No comment provided by engineer. + + + Enter welcome message… (optional) + Kirjoita tervetuloviesti... (valinnainen) + placeholder + + + The hash of the previous message is different. + Edellisen viestin tarkiste on erilainen. + No comment provided by engineer. + + + Unlock app + Avaa sovellus + authentication reason + + + You can create it later + Voit luoda sen myöhemmin + No comment provided by engineer. + + + Delete file + Poista tiedosto + server test step + + + Delivery receipts are disabled! + Toimituskuittaukset poissa käytöstä! + No comment provided by engineer. + + + Disable (keep overrides) + Poista käytöstä (pidä ohitukset) + No comment provided by engineer. + + + Disable for all + Poista käytöstä kaikilta + No comment provided by engineer. + + + Disappearing message + Tuhoutuva viesti + No comment provided by engineer. + + + Disappears at: %@ + Katoaa klo: %@ + copied message info + + + Enable (keep overrides) + Salli (pidä ohitukset) + No comment provided by engineer. + + + Error synchronizing connection + Virhe yhteyden synkronoinnissa + No comment provided by engineer. + + + Even when disabled in the conversation. + Jopa kun ei käytössä keskustelussa. + No comment provided by engineer. + + + Favorite + Suosikki + No comment provided by engineer. + + + File will be deleted from servers. + Tiedosto poistetaan palvelimilta. + No comment provided by engineer. + + + Fix encryption after restoring backups. + Korjaa salaus varmuuskopioiden palauttamisen jälkeen. + No comment provided by engineer. + + + If you enter this passcode when opening the app, all app data will be irreversibly removed! + Jos syötät tämän pääsykoodin sovellusta avatessasi, kaikki sovelluksen tiedot poistetaan peruuttamattomasti! + No comment provided by engineer. + + + Info + Tiedot + chat item action + + + Migrating database archive… + Siirretään tietokannan arkistoa… + No comment provided by engineer. + + + No filtered chats + Ei suodatettuja keskusteluja + No comment provided by engineer. + + + Only group owners can enable files and media. + Vain ryhmän omistajat voivat sallia tiedostoja ja mediaa. + No comment provided by engineer. + + + Passcode changed! + Pääsykoodi vaihdettu! + No comment provided by engineer. + + + Permanent decryption error + Pysyvä salauksen purkuvirhe + message decrypt error item + + + Protocol timeout per KB + Protokollan aikakatkaisu per KB + No comment provided by engineer. + + + Receiving address will be changed to a different server. Address change will complete after sender comes online. + Vastaanotto-osoite vaihdetaan toiseen palvelimeen. Osoitteenmuutos tehdään sen jälkeen, kun lähettäjä tulee verkkoon. + No comment provided by engineer. + + + Reconnect servers? + Yhdistä palvelimet uudelleen? + No comment provided by engineer. + + + Record updated at + Tietue päivitetty klo + No comment provided by engineer. + + + Renegotiate + Neuvottele uudelleen + No comment provided by engineer. + + + Send delivery receipts to + Lähetä toimituskuittaukset vastaanottajalle + No comment provided by engineer. + + + Self-destruct passcode changed! + Itsetuhoutuva pääsykoodi vaihdettu! + No comment provided by engineer. + + + Sending file will be stopped. + Tiedoston lähettäminen lopetetaan. + No comment provided by engineer. + + + Stop file + Pysäytä tiedosto + cancel file action + + + Stop sharing + Lopeta jakaminen + No comment provided by engineer. + + + Stop sharing address? + Lopeta osoitteen jakaminen? + No comment provided by engineer. + + + The second tick we missed! ✅ + Toinen kuittaus, joka uupui! ✅ + No comment provided by engineer. + + + To connect, your contact can scan QR code or use the link in the app. + Kontaktisi voi muodostaa yhteyden skannaamalla QR-koodin tai käyttämällä sovelluksessa olevaa linkkiä. + No comment provided by engineer. + + + Unfav. + Epäsuotuisa. + No comment provided by engineer. + + + Unhide profile + Näytä profiili + No comment provided by engineer. + + + Videos and files up to 1gb + Videot ja tiedostot 1 Gt asti + No comment provided by engineer. + + + When people request to connect, you can accept or reject it. + Kun ihmiset pyytävät yhteyden muodostamista, voit hyväksyä tai hylätä sen. + No comment provided by engineer. + + + You can enable them later via app Privacy & Security settings. + Voit ottaa ne käyttöön myöhemmin sovelluksen Yksityisyys & Turvallisuus -asetuksista. + No comment provided by engineer. + + + You won't lose your contacts if you later delete your address. + Et menetä kontaktejasi, jos poistat osoitteesi myöhemmin. + No comment provided by engineer. + + + Your %@ servers + %@-palvelimesi + No comment provided by engineer. + + + Your XFTP servers + XFTP-palvelimesi + No comment provided by engineer. + + + changing address for %@… + osoitteen muuttaminen %@:lle… + chat item text + + + changing address… + muuttamassa osoitetta… + chat item text + + + default (no) + oletusarvo (ei) + No comment provided by engineer. + + + default (yes) + oletusarvo (kyllä) + No comment provided by engineer. + + + database version is newer than the app, but no down migration for: %@ + tietokantaversio on uudempi kuin sovellus, mutta ei alaspäin siirtymistä varten: %@ + No comment provided by engineer. + + + encryption agreed for %@ + salaus sovittu %@:lle + chat item text + + + encryption ok + salaus ok + chat item text + + + encryption agreed + salaus sovittu + chat item text + + + encryption re-negotiation required for %@ + tarvitaan salauksen uudelleenneuvottelu %@:lle + chat item text + + + hours + tuntia + time unit + + + months + kuukautta + time unit + + + Enable self-destruct passcode + Ota itsetuhoava pääsykoodi käyttöön + set passcode view + + + Hide: + Piilota: + No comment provided by engineer. + + + Read more in [User Guide](https://simplex.chat/docs/guide/readme.html#connect-to-friends). + Lue lisää [Käyttöoppaasta](https://simplex.chat/docs/guide/readme.html#connect-to-friends). + No comment provided by engineer. + + + Received at + Vastaanotettu klo + No comment provided by engineer. + + + Received at: %@ + Vastaanotettu klo: %@ + copied message info + + + Delete profile + Poista profiili + No comment provided by engineer. + + + Make sure %@ server addresses are in correct format, line separated and are not duplicated (%@). + Varmista, että %@-palvelinosoitteet ovat oikeassa muodossa, että ne on erotettu toisistaan riveittäin ja että ne eivät ole päällekkäisiä (%@). + No comment provided by engineer. + + + Receiving file will be stopped. + Tiedoston vastaanotto pysäytetään. + No comment provided by engineer. + + + Revoke file + Peruuta tiedosto + cancel file action + + + Revoke file? + Peruuta tiedosto? + No comment provided by engineer. + + + %1$@ at %2$@: + %1$@ klo %2$@: + copied message info, <sender> at <time> + + + Delivery receipts! + Toimituskuittaukset! + No comment provided by engineer. + + + It can happen when: +1. The messages expired in the sending client after 2 days or on the server after 30 days. +2. Message decryption failed, because you or your contact used old database backup. +3. The connection was compromised. + Se voi tapahtua, kun: +1. Viestit vanhenivat lähettävässä päätelaitteessa kahden päivän päästä tai palvelimella 30 päivän kuluttua. +2. Viestin salauksen purku epäonnistui, koska sinä tai kontaktisi käytitte vanhaa varmuuskopiota tietokannasta. +3. Yhteys vaarantui. + No comment provided by engineer. + + + Preview + Esikatselu + No comment provided by engineer. + + + SimpleX Address + SimpleX-osoite + No comment provided by engineer. + + + %@, %@ and %lld other members connected + %@, %@ ja %lld muut jäsenet yhdistetty + No comment provided by engineer. + + + Connect via contact link + Yhdistä kontaktilinkillä + No comment provided by engineer. + + + Connect via one-time link + Yhdistä kertalinkillä + No comment provided by engineer. + + + Database ID: %d + Tietokannan tunnus: %d + copied message info + + + Delivery + Toimitus + No comment provided by engineer. + + + Disappears at + Katoaa klo + No comment provided by engineer. + + + Download file + Lataa tiedosto + server test step + + + Enable for all + Salli kaikille + No comment provided by engineer. + + + Enter welcome message… + Kirjoita tervetuloviesti… + placeholder + + + Error aborting address change + Virhe osoitteenmuutoksen keskeytyksessä + No comment provided by engineer. + + + Error loading %@ servers + Virhe %@-palvelimien lataamisessa + No comment provided by engineer. + + + Error saving %@ servers + Virhe %@ palvelimien tallentamisessa + No comment provided by engineer. + + + Error saving passcode + Virhe pääsykoodin tallentamisessa + No comment provided by engineer. + + + Error sending email + Virhe sähköpostin lähettämisessä + No comment provided by engineer. + + + Error: + Virhe: + No comment provided by engineer. + + + Exporting database archive… + Tietokanta-arkiston vienti… + No comment provided by engineer. + + + Fast and no wait until the sender is online! + Nopea ja ei odotusta, kunnes lähettäjä on online-tilassa! + No comment provided by engineer. + + + Group members can send files and media. + Ryhmän jäsenet voivat lähettää tiedostoja ja mediaa. + No comment provided by engineer. + + + History + Historia + No comment provided by engineer. + + + If you can't meet in person, show QR code in a video call, or share the link. + Jos et voi tavata henkilökohtaisesti, näytä QR-koodi videopuhelussa tai jaa linkki. + No comment provided by engineer. + + + In reply to + Vastauksena + No comment provided by engineer. + + + Incognito mode protects your privacy by using a new random profile for each contact. + Incognito-tila suojaa yksityisyyttäsi käyttämällä uutta satunnaista profiilia jokaiselle kontaktille. + No comment provided by engineer. + + + It can happen when you or your connection used the old database backup. + Se voi tapahtua, kun sinä tai kontaktisi käytitte vanhaa varmuuskopiota tietokannasta. + No comment provided by engineer. + + + Keep your connections + Pidä kontaktisi + No comment provided by engineer. + + + Learn more + Lue lisää + No comment provided by engineer. + + + Lock after + Lukitse jälkeen + No comment provided by engineer. + + + Lock mode + Lukitustila + No comment provided by engineer. + + + Message delivery receipts! + Viestien toimituskuittaukset! + No comment provided by engineer. + + + Message reactions + Viestireaktiot + chat feature + + + Message reactions are prohibited in this chat. + Viestireaktiot ovat kiellettyjä tässä keskustelussa. + No comment provided by engineer. + + + Moderated at + Moderoitu klo + No comment provided by engineer. + + + Most likely this connection is deleted. + Todennäköisesti tämä yhteys on poistettu. + item status description + + + New Passcode + Uusi pääsykoodi + No comment provided by engineer. + + + No delivery information + Ei toimitustietoja + No comment provided by engineer. + + + No history + Ei historiaa + No comment provided by engineer. + + + Off + Pois + No comment provided by engineer. + + + Opening database… + Avataan tietokantaa… + No comment provided by engineer. + + + Reconnect all connected servers to force message delivery. It uses additional traffic. + Yhdistä kaikki yhdistetyt palvelimet uudelleen pakottaaksesi viestin toimituksen. Tämä käyttää ylimääräistä liikennettä. + No comment provided by engineer. + + + Renegotiate encryption? + Uudelleenneuvottele salaus? + No comment provided by engineer. + + + Sending receipts is enabled for %lld contacts + Kuittauksien lähettäminen on käytössä %lld kontakteille + No comment provided by engineer. + + + Sending receipts is enabled for %lld groups + Kuittauksien lähettäminen on käytössä %lld ryhmille + No comment provided by engineer. + + + Revoke + Peruuta + No comment provided by engineer. + + + Save auto-accept settings + Tallenna automaattisen hyväksynnän asetukset + No comment provided by engineer. + + + Self-destruct passcode + Itsetuhoutuva pääsykoodi + No comment provided by engineer. + + + Self-destruct passcode enabled! + Itsetuhoutuva pääsykoodi käytössä! + No comment provided by engineer. + + + The ID of the next message is incorrect (less or equal to the previous). +It can happen because of some bug or when the connection is compromised. + Seuraavan viestin tunnus on väärä (pienempi tai yhtä suuri kuin edellisen). +Tämä voi johtua jostain virheestä tai siitä, että yhteys on vaarantunut. + No comment provided by engineer. + + + The encryption is working and the new encryption agreement is not required. It may result in connection errors! + Salaus toimii ja uutta salaussopimusta ei tarvita. Tämä voi johtaa yhteysvirheisiin! + No comment provided by engineer. + + + Video will be received when your contact completes uploading it. + Video vastaanotetaan, kun kontaktisi on ladannut sen. + No comment provided by engineer. + + + You can enable later via Settings + Voit ottaa käyttöön myöhemmin asetusten kautta + No comment provided by engineer. + + + You can share this address with your contacts to let them connect with **%@**. + Voit jakaa tämän osoitteen kontaktiesi kanssa, jotta ne voivat muodostaa yhteyden **%@** kanssa. + No comment provided by engineer. + + + Your contacts in SimpleX will see it. +You can change it in Settings. + Kontaktisi SimpleX:ssä näkevät sen. +Voit muuttaa sitä Asetuksista. + No comment provided by engineer. + + + Your profile **%@** will be shared. + Profiilisi **%@** jaetaan. + No comment provided by engineer. + + + agreeing encryption for %@… + salauksesta sovitaan %@:lle… + chat item text + + + custom + mukautettu + dropdown time picker choice + + + days + päivää + time unit + + + encryption re-negotiation allowed for %@ + salauksen uudelleenneuvottelu sallittu %@:lle + chat item text + + + encryption re-negotiation required + tarvitaan salauksen uudelleenneuvottelu + chat item text + + + event happened + tapahtuma tapahtui + No comment provided by engineer. + + + security code changed + turvakoodi on muuttunut + chat item text + @@ -4253,24 +6345,29 @@ SimpleX servers cannot see your profile. - + SimpleX + SimpleX Bundle name - + SimpleX needs camera access to scan QR codes to connect to other users and for video calls. + SimpleX tarvitsee pääsyn kameraan, jotta se voi skannata QR-koodeja muodostaakseen yhteyden muihin käyttäjiin ja videopuheluita varten. Privacy - Camera Usage Description - + SimpleX uses Face ID for local authentication + SimpleX käyttää Face ID:tä paikalliseen todennukseen Privacy - Face ID Usage Description - + SimpleX needs microphone access for audio and video calls, and to record voice messages. + SimpleX tarvitsee mikrofonia ääni- ja videopuheluita ja ääniviestien tallentamista varten. Privacy - Microphone Usage Description - + SimpleX needs access to Photo Library for saving captured and received media + SimpleX tarvitsee pääsyn valokuvakirjastoon kuvattujen ja vastaanotettujen medioiden tallentamista varten Privacy - Photo Library Additions Usage Description @@ -4280,16 +6377,19 @@ SimpleX servers cannot see your profile. - + SimpleX NSE + SimpleX NSE Bundle display name - + SimpleX NSE + SimpleX NSE Bundle name - + Copyright © 2022 SimpleX Chat. All rights reserved. + Copyright © 2022 SimpleX Chat. Kaikki oikeudet pidätetään. Copyright (human-readable) 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 fcb2cdecc..113e00309 100644 --- a/apps/ios/SimpleX Localizations/fr.xcloc/Localized Contents/fr.xliff +++ b/apps/ios/SimpleX Localizations/fr.xcloc/Localized Contents/fr.xliff @@ -5725,7 +5725,7 @@ Les serveurs SimpleX ne peuvent pas voir votre profil. encryption ok - chiffrement ok + chiffrement OK chat item text diff --git a/apps/ios/SimpleX Localizations/fr.xcloc/Source Contents/SimpleX NSE/en.lproj/Localizable.strings b/apps/ios/SimpleX Localizations/fr.xcloc/Source Contents/SimpleX NSE/en.lproj/Localizable.strings deleted file mode 100644 index 8b1378917..000000000 --- a/apps/ios/SimpleX Localizations/fr.xcloc/Source Contents/SimpleX NSE/en.lproj/Localizable.strings +++ /dev/null @@ -1 +0,0 @@ - diff --git a/apps/ios/SimpleX Localizations/he.xcloc/Localized Contents/he.xliff b/apps/ios/SimpleX Localizations/he.xcloc/Localized Contents/he.xliff index 428cb8dfb..ac71ed26b 100644 --- a/apps/ios/SimpleX Localizations/he.xcloc/Localized Contents/he.xliff +++ b/apps/ios/SimpleX Localizations/he.xcloc/Localized Contents/he.xliff @@ -284,7 +284,7 @@ Available in v5.1 . - . + . No comment provided by engineer. @@ -1971,8 +1971,9 @@ Available in v5.1 חברי הקבוצה יכולים לשלוח הודעות קוליות. No comment provided by engineer. - + Group message: + הודעה קבוצתית: notification @@ -2377,262 +2378,327 @@ Available in v5.1 נתוני פרופיל מקומיים בלבד No comment provided by engineer. - + Lock after + נעל אחרי No comment provided by engineer. - + Lock mode + מצב נעילה No comment provided by engineer. - + Make a private connection + צור חיבור פרטי No comment provided by engineer. - + Make profile private! + הפוך את הפרופיל לפרטי! No comment provided by engineer. - + Make sure %@ server addresses are in correct format, line separated and are not duplicated (%@). + ודא שכתובות השרת %@ הן בפורמט הנכון, מופרדות בשורה ואינן משוכפלות (%@). No comment provided by engineer. - + Make sure WebRTC ICE server addresses are in correct format, line separated and are not duplicated. + ודאו שכתובות שרתי ה־WebRTC ICE הן בפורמט הנכון, מופרדות בשורה ולא משוכפלות. No comment provided by engineer. - + Many people asked: *if SimpleX has no user identifiers, how can it deliver messages?* + אנשים רבים שאלו: *אם ל-SimpleX אין מזהי משתמש, איך הוא יכול לשלוח הודעות?* No comment provided by engineer. - + Mark deleted for everyone + לסמן נמחק לכולם No comment provided by engineer. - + Mark read + סמן כנקרא No comment provided by engineer. - + Mark verified + סמן מאומת No comment provided by engineer. - + Markdown in messages + מרקדאון בהודעות No comment provided by engineer. - + Max 30 seconds, received instantly. + מקסימום 30 שניות, התקבל באופן מיידי. No comment provided by engineer. - + Member + חבר קבוצה No comment provided by engineer. - + Member role will be changed to "%@". All group members will be notified. + תפקיד חבר הקבוצה ישתנה ל-"%@". כל חברי הקבוצה יקבלו הודעה. No comment provided by engineer. - + Member role will be changed to "%@". The member will receive a new invitation. + תפקיד חבר הקבוצה ישתנה ל-"%@". חבר הקבוצה יקבל הזמנה חדשה. No comment provided by engineer. - + Member will be removed from group - this cannot be undone! + חבר הקבוצה יוסר מהקבוצה – לא ניתן לבטל זאת! No comment provided by engineer. - + Message delivery error + שגיאת מסירת הודעה No comment provided by engineer. - + Message draft + טיוטת הודעה No comment provided by engineer. - + Message text + טקסט הודעה No comment provided by engineer. - + Messages + הודעות No comment provided by engineer. - + Messages & files + הודעות וקבצים No comment provided by engineer. Migrating database archive... No comment provided by engineer. - + Migration error: + שגיאת העברה: No comment provided by engineer. - + Migration failed. Tap **Skip** below to continue using the current database. Please report the issue to the app developers via chat or email [chat@simplex.chat](mailto:chat@simplex.chat). + ההעברה נכשלה. הקש על **דלג** למטה כדי להמשיך להשתמש במסד הנתונים הנוכחי. אנא דווח על הבעיה למפתחי האפליקציה באמצעות צ'אט או דוא"ל [chat@simplex.chat](mailto:chat@simplex.chat). No comment provided by engineer. - + Migration is completed + ההעברה הושלמה No comment provided by engineer. - + Migrations: %@ + העברות: %@ No comment provided by engineer. - + Moderate + חסימת הודעה chat item action - + More improvements are coming soon! + שיפורים נוספים יגיעו בקרוב! No comment provided by engineer. - + Most likely this contact has deleted the connection with you. + ככל הנראה איש קשר זה מחק את החיבור איתך. No comment provided by engineer. - + Multiple chat profiles + פרופילי צ׳אט מרובים No comment provided by engineer. - + Mute + השתק No comment provided by engineer. - + Muted when inactive! + מושתק כאשר אין פעילות! No comment provided by engineer. - + Name + שם No comment provided by engineer. - + Network & servers + רשת ושרתים No comment provided by engineer. - + Network settings + הגדרות רשת No comment provided by engineer. - + Network status + מצב רשת No comment provided by engineer. - + New Passcode + קוד גישה חדש No comment provided by engineer. - + New contact request + בקשה חדשה ליצירת קשר notification - + New contact: + איש קשר חדש: notification - + New database archive + ארכיון מסד נתונים חדש No comment provided by engineer. - + New in %@ + חדש ב %@ No comment provided by engineer. - + New member role + תפקיד חבר קבוצה חדש No comment provided by engineer. - + New message + הודעה חדשה notification - + New passphrase… + סיסמה חדשה… No comment provided by engineer. - + No + לא No comment provided by engineer. - + No app password + אין סיסמה לאפליקציה Authentication unavailable - + No contacts selected + לא נבחרו אנשי קשר No comment provided by engineer. - + No contacts to add + אין אנשי קשר להוסיף No comment provided by engineer. - + No device token! + אין אסימון מכשיר! No comment provided by engineer. - + Group not found! + קבוצה לא נמצאה! No comment provided by engineer. - + No permission to record voice message + אין הרשאה להקליט הודעה קולית No comment provided by engineer. - + No received or sent files + לא התקבלו או נשלחו קבצים No comment provided by engineer. - + Notifications + התראות No comment provided by engineer. - + Notifications are disabled! + ההתראות מושבתות! No comment provided by engineer. - + Now admins can: - delete members' messages. - disable members ("observer" role) + כעת מנהלים יכולים: +- למחוק הודעות של חברי קבוצה. +- להשבית חברי קבוצה (תפקיד ”צופה”) No comment provided by engineer. - + Off + כבוי No comment provided by engineer. - + Off (Local) + כבוי (מקומי) No comment provided by engineer. - + Ok + אישור No comment provided by engineer. - + Old database + מסד נתונים ישן No comment provided by engineer. - + Old database archive + ארכיון מסד נתונים ישן No comment provided by engineer. - + One-time invitation link + קישור הזמנה חד־פעמי No comment provided by engineer. - + Onion hosts will be required for connection. Requires enabling VPN. + לחיבור יידרשו מארחי Onion. דורש הפעלת VPN. No comment provided by engineer. - + Onion hosts will be used when available. Requires enabling VPN. + מארחי Onion ישומשו כאשר יהיו זמינים. דורש הפעלת VPN. No comment provided by engineer. - + Onion hosts will not be used. + לא ייעשה שימוש במארחי Onion. No comment provided by engineer. @@ -4981,6 +5047,270 @@ SimpleX servers cannot see your profile. %1$@ בזמן %2$@: copied message info, <sender> at <time> + + # %@ + # %@ + copied message info title, # <title> + + + ## History + ## היסטוריה + copied message info + + + ## In reply to + ## בתגובה ל + copied message info + + + - more stable message delivery. +- a bit better groups. +- and more! + - שליחת הודעות יציבה יותר. +- קבוצות קצת יותר טובות. +- ועוד! + No comment provided by engineer. + + + A few more things + עוד כמה דברים + No comment provided by engineer. + + + A new random profile will be shared. + ישותף פרופיל אקראי חדש. + No comment provided by engineer. + + + Accept connection request? + לאשר בקשת חיבור? + No comment provided by engineer. + + + Connect directly + התחבר ישירות + No comment provided by engineer. + + + Connect incognito + התחבר בזהות נסתרת + No comment provided by engineer. + + + Connect via one-time link + התחבר באמצעות קישור חד־פעמי + No comment provided by engineer. + + + Contacts + אנשי קשר + No comment provided by engineer. + + + Delivery + מסירה + No comment provided by engineer. + + + Error synchronizing connection + שגיאה בסנכרון החיבור + No comment provided by engineer. + + + Fix encryption after restoring backups. + תקן הצפנה לאחר שחזור גיבויים. + No comment provided by engineer. + + + Fix not supported by group member + תיקון אינו נתמך על ידי חבר הקבוצה + No comment provided by engineer. + + + Invalid status + סטטוס לא חוקי + item status text + + + Migrating database archive… + מעביר את ארכיון מסד הנתונים… + No comment provided by engineer. + + + Moderated at: %@ + נחסם ב: %@ + copied message info + + + Most likely this connection is deleted. + סביר להניח שהחיבור הזה נמחק. + item status description + + + %@ and %@ connected + %@ ו-%@ מחוברים + No comment provided by engineer. + + + Connect via contact link + התחבר באמצעות קישור איש קשר + No comment provided by engineer. + + + Delivery receipts! + קבלות על המשלוח! + No comment provided by engineer. + + + Disable for all + השבת לכולם + No comment provided by engineer. + + + Error enabling delivery receipts! + שגיאה בהפעלת קבלות משלוח! + No comment provided by engineer. + + + Even when disabled in the conversation. + גם אם הוא מושבת בשיחה. + No comment provided by engineer. + + + Fix + תקן + No comment provided by engineer. + + + Fix connection + תקן את החיבור + No comment provided by engineer. + + + Find chats faster + מצא צ'אטים מהר יותר + No comment provided by engineer. + + + Fix connection? + לתקן את החיבור? + No comment provided by engineer. + + + Make one message disappear + העלם הודעה אחת + No comment provided by engineer. + + + Fix not supported by contact + תיקון לא נתמך על ידי איש קשר + No comment provided by engineer. + + + Incognito mode protects your privacy by using a new random profile for each contact. + מצב זהות נסתרת מגן על הפרטיות שלך על ידי שימוש בפרופיל אקראי חדש עבור כל איש קשר. + No comment provided by engineer. + + + In reply to + בתגובה ל + No comment provided by engineer. + + + Keep your connections + שימרו על הקשרים שלכם + No comment provided by engineer. + + + Message delivery receipts! + קבלות על הודעות! + No comment provided by engineer. + + + Message reactions are prohibited in this chat. + תגובות אמוג׳י להודעות אסורות בצ׳אט זה. + No comment provided by engineer. + + + Message reactions are prohibited in this group. + תגובות אמוג׳י להודעות אסורות בקבוצה זו. + No comment provided by engineer. + + + Delivery receipts are disabled! + קבלות על משלוח מושבתות! + No comment provided by engineer. + + + Disable (keep overrides) + השבת (שמור עקיפות) + No comment provided by engineer. + + + Don't enable + אל תפעיל + No comment provided by engineer. + + + Enable (keep overrides) + הפעל (שמור עקיפות) + No comment provided by engineer. + + + Enable for all + הפעל עבור כולם + No comment provided by engineer. + + + Error setting delivery receipts! + שגיאה בהגדרת קבלות משלוח! + No comment provided by engineer. + + + Filter unread and favorite chats. + סנן צ'אטים שלא נקראו וצ'אטים מועדפים. + No comment provided by engineer. + + + Moderated at + נחסם + No comment provided by engineer. + + + Message reactions + תגובות אמוג׳י להודעות + chat feature + + + Exporting database archive… + מייצא את ארכיון מסד הנתונים… + No comment provided by engineer. + + + %@, %@ and %lld other members connected + %@, %@ ו-%lld חברים אחרים מחוברים + No comment provided by engineer. + + + New display name + שם תצוגה חדש + No comment provided by engineer. + + + No delivery information + אין מידע על מסירה + No comment provided by engineer. + + + No filtered chats + אין צ'אטים מסוננים + No comment provided by engineer. + + + No history + ללא היסטוריה + No comment provided by engineer. + diff --git a/apps/ios/SimpleX Localizations/it.xcloc/Source Contents/SimpleX NSE/en.lproj/Localizable.strings b/apps/ios/SimpleX Localizations/it.xcloc/Source Contents/SimpleX NSE/en.lproj/Localizable.strings deleted file mode 100644 index 8b1378917..000000000 --- a/apps/ios/SimpleX Localizations/it.xcloc/Source Contents/SimpleX NSE/en.lproj/Localizable.strings +++ /dev/null @@ -1 +0,0 @@ - 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 b28d7391d..1bba3acde 100644 --- a/apps/ios/SimpleX Localizations/ja.xcloc/Localized Contents/ja.xliff +++ b/apps/ios/SimpleX Localizations/ja.xcloc/Localized Contents/ja.xliff @@ -44,14 +44,17 @@ # %@ + # %@ copied message info title, # <title> ## History + ## 履歴 copied message info ## In reply to + ## 返信先 copied message info @@ -86,10 +89,12 @@ %@ and %@ connected + %@ と %@ は接続中 No comment provided by engineer. %1$@ at %2$@: + %1$@ at %2$@: copied message info, <sender> at <time> @@ -119,6 +124,7 @@ %@, %@ and %lld other members connected + %@, %@ および %lld 人のメンバーが接続中 No comment provided by engineer. @@ -325,6 +331,9 @@ - more stable message delivery. - a bit better groups. - and more! + - より安定したメッセージ配信。 +- 改良されたグループ。 +- などなど! No comment provided by engineer. @@ -405,6 +414,7 @@ A few more things + その他 No comment provided by engineer. @@ -414,6 +424,7 @@ A new random profile will be shared. + 新しいランダムなプロファイルが共有されます。 No comment provided by engineer. @@ -430,14 +441,17 @@ Abort + 中止 No comment provided by engineer. Abort changing address + アドレス変更の中止 No comment provided by engineer. Abort changing address? + アドレス変更を中止しますか? No comment provided by engineer. @@ -523,6 +537,7 @@ Address change will be aborted. Old receiving address will be used. + アドレス変更は中止されます。古い受信アドレスが使用されます。 No comment provided by engineer. @@ -617,6 +632,7 @@ Allow to send files and media. + ファイルやメディアの送信を許可する。 No comment provided by engineer. @@ -791,6 +807,7 @@ Better messages + より良いメッセージ No comment provided by engineer. @@ -1051,10 +1068,12 @@ Connect directly + 直接接続する No comment provided by engineer. Connect incognito + シークレットモードで接続 No comment provided by engineer. @@ -1159,6 +1178,7 @@ Contacts + 連絡先 No comment provided by engineer. @@ -1556,10 +1576,12 @@ Delivery + Delivery No comment provided by engineer. Delivery receipts are disabled! + Delivery receipts are disabled! No comment provided by engineer. @@ -1613,6 +1635,7 @@ Disable (keep overrides) + 無効にする(設定の優先を維持) No comment provided by engineer. @@ -1622,6 +1645,7 @@ Disable for all + すべて無効 No comment provided by engineer. @@ -1686,6 +1710,7 @@ Don't enable + 有効にしない No comment provided by engineer. @@ -1730,6 +1755,7 @@ Enable (keep overrides) + 有効にする(設定の優先を維持) No comment provided by engineer. @@ -1749,6 +1775,7 @@ Enable for all + すべて有効 No comment provided by engineer. @@ -1868,6 +1895,7 @@ Error aborting address change + アドレス変更中止エラー No comment provided by engineer. @@ -2065,6 +2093,7 @@ Error synchronizing connection + 接続の同期エラー No comment provided by engineer. @@ -2109,6 +2138,7 @@ Even when disabled in the conversation. + 会話中に無効になっている場合でも。 No comment provided by engineer. @@ -2148,6 +2178,7 @@ Favorite + お気に入り No comment provided by engineer. @@ -2177,50 +2208,62 @@ Files and media + ファイルとメディア chat feature Files and media are prohibited in this group. + このグループでは、ファイルとメディアは禁止されています。 No comment provided by engineer. Files and media prohibited! + ファイルとメディアは禁止されています! No comment provided by engineer. Filter unread and favorite chats. + 未読とお気に入りをフィルターします。 No comment provided by engineer. Finally, we have them! 🚀 + ついに、私たちはそれらを手に入れました! 🚀 No comment provided by engineer. Find chats faster + チャットを素早く検索 No comment provided by engineer. Fix + 修正 No comment provided by engineer. Fix connection + 接続を修正 No comment provided by engineer. Fix connection? + 接続を修正しますか? No comment provided by engineer. Fix encryption after restoring backups. + バックアップの復元後に暗号化を修正します。 No comment provided by engineer. Fix not supported by contact + 連絡先による修正はサポートされていません No comment provided by engineer. Fix not supported by group member + グループメンバーによる修正はサポートされていません No comment provided by engineer. @@ -2330,6 +2373,7 @@ Group members can send files and media. + グループメンバーはファイルやメディアを送信できます。 No comment provided by engineer. @@ -2529,6 +2573,7 @@ In reply to + 返信先 No comment provided by engineer. @@ -2543,6 +2588,7 @@ Incognito mode protects your privacy by using a new random profile for each contact. + シークレットモードとは、メインのプロフィールとプロフィール画像を守るために、新しい連絡先を追加する時に、その連絡先に対してランダムなプロフィールが作成されるという対策です。 No comment provided by engineer. @@ -2619,6 +2665,7 @@ Invalid status + 無効なステータス item status text @@ -2714,6 +2761,7 @@ Keep your connections + 接続を維持 No comment provided by engineer. @@ -2808,6 +2856,7 @@ Make one message disappear + メッセージを1つ消す No comment provided by engineer. @@ -2966,6 +3015,7 @@ Most likely this connection is deleted. + おそらく、この接続は削除されています。 item status description @@ -3075,6 +3125,7 @@ No delivery information + 送信情報なし No comment provided by engineer. @@ -3084,6 +3135,7 @@ No filtered chats + フィルタされたチャットはありません No comment provided by engineer. @@ -3093,6 +3145,7 @@ No history + 履歴はありません No comment provided by engineer. @@ -3181,6 +3234,7 @@ Only group owners can enable files and media. + ファイルやメディアを有効にできるのは、グループオーナーだけです。 No comment provided by engineer. @@ -3505,6 +3559,7 @@ Prohibit sending files and media. + ファイルやメディアの送信を禁止します。 No comment provided by engineer. @@ -3529,6 +3584,7 @@ Protocol timeout per KB + KB あたりのプロトコル タイムアウト No comment provided by engineer. @@ -3543,6 +3599,7 @@ React… + 反応する… chat item menu @@ -3601,6 +3658,7 @@ Receiving address will be changed to a different server. Address change will complete after sender comes online. + 開発中の機能です!相手のクライアントが4.2でなければ機能しません。アドレス変更が完了すると、会話にメッセージが出ます。連絡相手 (またはグループのメンバー) からメッセージを受信できないかをご確認ください。 No comment provided by engineer. @@ -3620,10 +3678,12 @@ Reconnect all connected servers to force message delivery. It uses additional traffic. + 接続されているすべてのサーバーを再接続して、メッセージを強制的に配信します。 追加のトラフィックを使用します。 No comment provided by engineer. Reconnect servers? + サーバーに再接続しますか? No comment provided by engineer. @@ -3688,14 +3748,17 @@ Renegotiate + 再ネゴシエート No comment provided by engineer. Renegotiate encryption + 暗号化の再ネゴシエート No comment provided by engineer. Renegotiate encryption? + 暗号化を再ネゴシエートしますか? No comment provided by engineer. @@ -4182,6 +4245,7 @@ Show last messages + 最新のメッセージを表示 No comment provided by engineer. @@ -4266,10 +4330,12 @@ Small groups (max 20) + 小グループ(最大20名) No comment provided by engineer. Some non-fatal errors occurred during import - you may see Chat console for more details. + インポート中に致命的でないエラーが発生しました - 詳細はチャットコンソールを参照してください。 No comment provided by engineer. @@ -4486,6 +4552,7 @@ It can happen because of some bug or when the connection is compromised. The encryption is working and the new encryption agreement is not required. It may result in connection errors! + 暗号化は機能しており、新しい暗号化への同意は必要ありません。接続エラーが発生する可能性があります! No comment provided by engineer. @@ -4525,6 +4592,7 @@ It can happen because of some bug or when the connection is compromised. The second tick we missed! ✅ + 長らくお待たせしました! ✅ No comment provided by engineer. @@ -4554,10 +4622,12 @@ It can happen because of some bug or when the connection is compromised. These settings are for your current profile **%@**. + これらの設定は現在のプロファイル **%@** 用です。 No comment provided by engineer. They can be overridden in contact and group settings. + これらは連絡先の設定が優先します。 No comment provided by engineer. @@ -4688,6 +4758,7 @@ You will be prompted to complete authentication before this feature is enabled.< Unfav. + お気に入りを取り消す。 No comment provided by engineer. @@ -4819,6 +4890,7 @@ To connect, please ask your contact to create another connection link and check Use current profile + 現在のプロファイルを使用する No comment provided by engineer. @@ -4833,6 +4905,7 @@ To connect, please ask your contact to create another connection link and check Use new incognito profile + 新しいシークレットプロファイルを使用する No comment provided by engineer. @@ -5047,10 +5120,12 @@ To connect, please ask your contact to create another connection link and check You can enable later via Settings + あとで設定から有効にできます No comment provided by engineer. You can enable them later via app Privacy & Security settings. + あとでアプリのプライバシーとセキュリティの設定から有効にすることができます。 No comment provided by engineer. @@ -5309,6 +5384,7 @@ You can change it in Settings. Your profile **%@** will be shared. + あなたのプロファイル **%@** が共有されます。 No comment provided by engineer. @@ -5385,10 +5461,12 @@ SimpleX サーバーはあなたのプロファイルを参照できません。 agreeing encryption for %@… + %@の暗号化に同意しています… chat item text agreeing encryption… + 暗号化に同意しています… chat item text @@ -5453,10 +5531,12 @@ SimpleX サーバーはあなたのプロファイルを参照できません。 changing address for %@… + %@ のアドレスを変更しています… chat item text changing address… + アドレスを変更しています… chat item text @@ -5561,10 +5641,12 @@ SimpleX サーバーはあなたのプロファイルを参照できません。 default (no) + デフォルト(いいえ) No comment provided by engineer. default (yes) + デフォルト(はい) No comment provided by engineer. @@ -5589,6 +5671,7 @@ SimpleX サーバーはあなたのプロファイルを参照できません。 disabled + 無効 No comment provided by engineer. @@ -5618,34 +5701,42 @@ SimpleX サーバーはあなたのプロファイルを参照できません。 encryption agreed + 暗号化に同意しました chat item text encryption agreed for %@ + %@ の暗号化に同意しました chat item text encryption ok + 暗号化OK chat item text encryption ok for %@ + %@ の暗号化OK chat item text encryption re-negotiation allowed + 暗号化の再ネゴシエーションを許可 chat item text encryption re-negotiation allowed for %@ + %@ の暗号化の再ネゴシエーションを許可 chat item text encryption re-negotiation required + 暗号化の再ネゴシエーションが必要 chat item text encryption re-negotiation required for %@ + %@ の暗号化の再ネゴシエーションが必要 chat item text @@ -5665,6 +5756,7 @@ SimpleX サーバーはあなたのプロファイルを参照できません。 event happened + イベント発生 No comment provided by engineer. @@ -5834,6 +5926,7 @@ SimpleX サーバーはあなたのプロファイルを参照できません。 no text + テキストなし copied message info in history @@ -5924,6 +6017,7 @@ SimpleX サーバーはあなたのプロファイルを参照できません。 security code changed + セキュリティコードが変更されました chat item text diff --git a/apps/ios/SimpleX Localizations/ja.xcloc/Source Contents/SimpleX NSE/en.lproj/Localizable.strings b/apps/ios/SimpleX Localizations/ja.xcloc/Source Contents/SimpleX NSE/en.lproj/Localizable.strings deleted file mode 100644 index 8b1378917..000000000 --- a/apps/ios/SimpleX Localizations/ja.xcloc/Source Contents/SimpleX NSE/en.lproj/Localizable.strings +++ /dev/null @@ -1 +0,0 @@ - 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 b7a73f1f9..c32f79b78 100644 --- a/apps/ios/SimpleX Localizations/nl.xcloc/Localized Contents/nl.xliff +++ b/apps/ios/SimpleX Localizations/nl.xcloc/Localized Contents/nl.xliff @@ -244,7 +244,7 @@ %u messages failed to decrypt. - %u-berichten kunnen niet worden gedecodeerd. + %u berichten kunnen niet worden ontsleuteld. No comment provided by engineer. @@ -2713,7 +2713,7 @@ It can happen when you or your connection used the old database backup. - Het kan gebeuren wanneer u of uw verbinding de oude databaseback-up gebruikte. + Het kan gebeuren wanneer u of de ander een oude databaseback-up gebruikt. No comment provided by engineer. @@ -4919,7 +4919,7 @@ Om verbinding te maken, vraagt u uw contactpersoon om een andere verbinding link Use new incognito profile - Gebruik een nieuw incognito -profiel + Gebruik een nieuw incognitoprofiel No comment provided by engineer. diff --git a/apps/ios/SimpleX Localizations/nl.xcloc/Source Contents/SimpleX NSE/en.lproj/Localizable.strings b/apps/ios/SimpleX Localizations/nl.xcloc/Source Contents/SimpleX NSE/en.lproj/Localizable.strings deleted file mode 100644 index 8b1378917..000000000 --- a/apps/ios/SimpleX Localizations/nl.xcloc/Source Contents/SimpleX NSE/en.lproj/Localizable.strings +++ /dev/null @@ -1 +0,0 @@ - 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 767dcccba..68bc9b929 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 %@ connected + %@ i %@ połączeni No comment provided by engineer. @@ -123,6 +124,7 @@ %@, %@ and %lld other members connected + %@, %@ i %lld innych członków połączeni No comment provided by engineer. @@ -4256,6 +4258,7 @@ Show last messages + Pokaż ostatnie wiadomości No comment provided by engineer. @@ -5767,6 +5770,7 @@ Serwery SimpleX nie mogą zobaczyć Twojego profilu. event happened + nowe wydarzenie No comment provided by engineer. diff --git a/apps/ios/SimpleX Localizations/pl.xcloc/Source Contents/SimpleX NSE/en.lproj/Localizable.strings b/apps/ios/SimpleX Localizations/pl.xcloc/Source Contents/SimpleX NSE/en.lproj/Localizable.strings deleted file mode 100644 index 8b1378917..000000000 --- a/apps/ios/SimpleX Localizations/pl.xcloc/Source Contents/SimpleX NSE/en.lproj/Localizable.strings +++ /dev/null @@ -1 +0,0 @@ - diff --git a/apps/ios/SimpleX Localizations/ru.xcloc/Source Contents/SimpleX NSE/en.lproj/Localizable.strings b/apps/ios/SimpleX Localizations/ru.xcloc/Source Contents/SimpleX NSE/en.lproj/Localizable.strings deleted file mode 100644 index 8b1378917..000000000 --- a/apps/ios/SimpleX Localizations/ru.xcloc/Source Contents/SimpleX NSE/en.lproj/Localizable.strings +++ /dev/null @@ -1 +0,0 @@ - diff --git a/apps/ios/SimpleX Localizations/th.xcloc/Source Contents/SimpleX NSE/en.lproj/Localizable.strings b/apps/ios/SimpleX Localizations/th.xcloc/Source Contents/SimpleX NSE/en.lproj/Localizable.strings deleted file mode 100644 index 8b1378917..000000000 --- a/apps/ios/SimpleX Localizations/th.xcloc/Source Contents/SimpleX NSE/en.lproj/Localizable.strings +++ /dev/null @@ -1 +0,0 @@ - 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 eda74c5fd..d77ef1e81 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 @@ -44,14 +44,17 @@ # %@ + # %@ copied message info title, # <title> ## History + ## 历史 copied message info ## In reply to + ## 回复 copied message info @@ -86,10 +89,12 @@ %@ and %@ connected + %@ 和%@ 以建立连接 No comment provided by engineer. %1$@ at %2$@: + %2$@: copied message info, <sender> at <time> @@ -119,6 +124,7 @@ %@, %@ and %lld other members connected + %@, %@ 和 %lld 个成员 No comment provided by engineer. @@ -325,6 +331,9 @@ - more stable message delivery. - a bit better groups. - and more! + - 更稳定的传输! +- 更好的社群! +- 以及更多! No comment provided by engineer. @@ -405,6 +414,7 @@ A few more things + No comment provided by engineer. @@ -414,6 +424,7 @@ A new random profile will be shared. + 创建一个随机的共享文件 No comment provided by engineer. diff --git a/apps/ios/SimpleX Localizations/zh-Hans.xcloc/Source Contents/SimpleX NSE/en.lproj/Localizable.strings b/apps/ios/SimpleX Localizations/zh-Hans.xcloc/Source Contents/SimpleX NSE/en.lproj/Localizable.strings deleted file mode 100644 index 8b1378917..000000000 --- a/apps/ios/SimpleX Localizations/zh-Hans.xcloc/Source Contents/SimpleX NSE/en.lproj/Localizable.strings +++ /dev/null @@ -1 +0,0 @@ - diff --git a/apps/ios/cs.lproj/Localizable.strings b/apps/ios/cs.lproj/Localizable.strings index ef5a2e2d2..3d7d5f8fe 100644 --- a/apps/ios/cs.lproj/Localizable.strings +++ b/apps/ios/cs.lproj/Localizable.strings @@ -88,6 +88,15 @@ /* No comment provided by engineer. */ "*bold*" = "\\*tučně*"; +/* copied message info title, # */ +"# %@" = "# %@"; + +/* copied message info */ +"## History" = "## Historie"; + +/* copied message info */ +"## In reply to" = "## Odpovídáno"; + /* No comment provided by engineer. */ "#secret#" = "#tajný#"; @@ -106,6 +115,9 @@ /* No comment provided by engineer. */ "%@ %@" = "%@ %@"; +/* No comment provided by engineer. */ +"%@ and %@ connected" = "%@ a %@ připojen"; + /* copied message info, <sender> at <time> */ "%@ at %@:" = "%1$@ na %2$@:"; @@ -124,6 +136,9 @@ /* notification title */ "%@ wants to connect!" = "%@ se chce připojit!"; +/* No comment provided by engineer. */ +"%@, %@ and %lld other members connected" = "%@, %@ a %lld ostatní členové připojeni"; + /* copied message info */ "%@:" = "%@:"; @@ -282,7 +297,7 @@ "Accept" = "Přijmout"; /* No comment provided by engineer. */ -"Accept connection request?" = "Přijmout kontakt"; +"Accept connection request?" = "Přijmout kontakt?"; /* notification body */ "Accept contact request from %@?" = "Přijmout žádost o kontakt od %@?"; @@ -693,11 +708,17 @@ /* server test step */ "Connect" = "Připojit"; +/* No comment provided by engineer. */ +"Connect directly" = "Připojit přímo"; + +/* No comment provided by engineer. */ +"Connect incognito" = "Spojit se inkognito"; + /* No comment provided by engineer. */ "connect to SimpleX Chat developers." = "připojit se k vývojářům SimpleX Chat."; /* No comment provided by engineer. */ -"Connect via contact link" = "Připojit se přes kontaktní odkaz?"; +"Connect via contact link" = "Připojit se přes odkaz"; /* No comment provided by engineer. */ "Connect via group link?" = "Připojit se přes odkaz skupiny?"; @@ -709,7 +730,7 @@ "Connect via link / QR code" = "Připojit se prostřednictvím odkazu / QR kódu"; /* No comment provided by engineer. */ -"Connect via one-time link" = "Připojit se jednorázovým odkazem?"; +"Connect via one-time link" = "Připojit se jednorázovým odkazem"; /* No comment provided by engineer. */ "connected" = "připojeno"; @@ -1053,6 +1074,9 @@ /* rcv group event chat item */ "deleted group" = "odstraněna skupina"; +/* No comment provided by engineer. */ +"Delivery" = "Doručenka"; + /* No comment provided by engineer. */ "Delivery receipts are disabled!" = "Potvrzení o doručení jsou vypnuté!"; @@ -1722,6 +1746,9 @@ /* No comment provided by engineer. */ "Incognito mode" = "Režim inkognito"; +/* No comment provided by engineer. */ +"Incognito mode protects your privacy by using a new random profile for each contact." = "Režim inkognito chrání vaše soukromí používáním nového náhodného profilu pro každý kontakt."; + /* chat list item description */ "incognito via contact address link" = "inkognito přes odkaz na kontaktní adresu"; @@ -1785,6 +1812,9 @@ /* No comment provided by engineer. */ "Invalid server address!" = "Neplatná adresa serveru!"; +/* item status text */ +"Invalid status" = "Neplatný status"; + /* No comment provided by engineer. */ "Invitation expired!" = "Platnost pozvánky vypršela!"; @@ -1858,10 +1888,10 @@ "Join group" = "Připojit ke skupině"; /* No comment provided by engineer. */ -"Join incognito" = "Připojte se inkognito"; +"Join incognito" = "Připojit se inkognito"; /* No comment provided by engineer. */ -"Joining group" = "Připojení ke skupině"; +"Joining group" = "Připojování ke skupině"; /* No comment provided by engineer. */ "Keep your connections" = "Zachovat vaše připojení"; @@ -2046,6 +2076,9 @@ /* No comment provided by engineer. */ "More improvements are coming soon!" = "Další vylepšení se chystají již brzy!"; +/* item status description */ +"Most likely this connection is deleted." = "Pravděpodobně je toto spojení smazáno."; + /* No comment provided by engineer. */ "Most likely this contact has deleted the connection with you." = "Tento kontakt s největší pravděpodobností smazal spojení s vámi."; @@ -3071,7 +3104,7 @@ "These settings are for your current profile **%@**." = "Toto nastavení je pro váš aktuální profil **%@**."; /* No comment provided by engineer. */ -"They can be overridden in contact and group settings." = "Mohou být přepsány v nastavení kontaktů"; +"They can be overridden in contact and group settings." = "Mohou být přepsány v nastavení kontaktů."; /* No comment provided by engineer. */ "This action cannot be undone - all received and sent files and media will be deleted. Low resolution pictures will remain." = "Tuto akci nelze vrátit zpět - všechny přijaté a odeslané soubory a média budou smazány. Obrázky s nízkým rozlišením zůstanou zachovány."; diff --git a/apps/ios/es.lproj/Localizable.strings b/apps/ios/es.lproj/Localizable.strings index d1cd5816a..e351114d7 100644 --- a/apps/ios/es.lproj/Localizable.strings +++ b/apps/ios/es.lproj/Localizable.strings @@ -281,7 +281,7 @@ "About SimpleX" = "Acerca de SimpleX"; /* No comment provided by engineer. */ -"About SimpleX address" = "Acerca de dirección SimpleX"; +"About SimpleX address" = "Acerca de la dirección SimpleX"; /* No comment provided by engineer. */ "About SimpleX Chat" = "Sobre SimpleX Chat"; @@ -832,7 +832,7 @@ "Create" = "Crear"; /* No comment provided by engineer. */ -"Create an address to let people connect with you." = "Crear una dirección para que otras personas se puedan conectar contigo."; +"Create an address to let people connect with you." = "Crea una dirección para que otras personas puedan conectar contigo."; /* server test step */ "Create file" = "Crear archivo"; @@ -853,10 +853,10 @@ "Create secret group" = "Crea grupo secreto"; /* No comment provided by engineer. */ -"Create SimpleX address" = "Crear dirección SimpleX"; +"Create SimpleX address" = "Crear tu dirección SimpleX"; /* No comment provided by engineer. */ -"Create your profile" = "Crear tu perfil"; +"Create your profile" = "Crea tu perfil"; /* No comment provided by engineer. */ "Created on %@" = "Creado en %@"; @@ -943,7 +943,7 @@ "days" = "días"; /* No comment provided by engineer. */ -"Decentralized" = "Descentralizado"; +"Decentralized" = "Descentralizada"; /* message decrypt error item */ "Decryption error" = "Error descifrado"; @@ -1162,7 +1162,7 @@ "Do NOT use SimpleX for emergency calls." = "NO uses SimpleX para llamadas de emergencia."; /* No comment provided by engineer. */ -"Don't create address" = "No crear dirección"; +"Don't create address" = "No crear dirección SimpleX"; /* No comment provided by engineer. */ "Don't enable" = "No activar"; @@ -1981,7 +1981,7 @@ "Mark verified" = "Marcar como verificado"; /* No comment provided by engineer. */ -"Markdown in messages" = "Sintaxis markdown en los mensajes"; +"Markdown in messages" = "Sintaxis Markdown"; /* marked deleted chat item preview text */ "marked deleted" = "marcado eliminado"; @@ -2510,7 +2510,7 @@ "Received message" = "Mensaje entrante"; /* No comment provided by engineer. */ -"Receiving address will be changed to a different server. Address change will complete after sender comes online." = "La dirección de recepción se cambiará. El cambio se completará cuando el remitente esté en línea."; +"Receiving address will be changed to a different server. Address change will complete after sender comes online." = "La dirección de recepción pasará a otro servidor. El cambio se completará cuando el remitente esté en línea."; /* No comment provided by engineer. */ "Receiving file will be stopped." = "Se detendrá la recepción del archivo."; @@ -2966,7 +2966,7 @@ "Stop" = "Detener"; /* No comment provided by engineer. */ -"Stop chat to enable database actions" = "Para habilitar las acciones sobre la base de datos, previamente debes detener Chat"; +"Stop chat to enable database actions" = "Detén SimpleX para habilitar las acciones sobre la base de datos"; /* No comment provided by engineer. */ "Stop chat to export, import or delete chat database. You will not be able to receive and send messages while the chat is stopped." = "Para poder exportar, importar o eliminar la base de datos primero debes detener Chat. Durante el tiempo que esté detenido no podrás recibir ni enviar mensajes."; @@ -3095,7 +3095,7 @@ "The message will be marked as moderated for all members." = "El mensaje será marcado como moderado para todos los miembros."; /* No comment provided by engineer. */ -"The next generation of private messaging" = "La próxima generación de mensajería privada"; +"The next generation of private messaging" = "La nueva generación de mensajería privada"; /* No comment provided by engineer. */ "The old database was not removed during the migration, it can be deleted." = "La base de datos antigua no se eliminó durante la migración, puede eliminarse."; @@ -3464,7 +3464,7 @@ "You can also connect by clicking the link. If it opens in the browser, click **Open in mobile app** button." = "También puedes conectarte haciendo clic en el enlace. Si se abre en el navegador, haz clic en el botón **Abrir en aplicación móvil**."; /* No comment provided by engineer. */ -"You can create it later" = "Puedes crearlo más tarde"; +"You can create it later" = "Puedes crearla más tarde"; /* No comment provided by engineer. */ "You can enable later via Settings" = "Puedes activar más tarde en Configuración"; @@ -3599,7 +3599,7 @@ "Your calls" = "Llamadas"; /* No comment provided by engineer. */ -"Your chat database" = "Base de datos Chat"; +"Your chat database" = "Base de datos"; /* No comment provided by engineer. */ "Your chat database is not encrypted - set passphrase to encrypt it." = "La base de datos no está cifrada - establece una contraseña para cifrarla."; @@ -3647,7 +3647,7 @@ "Your profile is stored on your device and shared only with your contacts.\nSimpleX servers cannot see your profile." = "Tu perfil se almacena en tu dispositivo y sólo se comparte con tus contactos.\nLos servidores de SimpleX no pueden ver tu perfil."; /* No comment provided by engineer. */ -"Your profile, contacts and delivered messages are stored on your device." = "Tu perfil, contactos y mensajes entregados se almacenan en tu dispositivo."; +"Your profile, contacts and delivered messages are stored on your device." = "Tu perfil, contactos y mensajes se almacenan en tu dispositivo."; /* No comment provided by engineer. */ "Your random profile" = "Tu perfil aleatorio"; diff --git a/apps/ios/fr.lproj/Localizable.strings b/apps/ios/fr.lproj/Localizable.strings index b2428f5e7..9ce7245cd 100644 --- a/apps/ios/fr.lproj/Localizable.strings +++ b/apps/ios/fr.lproj/Localizable.strings @@ -1273,7 +1273,7 @@ "encryption agreed for %@" = "chiffrement accepté pour %@"; /* chat item text */ -"encryption ok" = "chiffrement ok"; +"encryption ok" = "chiffrement OK"; /* chat item text */ "encryption ok for %@" = "chiffrement ok pour %@"; diff --git a/apps/ios/ja.lproj/Localizable.strings b/apps/ios/ja.lproj/Localizable.strings index 7a00b096b..3d78a9f6e 100644 --- a/apps/ios/ja.lproj/Localizable.strings +++ b/apps/ios/ja.lproj/Localizable.strings @@ -19,6 +19,9 @@ /* No comment provided by engineer. */ "_italic_" = "\\_斜体_"; +/* No comment provided by engineer. */ +"- more stable message delivery.\n- a bit better groups.\n- and more!" = "- より安定したメッセージ配信。\n- 改良されたグループ。\n- などなど!"; + /* No comment provided by engineer. */ "- voice messages up to 5 minutes.\n- custom time to disappear.\n- editing history." = "- 最長 5 分間の音声メッセージ。\n- 消えるまでのカスタム時間。\n- 編集履歴。"; @@ -85,6 +88,15 @@ /* No comment provided by engineer. */ "*bold*" = "\\*太文字*"; +/* copied message info title, # <title> */ +"# %@" = "# %@"; + +/* copied message info */ +"## History" = "## 履歴"; + +/* copied message info */ +"## In reply to" = "## 返信先"; + /* No comment provided by engineer. */ "#secret#" = "シークレット"; @@ -103,6 +115,12 @@ /* No comment provided by engineer. */ "%@ %@" = "%@ %@"; +/* No comment provided by engineer. */ +"%@ and %@ connected" = "%@ と %@ は接続中"; + +/* copied message info, <sender> at <time> */ +"%@ at %@:" = "%1$@ at %2$@:"; + /* notification title */ "%@ is connected!" = "%@ 接続中!"; @@ -118,6 +136,9 @@ /* notification title */ "%@ wants to connect!" = "%@ が接続を希望しています!"; +/* No comment provided by engineer. */ +"%@, %@ and %lld other members connected" = "%@, %@ および %lld 人のメンバーが接続中"; + /* copied message info */ "%@:" = "%@:"; @@ -232,15 +253,30 @@ /* No comment provided by engineer. */ "30 seconds" = "30秒"; +/* No comment provided by engineer. */ +"A few more things" = "その他"; + /* notification title */ "A new contact" = "新しい連絡先"; +/* No comment provided by engineer. */ +"A new random profile will be shared." = "新しいランダムなプロファイルが共有されます。"; + /* No comment provided by engineer. */ "A separate TCP connection will be used **for each chat profile you have in the app**." = "**アプリ内のチャット プロフィールごとに**、個別の TCP 接続が使用されます。"; /* No comment provided by engineer. */ "A separate TCP connection will be used **for each contact and group member**.\n**Please note**: if you have many connections, your battery and traffic consumption can be substantially higher and some connections may fail." = "**アプリ内のチャット プロファイルごとに**、個別の TCP 接続が使用されます。\n**注意**:多くの接続がある場合、バッテリーと通信量の消費が大幅に増加し、一部の接続に失敗することがあります。"; +/* No comment provided by engineer. */ +"Abort" = "中止"; + +/* No comment provided by engineer. */ +"Abort changing address" = "アドレス変更の中止"; + +/* No comment provided by engineer. */ +"Abort changing address?" = "アドレス変更を中止しますか?"; + /* No comment provided by engineer. */ "About SimpleX" = "SimpleXについて"; @@ -296,6 +332,9 @@ /* No comment provided by engineer. */ "Address" = "アドレス"; +/* No comment provided by engineer. */ +"Address change will be aborted. Old receiving address will be used." = "アドレス変更は中止されます。古い受信アドレスが使用されます。"; + /* member role */ "admin" = "管理者"; @@ -305,6 +344,12 @@ /* No comment provided by engineer. */ "Advanced network settings" = "ネットワーク詳細設定"; +/* chat item text */ +"agreeing encryption for %@…" = "%@の暗号化に同意しています…"; + +/* chat item text */ +"agreeing encryption…" = "暗号化に同意しています…"; + /* No comment provided by engineer. */ "All app data is deleted." = "すべてのアプリデータが削除されます。"; @@ -353,6 +398,9 @@ /* No comment provided by engineer. */ "Allow to irreversibly delete sent messages." = "送信済みメッセージの永久削除を許可する。"; +/* No comment provided by engineer. */ +"Allow to send files and media." = "ファイルやメディアの送信を許可する。"; + /* No comment provided by engineer. */ "Allow to send voice messages." = "音声メッセージの送信を許可する。"; @@ -467,6 +515,9 @@ /* No comment provided by engineer. */ "Bad message ID" = "メッセージ ID が正しくありません"; +/* No comment provided by engineer. */ +"Better messages" = "より良いメッセージ"; + /* No comment provided by engineer. */ "bold" = "太文字"; @@ -564,6 +615,12 @@ /* rcv group event chat item */ "changed your role to %@" = "あなたの役割を %@ に変更しました"; +/* chat item text */ +"changing address for %@…" = "%@ のアドレスを変更しています…"; + +/* chat item text */ +"changing address…" = "アドレスを変更しています…"; + /* No comment provided by engineer. */ "Chat archive" = "チャットのアーカイブ"; @@ -651,6 +708,12 @@ /* server test step */ "Connect" = "接続"; +/* No comment provided by engineer. */ +"Connect directly" = "直接接続する"; + +/* No comment provided by engineer. */ +"Connect incognito" = "シークレットモードで接続"; + /* No comment provided by engineer. */ "connect to SimpleX Chat developers." = "SimpleX Chat 開発者に接続します。"; @@ -750,6 +813,9 @@ /* No comment provided by engineer. */ "Contact preferences" = "連絡先の設定"; +/* No comment provided by engineer. */ +"Contacts" = "連絡先"; + /* No comment provided by engineer. */ "Contacts can mark messages for deletion; you will be able to view them." = "連絡先はメッセージを削除対象とすることができます。あなたには閲覧可能です。"; @@ -885,6 +951,12 @@ /* pref value */ "default (%@)" = "デフォルト (%@)"; +/* No comment provided by engineer. */ +"default (no)" = "デフォルト(いいえ)"; + +/* No comment provided by engineer. */ +"default (yes)" = "デフォルト(はい)"; + /* chat item action */ "Delete" = "削除"; @@ -1002,6 +1074,12 @@ /* rcv group event chat item */ "deleted group" = "削除されたグループ"; +/* No comment provided by engineer. */ +"Delivery" = "Delivery"; + +/* No comment provided by engineer. */ +"Delivery receipts are disabled!" = "Delivery receipts are disabled!"; + /* No comment provided by engineer. */ "Description" = "説明"; @@ -1035,9 +1113,18 @@ /* No comment provided by engineer. */ "Direct messages between members are prohibited in this group." = "このグループではメンバー間のダイレクトメッセージが使用禁止です。"; +/* No comment provided by engineer. */ +"Disable (keep overrides)" = "無効にする(設定の優先を維持)"; + +/* No comment provided by engineer. */ +"Disable for all" = "すべて無効"; + /* authentication reason */ "Disable SimpleX Lock" = "SimpleXロックを無効にする"; +/* No comment provided by engineer. */ +"disabled" = "無効"; + /* No comment provided by engineer. */ "Disappearing message" = "消えるメッセージ"; @@ -1074,6 +1161,9 @@ /* No comment provided by engineer. */ "Don't create address" = "アドレスを作成しないでください"; +/* No comment provided by engineer. */ +"Don't enable" = "有効にしない"; + /* No comment provided by engineer. */ "Don't show again" = "次から表示しない"; @@ -1104,9 +1194,15 @@ /* No comment provided by engineer. */ "Enable" = "有効"; +/* No comment provided by engineer. */ +"Enable (keep overrides)" = "有効にする(設定の優先を維持)"; + /* No comment provided by engineer. */ "Enable automatic message deletion?" = "自動メッセージ削除を有効にしますか?"; +/* No comment provided by engineer. */ +"Enable for all" = "すべて有効"; + /* No comment provided by engineer. */ "Enable instant notifications?" = "即時通知を有効にしますか?"; @@ -1167,6 +1263,30 @@ /* notification */ "Encrypted message: unexpected error" = "暗号化されたメッセージ : 予期しないエラー"; +/* chat item text */ +"encryption agreed" = "暗号化に同意しました"; + +/* chat item text */ +"encryption agreed for %@" = "%@ の暗号化に同意しました"; + +/* chat item text */ +"encryption ok" = "暗号化OK"; + +/* chat item text */ +"encryption ok for %@" = "%@ の暗号化OK"; + +/* chat item text */ +"encryption re-negotiation allowed" = "暗号化の再ネゴシエーションを許可"; + +/* chat item text */ +"encryption re-negotiation allowed for %@" = "%@ の暗号化の再ネゴシエーションを許可"; + +/* chat item text */ +"encryption re-negotiation required" = "暗号化の再ネゴシエーションが必要"; + +/* chat item text */ +"encryption re-negotiation required for %@" = "%@ の暗号化の再ネゴシエーションが必要"; + /* No comment provided by engineer. */ "ended" = "終了"; @@ -1200,6 +1320,9 @@ /* No comment provided by engineer. */ "Error" = "エラー"; +/* No comment provided by engineer. */ +"Error aborting address change" = "アドレス変更中止エラー"; + /* No comment provided by engineer. */ "Error accepting contact request" = "連絡先リクエストの承諾にエラー発生"; @@ -1311,6 +1434,9 @@ /* No comment provided by engineer. */ "Error switching profile!" = "プロフィール切り替えにエラー発生!"; +/* No comment provided by engineer. */ +"Error synchronizing connection" = "接続の同期エラー"; + /* No comment provided by engineer. */ "Error updating group link" = "グループのリンクのアップデートにエラー発生"; @@ -1335,6 +1461,12 @@ /* No comment provided by engineer. */ "Error: URL is invalid" = "エラー: 無効なURL"; +/* No comment provided by engineer. */ +"Even when disabled in the conversation." = "会話中に無効になっている場合でも。"; + +/* No comment provided by engineer. */ +"event happened" = "イベント発生"; + /* No comment provided by engineer. */ "Exit without saving" = "保存せずに閉じる"; @@ -1356,6 +1488,9 @@ /* No comment provided by engineer. */ "Fast and no wait until the sender is online!" = "送信者がオンラインになるまでの待ち時間がなく、速い!"; +/* No comment provided by engineer. */ +"Favorite" = "お気に入り"; + /* No comment provided by engineer. */ "File will be deleted from servers." = "ファイルはサーバーから削除されます。"; @@ -1371,6 +1506,42 @@ /* No comment provided by engineer. */ "Files & media" = "ファイルとメディア"; +/* chat feature */ +"Files and media" = "ファイルとメディア"; + +/* No comment provided by engineer. */ +"Files and media are prohibited in this group." = "このグループでは、ファイルとメディアは禁止されています。"; + +/* No comment provided by engineer. */ +"Files and media prohibited!" = "ファイルとメディアは禁止されています!"; + +/* No comment provided by engineer. */ +"Filter unread and favorite chats." = "未読とお気に入りをフィルターします。"; + +/* No comment provided by engineer. */ +"Finally, we have them! 🚀" = "ついに、私たちはそれらを手に入れました! 🚀"; + +/* No comment provided by engineer. */ +"Find chats faster" = "チャットを素早く検索"; + +/* No comment provided by engineer. */ +"Fix" = "修正"; + +/* No comment provided by engineer. */ +"Fix connection" = "接続を修正"; + +/* No comment provided by engineer. */ +"Fix connection?" = "接続を修正しますか?"; + +/* No comment provided by engineer. */ +"Fix encryption after restoring backups." = "バックアップの復元後に暗号化を修正します。"; + +/* No comment provided by engineer. */ +"Fix not supported by contact" = "連絡先による修正はサポートされていません"; + +/* No comment provided by engineer. */ +"Fix not supported by group member" = "グループメンバーによる修正はサポートされていません"; + /* No comment provided by engineer. */ "For console" = "コンソール"; @@ -1437,6 +1608,9 @@ /* No comment provided by engineer. */ "Group members can send disappearing messages." = "グループのメンバーが消えるメッセージを送信できます。"; +/* No comment provided by engineer. */ +"Group members can send files and media." = "グループメンバーはファイルやメディアを送信できます。"; + /* No comment provided by engineer. */ "Group members can send voice messages." = "グループのメンバーが音声メッセージを送信できます。"; @@ -1560,12 +1734,18 @@ /* No comment provided by engineer. */ "Improved server configuration" = "サーバ設定の向上"; +/* No comment provided by engineer. */ +"In reply to" = "返信先"; + /* No comment provided by engineer. */ "Incognito" = "シークレットモード"; /* No comment provided by engineer. */ "Incognito mode" = "シークレットモード"; +/* No comment provided by engineer. */ +"Incognito mode protects your privacy by using a new random profile for each contact." = "シークレットモードとは、メインのプロフィールとプロフィール画像を守るために、新しい連絡先を追加する時に、その連絡先に対してランダムなプロフィールが作成されるという対策です。"; + /* chat list item description */ "incognito via contact address link" = "連絡先リンク経由でシークレットモード"; @@ -1629,6 +1809,9 @@ /* No comment provided by engineer. */ "Invalid server address!" = "無効なサーバアドレス!"; +/* item status text */ +"Invalid status" = "無効なステータス"; + /* No comment provided by engineer. */ "Invitation expired!" = "招待が期限切れました!"; @@ -1707,6 +1890,9 @@ /* No comment provided by engineer. */ "Joining group" = "グループに参加"; +/* No comment provided by engineer. */ +"Keep your connections" = "接続を維持"; + /* No comment provided by engineer. */ "Keychain error" = "キーチェーンのエラー"; @@ -1764,6 +1950,9 @@ /* No comment provided by engineer. */ "Make a private connection" = "プライベートな接続をする"; +/* No comment provided by engineer. */ +"Make one message disappear" = "メッセージを1つ消す"; + /* No comment provided by engineer. */ "Make profile private!" = "プロフィールを非表示にできます!"; @@ -1881,6 +2070,9 @@ /* No comment provided by engineer. */ "More improvements are coming soon!" = "まだまだ改善してまいります!"; +/* item status description */ +"Most likely this connection is deleted." = "おそらく、この接続は削除されています。"; + /* No comment provided by engineer. */ "Most likely this contact has deleted the connection with you." = "恐らくこの連絡先があなたとの接続を削除しました。"; @@ -1953,21 +2145,33 @@ /* No comment provided by engineer. */ "No contacts to add" = "追加できる連絡先がありません"; +/* No comment provided by engineer. */ +"No delivery information" = "送信情報なし"; + /* No comment provided by engineer. */ "No device token!" = "デバイストークンがありません!"; /* No comment provided by engineer. */ "no e2e encryption" = "エンドツーエンド暗号化がありません"; +/* No comment provided by engineer. */ +"No filtered chats" = "フィルタされたチャットはありません"; + /* No comment provided by engineer. */ "No group!" = "グループが見つかりません!"; +/* No comment provided by engineer. */ +"No history" = "履歴はありません"; + /* No comment provided by engineer. */ "No permission to record voice message" = "音声メッセージを録音する権限がありません"; /* No comment provided by engineer. */ "No received or sent files" = "送受信済みのファイルがありません"; +/* copied message info in history */ +"no text" = "テキストなし"; + /* No comment provided by engineer. */ "Notifications" = "通知"; @@ -2026,6 +2230,9 @@ /* No comment provided by engineer. */ "Only group owners can change group preferences." = "グループ設定を変えられるのはグループのオーナーだけです。"; +/* No comment provided by engineer. */ +"Only group owners can enable files and media." = "ファイルやメディアを有効にできるのは、グループオーナーだけです。"; + /* No comment provided by engineer. */ "Only group owners can enable voice messages." = "音声メッセージを利用可能に設定できるのはグループのオーナーだけです。"; @@ -2227,6 +2434,9 @@ /* No comment provided by engineer. */ "Prohibit sending disappearing messages." = "消えるメッセージを使用禁止にする。"; +/* No comment provided by engineer. */ +"Prohibit sending files and media." = "ファイルやメディアの送信を禁止します。"; + /* No comment provided by engineer. */ "Prohibit sending voice messages." = "音声メッセージを使用禁止にする。"; @@ -2239,12 +2449,18 @@ /* No comment provided by engineer. */ "Protocol timeout" = "プロトコル・タイムアウト"; +/* No comment provided by engineer. */ +"Protocol timeout per KB" = "KB あたりのプロトコル タイムアウト"; + /* No comment provided by engineer. */ "Push notifications" = "プッシュ通知"; /* No comment provided by engineer. */ "Rate the app" = "アプリを評価"; +/* chat item menu */ +"React…" = "反応する…"; + /* No comment provided by engineer. */ "Read" = "読む"; @@ -2281,6 +2497,9 @@ /* message info title */ "Received message" = "受信したメッセージ"; +/* No comment provided by engineer. */ +"Receiving address will be changed to a different server. Address change will complete after sender comes online." = "開発中の機能です!相手のクライアントが4.2でなければ機能しません。アドレス変更が完了すると、会話にメッセージが出ます。連絡相手 (またはグループのメンバー) からメッセージを受信できないかをご確認ください。"; + /* No comment provided by engineer. */ "Receiving file will be stopped." = "ファイルの受信を停止します。"; @@ -2290,6 +2509,12 @@ /* No comment provided by engineer. */ "Recipients see updates as you type them." = "受信者には、入力時に更新内容が表示されます。"; +/* No comment provided by engineer. */ +"Reconnect all connected servers to force message delivery. It uses additional traffic." = "接続されているすべてのサーバーを再接続して、メッセージを強制的に配信します。 追加のトラフィックを使用します。"; + +/* No comment provided by engineer. */ +"Reconnect servers?" = "サーバーに再接続しますか?"; + /* No comment provided by engineer. */ "Record updated at" = "レコード更新日時"; @@ -2338,6 +2563,15 @@ /* rcv group event chat item */ "removed you" = "あなたを除名しました"; +/* No comment provided by engineer. */ +"Renegotiate" = "再ネゴシエート"; + +/* No comment provided by engineer. */ +"Renegotiate encryption" = "暗号化の再ネゴシエート"; + +/* No comment provided by engineer. */ +"Renegotiate encryption?" = "暗号化を再ネゴシエートしますか?"; + /* chat item action */ "Reply" = "返信"; @@ -2476,6 +2710,9 @@ /* No comment provided by engineer. */ "Security code" = "セキュリティコード"; +/* chat item text */ +"security code changed" = "セキュリティコードが変更されました"; + /* No comment provided by engineer. */ "Select" = "選択"; @@ -2614,6 +2851,9 @@ /* No comment provided by engineer. */ "Show developer options" = "開発者向けオプションを表示"; +/* No comment provided by engineer. */ +"Show last messages" = "最新のメッセージを表示"; + /* No comment provided by engineer. */ "Show preview" = "プレビューを表示"; @@ -2662,9 +2902,15 @@ /* No comment provided by engineer. */ "Skipped messages" = "飛ばしたメッセージ"; +/* No comment provided by engineer. */ +"Small groups (max 20)" = "小グループ(最大20名)"; + /* No comment provided by engineer. */ "SMP servers" = "SMPサーバ"; +/* No comment provided by engineer. */ +"Some non-fatal errors occurred during import - you may see Chat console for more details." = "インポート中に致命的でないエラーが発生しました - 詳細はチャットコンソールを参照してください。"; + /* notification title */ "Somebody" = "誰か"; @@ -2794,6 +3040,9 @@ /* No comment provided by engineer. */ "The created archive is available via app Settings / Database / Old database archive." = "作成されたアーカイブは、アプリの設定/データベース/過去のデータベースアーカイブから利用できます。"; +/* No comment provided by engineer. */ +"The encryption is working and the new encryption agreement is not required. It may result in connection errors!" = "暗号化は機能しており、新しい暗号化への同意は必要ありません。接続エラーが発生する可能性があります!"; + /* No comment provided by engineer. */ "The group is fully decentralized – it is visible only to the members." = "グループは完全分散型で、メンバーしか内容を見れません。"; @@ -2818,6 +3067,9 @@ /* No comment provided by engineer. */ "The profile is only shared with your contacts." = "プロフィールは連絡先にしか共有されません。"; +/* No comment provided by engineer. */ +"The second tick we missed! ✅" = "長らくお待たせしました! ✅"; + /* No comment provided by engineer. */ "The sender will NOT be notified" = "送信者には通知されません"; @@ -2833,6 +3085,12 @@ /* No comment provided by engineer. */ "There should be at least one visible user profile." = "少なくとも1つのユーザープロフィールが表示されている必要があります。"; +/* No comment provided by engineer. */ +"These settings are for your current profile **%@**." = "これらの設定は現在のプロファイル **%@** 用です。"; + +/* No comment provided by engineer. */ +"They can be overridden in contact and group settings." = "これらは連絡先の設定が優先します。"; + /* No comment provided by engineer. */ "This action cannot be undone - all received and sent files and media will be deleted. Low resolution pictures will remain." = "ファイルとメディアが全て削除されます (※元に戻せません※)。低解像度の画像が残ります。"; @@ -2908,6 +3166,9 @@ /* No comment provided by engineer. */ "Unexpected migration state" = "予期しない移行状態"; +/* No comment provided by engineer. */ +"Unfav." = "お気に入りを取り消す。"; + /* No comment provided by engineer. */ "Unhide" = "表示にする"; @@ -2986,12 +3247,18 @@ /* No comment provided by engineer. */ "Use chat" = "チャット"; +/* No comment provided by engineer. */ +"Use current profile" = "現在のプロファイルを使用する"; + /* No comment provided by engineer. */ "Use for new connections" = "新しい接続に使う"; /* No comment provided by engineer. */ "Use iOS call interface" = "iOS通話インターフェースを使用する"; +/* No comment provided by engineer. */ +"Use new incognito profile" = "新しいシークレットプロファイルを使用する"; + /* No comment provided by engineer. */ "Use server" = "サーバを使う"; @@ -3160,6 +3427,12 @@ /* No comment provided by engineer. */ "You can create it later" = "後からでも作成できます"; +/* No comment provided by engineer. */ +"You can enable later via Settings" = "あとで設定から有効にできます"; + +/* No comment provided by engineer. */ +"You can enable them later via app Privacy & Security settings." = "あとでアプリのプライバシーとセキュリティの設定から有効にすることができます。"; + /* No comment provided by engineer. */ "You can hide or mute a user profile - swipe it to the right." = "ユーザープロファイルを右にスワイプすると、非表示またはミュートにすることができます。"; @@ -3328,6 +3601,9 @@ /* No comment provided by engineer. */ "Your privacy" = "あなたのプライバシー"; +/* No comment provided by engineer. */ +"Your profile **%@** will be shared." = "あなたのプロファイル **%@** が共有されます。"; + /* No comment provided by engineer. */ "Your profile is stored on your device and shared only with your contacts.\nSimpleX servers cannot see your profile." = "プロフィールはデバイスに保存され、連絡先とのみ共有されます。\nSimpleX サーバーはあなたのプロファイルを参照できません。"; diff --git a/apps/ios/nl.lproj/Localizable.strings b/apps/ios/nl.lproj/Localizable.strings index 79d7b619e..199afb842 100644 --- a/apps/ios/nl.lproj/Localizable.strings +++ b/apps/ios/nl.lproj/Localizable.strings @@ -209,7 +209,7 @@ "%lldw" = "%lldw"; /* No comment provided by engineer. */ -"%u messages failed to decrypt." = "%u-berichten kunnen niet worden gedecodeerd."; +"%u messages failed to decrypt." = "%u berichten kunnen niet worden ontsleuteld."; /* No comment provided by engineer. */ "%u messages skipped." = "%u berichten zijn overgeslagen."; @@ -1867,7 +1867,7 @@ "It allows having many anonymous connections without any shared data between them in a single chat profile." = "Het maakt het mogelijk om veel anonieme verbindingen te hebben zonder enige gedeelde gegevens tussen hen in een enkel chat profiel."; /* No comment provided by engineer. */ -"It can happen when you or your connection used the old database backup." = "Het kan gebeuren wanneer u of uw verbinding de oude databaseback-up gebruikte."; +"It can happen when you or your connection used the old database backup." = "Het kan gebeuren wanneer u of de ander een oude databaseback-up gebruikt."; /* No comment provided by engineer. */ "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." = "Het kan gebeuren wanneer:\n1. De berichten zijn na 2 dagen verlopen bij de verzendende client of na 30 dagen op de server.\n2. Decodering van het bericht is mislukt, omdat u of uw contactpersoon een oude databaseback-up heeft gebruikt.\n3. De verbinding is verbroken."; @@ -3299,7 +3299,7 @@ "Use iOS call interface" = "De iOS-oproepinterface gebruiken"; /* No comment provided by engineer. */ -"Use new incognito profile" = "Gebruik een nieuw incognito -profiel"; +"Use new incognito profile" = "Gebruik een nieuw incognitoprofiel"; /* No comment provided by engineer. */ "Use server" = "Gebruik server"; diff --git a/apps/ios/pl.lproj/Localizable.strings b/apps/ios/pl.lproj/Localizable.strings index 4bf7ea862..fc20b1c7f 100644 --- a/apps/ios/pl.lproj/Localizable.strings +++ b/apps/ios/pl.lproj/Localizable.strings @@ -115,6 +115,9 @@ /* No comment provided by engineer. */ "%@ %@" = "%@ %@"; +/* No comment provided by engineer. */ +"%@ and %@ connected" = "%@ i %@ połączeni"; + /* copied message info, <sender> at <time> */ "%@ at %@:" = "%1$@ o %2$@:"; @@ -133,6 +136,9 @@ /* notification title */ "%@ wants to connect!" = "%@ chce się połączyć!"; +/* No comment provided by engineer. */ +"%@, %@ and %lld other members connected" = "%@, %@ i %lld innych członków połączeni"; + /* copied message info */ "%@:" = "%@:"; @@ -1467,6 +1473,9 @@ /* No comment provided by engineer. */ "Even when disabled in the conversation." = "Nawet po wyłączeniu w rozmowie."; +/* No comment provided by engineer. */ +"event happened" = "nowe wydarzenie"; + /* No comment provided by engineer. */ "Exit without saving" = "Wyjdź bez zapisywania"; @@ -2881,6 +2890,9 @@ /* No comment provided by engineer. */ "Show developer options" = "Pokaż opcje dewelopera"; +/* No comment provided by engineer. */ +"Show last messages" = "Pokaż ostatnie wiadomości"; + /* No comment provided by engineer. */ "Show preview" = "Pokaż podgląd"; diff --git a/apps/ios/zh-Hans.lproj/Localizable.strings b/apps/ios/zh-Hans.lproj/Localizable.strings index 474182846..a7f42837e 100644 --- a/apps/ios/zh-Hans.lproj/Localizable.strings +++ b/apps/ios/zh-Hans.lproj/Localizable.strings @@ -19,6 +19,9 @@ /* No comment provided by engineer. */ "_italic_" = "\\_斜体_"; +/* No comment provided by engineer. */ +"- more stable message delivery.\n- a bit better groups.\n- and more!" = "- 更稳定的传输!\n- 更好的社群!\n- 以及更多!"; + /* No comment provided by engineer. */ "- voice messages up to 5 minutes.\n- custom time to disappear.\n- editing history." = "- 语音消息最长5分钟。\n- 自定义限时消息。\n- 编辑消息历史。"; @@ -85,6 +88,15 @@ /* No comment provided by engineer. */ "*bold*" = "\\*加粗*"; +/* copied message info title, # <title> */ +"# %@" = "# %@"; + +/* copied message info */ +"## History" = "## 历史"; + +/* copied message info */ +"## In reply to" = "## 回复"; + /* No comment provided by engineer. */ "#secret#" = "#秘密#"; @@ -103,6 +115,12 @@ /* No comment provided by engineer. */ "%@ %@" = "%@ %@"; +/* No comment provided by engineer. */ +"%@ and %@ connected" = "%@ 和%@ 以建立连接"; + +/* copied message info, <sender> at <time> */ +"%@ at %@:" = "%2$@:"; + /* notification title */ "%@ is connected!" = "%@ 已连接!"; @@ -118,6 +136,9 @@ /* notification title */ "%@ wants to connect!" = "%@ 要连接!"; +/* No comment provided by engineer. */ +"%@, %@ and %lld other members connected" = "%@, %@ 和 %lld 个成员"; + /* copied message info */ "%@:" = "%@:"; @@ -232,9 +253,15 @@ /* No comment provided by engineer. */ "30 seconds" = "30秒"; +/* No comment provided by engineer. */ +"A few more things" = ""; + /* notification title */ "A new contact" = "新联系人"; +/* No comment provided by engineer. */ +"A new random profile will be shared." = "创建一个随机的共享文件"; + /* No comment provided by engineer. */ "A separate TCP connection will be used **for each chat profile you have in the app**." = "一个单独的 TCP 连接将被用于**您在应用程序中的每个聊天资料**。"; diff --git a/apps/multiplatform/android/build.gradle.kts b/apps/multiplatform/android/build.gradle.kts index e896047ca..bd45ee125 100644 --- a/apps/multiplatform/android/build.gradle.kts +++ b/apps/multiplatform/android/build.gradle.kts @@ -83,12 +83,15 @@ android { // Comma separated list of languages that will be included in the apk android.defaultConfig.resConfigs( "en", + "ar", "bg", "cs", "de", "es", + "fi", "fr", "it", + "iw", "ja", "nl", "pl", diff --git a/apps/multiplatform/android/src/main/java/chat/simplex/app/MainActivity.kt b/apps/multiplatform/android/src/main/java/chat/simplex/app/MainActivity.kt index 55d8202f8..06def4ce1 100644 --- a/apps/multiplatform/android/src/main/java/chat/simplex/app/MainActivity.kt +++ b/apps/multiplatform/android/src/main/java/chat/simplex/app/MainActivity.kt @@ -141,7 +141,12 @@ fun processExternalIntent(intent: Intent?) { when { intent.type == "text/plain" -> { val text = intent.getStringExtra(Intent.EXTRA_TEXT) - if (text != null) { + val uri = intent.getParcelableExtra<Parcelable>(Intent.EXTRA_STREAM) as? Uri + if (uri != null) { + // Shared file that contains plain text, like `*.log` file + chatModel.sharedContent.value = SharedContent.File(text ?: "", uri.toURI()) + } else if (text != null) { + // Shared just a text chatModel.sharedContent.value = SharedContent.Text(text) } } diff --git a/apps/multiplatform/android/src/main/java/chat/simplex/app/SimplexApp.kt b/apps/multiplatform/android/src/main/java/chat/simplex/app/SimplexApp.kt index c94194a35..f70032788 100644 --- a/apps/multiplatform/android/src/main/java/chat/simplex/app/SimplexApp.kt +++ b/apps/multiplatform/android/src/main/java/chat/simplex/app/SimplexApp.kt @@ -71,7 +71,7 @@ class SimplexApp: Application(), LifecycleEventObserver { } Lifecycle.Event.ON_RESUME -> { isAppOnForeground = true - if (chatModel.onboardingStage.value == OnboardingStage.OnboardingComplete) { + if (chatModel.controller.appPrefs.onboardingStage.get() == OnboardingStage.OnboardingComplete) { SimplexService.showBackgroundServiceNoticeIfNeeded() } /** @@ -80,7 +80,7 @@ class SimplexApp: Application(), LifecycleEventObserver { * It can happen when app was started and a user enables battery optimization while app in background * */ if (chatModel.chatRunning.value != false && - chatModel.onboardingStage.value == OnboardingStage.OnboardingComplete && + chatModel.controller.appPrefs.onboardingStage.get() == OnboardingStage.OnboardingComplete && appPrefs.notificationsMode.get() == NotificationsMode.SERVICE ) { SimplexService.start() @@ -191,7 +191,7 @@ class SimplexApp: Application(), LifecycleEventObserver { override fun androidChatInitializedAndStarted() { // Prevents from showing "Enable notifications" alert when onboarding wasn't complete yet - if (chatModel.onboardingStage.value == OnboardingStage.OnboardingComplete) { + if (chatModel.controller.appPrefs.onboardingStage.get() == OnboardingStage.OnboardingComplete) { SimplexService.showBackgroundServiceNoticeIfNeeded() if (appPrefs.notificationsMode.get() == NotificationsMode.SERVICE) withBGApi { diff --git a/apps/multiplatform/build.gradle.kts b/apps/multiplatform/build.gradle.kts index f277da4bd..3a6fbcbf9 100644 --- a/apps/multiplatform/build.gradle.kts +++ b/apps/multiplatform/build.gradle.kts @@ -46,7 +46,7 @@ buildscript { classpath("com.android.tools.build:gradle:${rootProject.extra["gradle.plugin.version"]}") classpath(kotlin("gradle-plugin", version = rootProject.extra["kotlin.version"] as String)) classpath("org.jetbrains.kotlin:kotlin-serialization:1.3.2") - classpath("dev.icerock.moko:resources-generator:0.22.3") + classpath("dev.icerock.moko:resources-generator:0.23.0") // NOTE: Do not place your application dependencies here; they belong // in the individual module build.gradle files diff --git a/apps/multiplatform/common/build.gradle.kts b/apps/multiplatform/common/build.gradle.kts index 14caaa252..5b9560b07 100644 --- a/apps/multiplatform/common/build.gradle.kts +++ b/apps/multiplatform/common/build.gradle.kts @@ -39,7 +39,7 @@ kotlin { api("org.jetbrains.kotlinx:kotlinx-datetime:0.3.2") api("com.russhwolf:multiplatform-settings:1.0.0") api("com.charleskorn.kaml:kaml:0.43.0") - api("dev.icerock.moko:resources-compose:0.22.3") + api("dev.icerock.moko:resources-compose:0.23.0") api("org.jetbrains.compose.ui:ui-text:${rootProject.extra["compose.version"] as String}") implementation("org.jetbrains.compose.components:components-animatedimage:${rootProject.extra["compose.version"] as String}") //Barcode @@ -48,7 +48,7 @@ kotlin { // Link Previews implementation("org.jsoup:jsoup:1.13.1") // Resources - implementation("dev.icerock.moko:resources:0.22.3") + implementation("dev.icerock.moko:resources:0.23.0") } } val commonTest by getting { @@ -62,7 +62,7 @@ kotlin { val work_version = "2.7.1" implementation("androidx.work:work-runtime-ktx:$work_version") implementation("com.google.accompanist:accompanist-insets:0.23.0") - implementation("dev.icerock.moko:resources:0.22.3") + implementation("dev.icerock.moko:resources:0.23.0") // Video support implementation("com.google.android.exoplayer:exoplayer:2.17.1") diff --git a/apps/multiplatform/common/src/androidMain/kotlin/chat/simplex/common/platform/Files.android.kt b/apps/multiplatform/common/src/androidMain/kotlin/chat/simplex/common/platform/Files.android.kt index 35c29371e..161bc51e6 100644 --- a/apps/multiplatform/common/src/androidMain/kotlin/chat/simplex/common/platform/Files.android.kt +++ b/apps/multiplatform/common/src/androidMain/kotlin/chat/simplex/common/platform/Files.android.kt @@ -23,6 +23,8 @@ actual val agentDatabaseFileName: String = "files_agent.db" actual val databaseExportDir: File = androidAppContext.cacheDir +actual fun desktopOpenDatabaseDir() {} + @Composable actual fun rememberFileChooserLauncher(getContent: Boolean, rememberedValue: Any?, onResult: (URI?) -> Unit): FileChooserLauncher { val launcher = rememberLauncherForActivityResult( diff --git a/apps/multiplatform/common/src/androidMain/kotlin/chat/simplex/common/views/database/DatabaseEncryptionView.android.kt b/apps/multiplatform/common/src/androidMain/kotlin/chat/simplex/common/views/database/DatabaseEncryptionView.android.kt new file mode 100644 index 000000000..df2499926 --- /dev/null +++ b/apps/multiplatform/common/src/androidMain/kotlin/chat/simplex/common/views/database/DatabaseEncryptionView.android.kt @@ -0,0 +1,106 @@ +package chat.simplex.common.views.database + +import SectionItemView +import SectionTextFooter +import androidx.compose.foundation.layout.* +import androidx.compose.material.* +import androidx.compose.runtime.Composable +import androidx.compose.runtime.MutableState +import androidx.compose.ui.Alignment +import androidx.compose.ui.Modifier +import androidx.compose.ui.graphics.Color +import androidx.compose.ui.unit.Dp +import androidx.compose.ui.unit.dp +import chat.simplex.common.ui.theme.SimplexGreen +import chat.simplex.common.views.helpers.* +import chat.simplex.res.MR +import dev.icerock.moko.resources.compose.painterResource +import dev.icerock.moko.resources.compose.stringResource + +@Composable +actual fun SavePassphraseSetting( + useKeychain: Boolean, + initialRandomDBPassphrase: Boolean, + storedKey: Boolean, + progressIndicator: Boolean, + minHeight: Dp, + onCheckedChange: (Boolean) -> Unit, +) { + SectionItemView(minHeight = minHeight) { + Row(verticalAlignment = Alignment.CenterVertically) { + Icon( + if (storedKey) painterResource(MR.images.ic_vpn_key_filled) else painterResource(MR.images.ic_vpn_key_off_filled), + stringResource(MR.strings.save_passphrase_in_keychain), + tint = if (storedKey) SimplexGreen else MaterialTheme.colors.secondary + ) + Spacer(Modifier.padding(horizontal = 4.dp)) + Text( + stringResource(MR.strings.save_passphrase_in_keychain), + Modifier.padding(end = 24.dp), + color = Color.Unspecified + ) + Spacer(Modifier.fillMaxWidth().weight(1f)) + DefaultSwitch( + checked = useKeychain, + onCheckedChange = onCheckedChange, + enabled = !initialRandomDBPassphrase && !progressIndicator + ) + } + } +} + +@Composable +actual fun DatabaseEncryptionFooter( + useKeychain: MutableState<Boolean>, + chatDbEncrypted: Boolean?, + storedKey: MutableState<Boolean>, + initialRandomDBPassphrase: MutableState<Boolean>, +) { + if (chatDbEncrypted == false) { + SectionTextFooter(generalGetString(MR.strings.database_is_not_encrypted)) + } else if (useKeychain.value) { + if (storedKey.value) { + SectionTextFooter(generalGetString(MR.strings.keychain_is_storing_securely)) + if (initialRandomDBPassphrase.value) { + SectionTextFooter(generalGetString(MR.strings.encrypted_with_random_passphrase)) + } else { + SectionTextFooter(annotatedStringResource(MR.strings.impossible_to_recover_passphrase)) + } + } else { + SectionTextFooter(generalGetString(MR.strings.keychain_allows_to_receive_ntfs)) + } + } else { + SectionTextFooter(generalGetString(MR.strings.you_have_to_enter_passphrase_every_time)) + SectionTextFooter(annotatedStringResource(MR.strings.impossible_to_recover_passphrase)) + } +} + +actual fun encryptDatabaseSavedAlert(onConfirm: () -> Unit) { + AlertManager.shared.showAlertDialog( + title = generalGetString(MR.strings.encrypt_database_question), + text = generalGetString(MR.strings.database_will_be_encrypted_and_passphrase_stored) + "\n" + storeSecurelySaved(), + confirmText = generalGetString(MR.strings.encrypt_database), + onConfirm = onConfirm, + destructive = true, + ) +} + +actual fun changeDatabaseKeySavedAlert(onConfirm: () -> Unit) { + AlertManager.shared.showAlertDialog( + title = generalGetString(MR.strings.change_database_passphrase_question), + text = generalGetString(MR.strings.database_encryption_will_be_updated) + "\n" + storeSecurelySaved(), + confirmText = generalGetString(MR.strings.update_database), + onConfirm = onConfirm, + destructive = false, + ) +} + +actual fun removePassphraseAlert(onConfirm: () -> Unit) { + AlertManager.shared.showAlertDialog( + title = generalGetString(MR.strings.remove_passphrase_from_keychain), + text = generalGetString(MR.strings.notifications_will_be_hidden) + "\n" + storeSecurelyDanger(), + confirmText = generalGetString(MR.strings.remove_passphrase), + onConfirm = onConfirm, + destructive = true, + ) +} diff --git a/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/App.kt b/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/App.kt index cb386be7a..6b9770c09 100644 --- a/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/App.kt +++ b/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/App.kt @@ -32,8 +32,7 @@ import chat.simplex.res.MR import dev.icerock.moko.resources.compose.painterResource import dev.icerock.moko.resources.compose.stringResource import kotlinx.coroutines.* -import kotlinx.coroutines.flow.MutableStateFlow -import kotlinx.coroutines.flow.distinctUntilChanged +import kotlinx.coroutines.flow.* data class SettingsViewState( val userPickerState: MutableStateFlow<AnimatedViewState>, @@ -64,7 +63,7 @@ fun MainScreen() { if ( !chatModel.controller.appPrefs.laNoticeShown.get() && showAdvertiseLAAlert - && chatModel.onboardingStage.value == OnboardingStage.OnboardingComplete + && chatModel.controller.appPrefs.onboardingStage.get() == OnboardingStage.OnboardingComplete && chatModel.chats.isNotEmpty() && chatModel.activeCallInvitation.value == null ) { @@ -102,7 +101,10 @@ fun MainScreen() { } Box { - val onboarding = chatModel.onboardingStage.value + var onboarding by remember { mutableStateOf(chatModel.controller.appPrefs.onboardingStage.get()) } + LaunchedEffect(Unit) { + snapshotFlow { chatModel.controller.appPrefs.onboardingStage.state.value }.distinctUntilChanged().collect { onboarding = it } + } val userCreated = chatModel.userCreated.value var showInitializationView by remember { mutableStateOf(false) } when { @@ -112,7 +114,7 @@ fun MainScreen() { DatabaseErrorView(chatModel.chatDbStatus, chatModel.controller.appPrefs) } } - onboarding == null || userCreated == null -> SplashView() + remember { chatModel.chatDbEncrypted }.value == null || userCreated == null -> SplashView() onboarding == OnboardingStage.OnboardingComplete && userCreated -> { Box { showAdvertiseLAAlert = true @@ -134,6 +136,7 @@ fun MainScreen() { } } onboarding == OnboardingStage.Step2_CreateProfile -> CreateProfile(chatModel) {} + onboarding == OnboardingStage.Step2_5_SetupDatabasePassphrase -> SetupDatabasePassphrase(chatModel) onboarding == OnboardingStage.Step3_CreateSimpleXAddress -> CreateSimpleXAddress(chatModel) onboarding == OnboardingStage.Step4_SetNotificationsMode -> SetNotificationsMode(chatModel) } diff --git a/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/model/ChatModel.kt b/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/model/ChatModel.kt index 629d4b869..0eb35fccd 100644 --- a/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/model/ChatModel.kt +++ b/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/model/ChatModel.kt @@ -38,7 +38,6 @@ import kotlin.time.* @Stable object ChatModel { val controller: ChatController = ChatController - val onboardingStage = mutableStateOf<OnboardingStage?>(null) val setDeliveryReceipts = mutableStateOf(false) val currentUser = mutableStateOf<User?>(null) val users = mutableStateListOf<UserInfo>() diff --git a/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/platform/Core.kt b/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/platform/Core.kt index c39c00080..341f4e954 100644 --- a/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/platform/Core.kt +++ b/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/platform/Core.kt @@ -50,17 +50,16 @@ suspend fun initChatController(useKey: String? = null, confirmMigrations: Migrat if (user == null) { chatModel.controller.appPrefs.onboardingStage.set(OnboardingStage.Step1_SimpleXInfo) chatModel.controller.appPrefs.privacyDeliveryReceiptsSet.set(true) - chatModel.onboardingStage.value = OnboardingStage.Step1_SimpleXInfo chatModel.currentUser.value = null chatModel.users.clear() } else { val savedOnboardingStage = appPreferences.onboardingStage.get() - chatModel.onboardingStage.value = if (listOf(OnboardingStage.Step1_SimpleXInfo, OnboardingStage.Step2_CreateProfile).contains(savedOnboardingStage) && chatModel.users.size == 1) { + appPreferences.onboardingStage.set(if (listOf(OnboardingStage.Step1_SimpleXInfo, OnboardingStage.Step2_CreateProfile).contains(savedOnboardingStage) && chatModel.users.size == 1) { OnboardingStage.Step3_CreateSimpleXAddress } else { savedOnboardingStage - } - if (chatModel.onboardingStage.value == OnboardingStage.OnboardingComplete && !chatModel.controller.appPrefs.privacyDeliveryReceiptsSet.get()) { + }) + if (appPreferences.onboardingStage.get() == OnboardingStage.OnboardingComplete && !chatModel.controller.appPrefs.privacyDeliveryReceiptsSet.get()) { chatModel.setDeliveryReceipts.value = true } chatController.startChat(user) diff --git a/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/platform/Files.kt b/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/platform/Files.kt index 9bc26d445..9c702df54 100644 --- a/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/platform/Files.kt +++ b/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/platform/Files.kt @@ -23,6 +23,8 @@ expect val agentDatabaseFileName: String * */ expect val databaseExportDir: File +expect fun desktopOpenDatabaseDir() + fun copyFileToFile(from: File, to: URI, finally: () -> Unit) { try { to.outputStream().use { stream -> diff --git a/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/platform/NtfManager.kt b/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/platform/NtfManager.kt index 6adadaffa..a03df5add 100644 --- a/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/platform/NtfManager.kt +++ b/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/platform/NtfManager.kt @@ -100,7 +100,7 @@ abstract class NtfManager { if (chatModel.chatRunning.value == null) { val step = 50L for (i in 0..(timeout / step)) { - if (chatModel.chatRunning.value == true || chatModel.onboardingStage.value == OnboardingStage.Step1_SimpleXInfo) { + if (chatModel.chatRunning.value == true || chatModel.controller.appPrefs.onboardingStage.get() == OnboardingStage.Step1_SimpleXInfo) { break } delay(step) diff --git a/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/WelcomeView.kt b/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/WelcomeView.kt index 9539a0790..13ce16d0a 100644 --- a/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/WelcomeView.kt +++ b/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/WelcomeView.kt @@ -24,6 +24,7 @@ import androidx.compose.ui.unit.dp import androidx.compose.ui.unit.sp import chat.simplex.common.model.ChatModel import chat.simplex.common.model.Profile +import chat.simplex.common.platform.appPlatform import chat.simplex.common.platform.navigationBarsWithImePadding import chat.simplex.common.ui.theme.* import chat.simplex.common.views.helpers.* @@ -88,14 +89,20 @@ fun CreateProfilePanel(chatModel: ChatModel, close: () -> Unit) { icon = painterResource(MR.images.ic_arrow_back_ios_new), textDecoration = TextDecoration.None, fontWeight = FontWeight.Medium - ) { chatModel.onboardingStage.value = OnboardingStage.Step1_SimpleXInfo } + ) { chatModel.controller.appPrefs.onboardingStage.set(OnboardingStage.Step1_SimpleXInfo) } } Spacer(Modifier.fillMaxWidth().weight(1f)) val enabled = displayName.value.isNotEmpty() && isValidDisplayName(displayName.value) val createModifier: Modifier val createColor: Color if (enabled) { - createModifier = Modifier.clickable { createProfile(chatModel, displayName.value, fullName.value, close) }.padding(8.dp) + createModifier = Modifier.clickable { + if (chatModel.controller.appPrefs.onboardingStage.get() == OnboardingStage.OnboardingComplete) { + createProfileInProfiles(chatModel, displayName.value, fullName.value, close) + } else { + createProfileOnboarding(chatModel, displayName.value, fullName.value, close) + } + }.padding(8.dp) createColor = MaterialTheme.colors.primary } else { createModifier = Modifier.padding(8.dp) @@ -116,7 +123,7 @@ fun CreateProfilePanel(chatModel: ChatModel, close: () -> Unit) { } } -fun createProfile(chatModel: ChatModel, displayName: String, fullName: String, close: () -> Unit) { +fun createProfileInProfiles(chatModel: ChatModel, displayName: String, fullName: String, close: () -> Unit) { withApi { val user = chatModel.controller.apiCreateActiveUser( Profile(displayName, fullName, null) @@ -125,16 +132,32 @@ fun createProfile(chatModel: ChatModel, displayName: String, fullName: String, c if (chatModel.users.isEmpty()) { chatModel.controller.startChat(user) chatModel.controller.appPrefs.onboardingStage.set(OnboardingStage.Step3_CreateSimpleXAddress) - chatModel.onboardingStage.value = OnboardingStage.Step3_CreateSimpleXAddress } else { val users = chatModel.controller.listUsers() chatModel.users.clear() chatModel.users.addAll(users) chatModel.controller.getUserChatData() + close() + } + } +} + +fun createProfileOnboarding(chatModel: ChatModel, displayName: String, fullName: String, close: () -> Unit) { + withApi { + chatModel.controller.apiCreateActiveUser( + Profile(displayName, fullName, null) + ) ?: return@withApi + val onboardingStage = chatModel.controller.appPrefs.onboardingStage + if (chatModel.users.isEmpty()) { + onboardingStage.set(if (appPlatform.isDesktop && chatModel.controller.appPrefs.initialRandomDBPassphrase.get()) { + OnboardingStage.Step2_5_SetupDatabasePassphrase + } else { + OnboardingStage.Step3_CreateSimpleXAddress + }) + } else { // the next two lines are only needed for failure case when because of the database error the app gets stuck on on-boarding screen, // this will get it unstuck. - chatModel.controller.appPrefs.onboardingStage.set(OnboardingStage.OnboardingComplete) - chatModel.onboardingStage.value = OnboardingStage.OnboardingComplete + onboardingStage.set(OnboardingStage.OnboardingComplete) close() } } diff --git a/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/database/DatabaseEncryptionView.kt b/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/database/DatabaseEncryptionView.kt index 37080ebd8..e34f80a7e 100644 --- a/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/database/DatabaseEncryptionView.kt +++ b/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/database/DatabaseEncryptionView.kt @@ -30,6 +30,7 @@ import chat.simplex.common.model.* import chat.simplex.common.ui.theme.* import chat.simplex.common.views.helpers.* import chat.simplex.common.model.* +import chat.simplex.common.platform.appPlatform import chat.simplex.res.MR import kotlinx.coroutines.flow.distinctUntilChanged import kotlinx.datetime.Clock @@ -61,46 +62,8 @@ fun DatabaseEncryptionView(m: ChatModel) { initialRandomDBPassphrase, progressIndicator, onConfirmEncrypt = { - progressIndicator.value = true withApi { - try { - prefs.encryptionStartedAt.set(Clock.System.now()) - val error = m.controller.apiStorageEncryption(currentKey.value, newKey.value) - prefs.encryptionStartedAt.set(null) - val sqliteError = ((error?.chatError as? ChatError.ChatErrorDatabase)?.databaseError as? DatabaseError.ErrorExport)?.sqliteError - when { - sqliteError is SQLiteError.ErrorNotADatabase -> { - operationEnded(m, progressIndicator) { - AlertManager.shared.showAlertMsg( - generalGetString(MR.strings.wrong_passphrase_title), - generalGetString(MR.strings.enter_correct_current_passphrase) - ) - } - } - error != null -> { - operationEnded(m, progressIndicator) { - AlertManager.shared.showAlertMsg(generalGetString(MR.strings.error_encrypting_database), - "failed to set storage encryption: ${error.responseType} ${error.details}" - ) - } - } - else -> { - prefs.initialRandomDBPassphrase.set(false) - initialRandomDBPassphrase.value = false - if (useKeychain.value) { - DatabaseUtils.ksDatabasePassword.set(newKey.value) - } - resetFormAfterEncryption(m, initialRandomDBPassphrase, currentKey, newKey, confirmNewKey, storedKey, useKeychain.value) - operationEnded(m, progressIndicator) { - AlertManager.shared.showAlertMsg(generalGetString(MR.strings.database_encrypted)) - } - } - } - } catch (e: Exception) { - operationEnded(m, progressIndicator) { - AlertManager.shared.showAlertMsg(generalGetString(MR.strings.error_encrypting_database), e.stackTraceToString()) - } - } + encryptDatabase(currentKey, newKey, confirmNewKey, initialRandomDBPassphrase, useKeychain, storedKey, progressIndicator) } } ) @@ -143,17 +106,11 @@ fun DatabaseEncryptionLayout( if (checked) { setUseKeychain(true, useKeychain, prefs) } else if (storedKey.value) { - AlertManager.shared.showAlertDialog( - title = generalGetString(MR.strings.remove_passphrase_from_keychain), - text = generalGetString(MR.strings.notifications_will_be_hidden) + "\n" + storeSecurelyDanger(), - confirmText = generalGetString(MR.strings.remove_passphrase), - onConfirm = { - DatabaseUtils.ksDatabasePassword.remove() - setUseKeychain(false, useKeychain, prefs) - storedKey.value = false - }, - destructive = true, - ) + removePassphraseAlert { + DatabaseUtils.ksDatabasePassword.remove() + setUseKeychain(false, useKeychain, prefs) + storedKey.value = false + } } else { setUseKeychain(false, useKeychain, prefs) } @@ -217,37 +174,13 @@ fun DatabaseEncryptionLayout( } Column { - if (chatDbEncrypted == false) { - SectionTextFooter(generalGetString(MR.strings.database_is_not_encrypted)) - } else if (useKeychain.value) { - if (storedKey.value) { - SectionTextFooter(generalGetString(MR.strings.keychain_is_storing_securely)) - if (initialRandomDBPassphrase.value) { - SectionTextFooter(generalGetString(MR.strings.encrypted_with_random_passphrase)) - } else { - SectionTextFooter(annotatedStringResource(MR.strings.impossible_to_recover_passphrase)) - } - } else { - SectionTextFooter(generalGetString(MR.strings.keychain_allows_to_receive_ntfs)) - } - } else { - SectionTextFooter(generalGetString(MR.strings.you_have_to_enter_passphrase_every_time)) - SectionTextFooter(annotatedStringResource(MR.strings.impossible_to_recover_passphrase)) - } + DatabaseEncryptionFooter(useKeychain, chatDbEncrypted, storedKey, initialRandomDBPassphrase) } SectionBottomSpacer() } } -fun encryptDatabaseSavedAlert(onConfirm: () -> Unit) { - AlertManager.shared.showAlertDialog( - title = generalGetString(MR.strings.encrypt_database_question), - text = generalGetString(MR.strings.database_will_be_encrypted_and_passphrase_stored) + "\n" + storeSecurelySaved(), - confirmText = generalGetString(MR.strings.encrypt_database), - onConfirm = onConfirm, - destructive = true, - ) -} +expect fun encryptDatabaseSavedAlert(onConfirm: () -> Unit) fun encryptDatabaseAlert(onConfirm: () -> Unit) { AlertManager.shared.showAlertDialog( @@ -259,15 +192,7 @@ fun encryptDatabaseAlert(onConfirm: () -> Unit) { ) } -fun changeDatabaseKeySavedAlert(onConfirm: () -> Unit) { - AlertManager.shared.showAlertDialog( - title = generalGetString(MR.strings.change_database_passphrase_question), - text = generalGetString(MR.strings.database_encryption_will_be_updated) + "\n" + storeSecurelySaved(), - confirmText = generalGetString(MR.strings.update_database), - onConfirm = onConfirm, - destructive = false, - ) -} +expect fun changeDatabaseKeySavedAlert(onConfirm: () -> Unit) fun changeDatabaseKeyAlert(onConfirm: () -> Unit) { AlertManager.shared.showAlertDialog( @@ -279,37 +204,25 @@ fun changeDatabaseKeyAlert(onConfirm: () -> Unit) { ) } +expect fun removePassphraseAlert(onConfirm: () -> Unit) + @Composable -fun SavePassphraseSetting( +expect fun SavePassphraseSetting( useKeychain: Boolean, initialRandomDBPassphrase: Boolean, storedKey: Boolean, progressIndicator: Boolean, minHeight: Dp = TextFieldDefaults.MinHeight, onCheckedChange: (Boolean) -> Unit, -) { - SectionItemView(minHeight = minHeight) { - Row(verticalAlignment = Alignment.CenterVertically) { - Icon( - if (storedKey) painterResource(MR.images.ic_vpn_key_filled) else painterResource(MR.images.ic_vpn_key_off_filled), - stringResource(MR.strings.save_passphrase_in_keychain), - tint = if (storedKey) SimplexGreen else MaterialTheme.colors.secondary - ) - Spacer(Modifier.padding(horizontal = 4.dp)) - Text( - stringResource(MR.strings.save_passphrase_in_keychain), - Modifier.padding(end = 24.dp), - color = Color.Unspecified - ) - Spacer(Modifier.fillMaxWidth().weight(1f)) - DefaultSwitch( - checked = useKeychain, - onCheckedChange = onCheckedChange, - enabled = !initialRandomDBPassphrase && !progressIndicator - ) - } - } -} +) + +@Composable +expect fun DatabaseEncryptionFooter( + useKeychain: MutableState<Boolean>, + chatDbEncrypted: Boolean?, + storedKey: MutableState<Boolean>, + initialRandomDBPassphrase: MutableState<Boolean>, +) fun resetFormAfterEncryption( m: ChatModel, @@ -443,6 +356,62 @@ fun PassphraseField( } } +suspend fun encryptDatabase( + currentKey: MutableState<String>, + newKey: MutableState<String>, + confirmNewKey: MutableState<String>, + initialRandomDBPassphrase: MutableState<Boolean>, + useKeychain: MutableState<Boolean>, + storedKey: MutableState<Boolean>, + progressIndicator: MutableState<Boolean> +): Boolean { + val m = ChatModel + val prefs = ChatController.appPrefs + progressIndicator.value = true + return try { + prefs.encryptionStartedAt.set(Clock.System.now()) + val error = m.controller.apiStorageEncryption(currentKey.value, newKey.value) + prefs.encryptionStartedAt.set(null) + val sqliteError = ((error?.chatError as? ChatError.ChatErrorDatabase)?.databaseError as? DatabaseError.ErrorExport)?.sqliteError + when { + sqliteError is SQLiteError.ErrorNotADatabase -> { + operationEnded(m, progressIndicator) { + AlertManager.shared.showAlertMsg( + generalGetString(MR.strings.wrong_passphrase_title), + generalGetString(MR.strings.enter_correct_current_passphrase) + ) + } + false + } + error != null -> { + operationEnded(m, progressIndicator) { + AlertManager.shared.showAlertMsg(generalGetString(MR.strings.error_encrypting_database), + "failed to set storage encryption: ${error.responseType} ${error.details}" + ) + } + false + } + else -> { + prefs.initialRandomDBPassphrase.set(false) + initialRandomDBPassphrase.value = false + if (useKeychain.value) { + DatabaseUtils.ksDatabasePassword.set(newKey.value) + } + resetFormAfterEncryption(m, initialRandomDBPassphrase, currentKey, newKey, confirmNewKey, storedKey, useKeychain.value) + operationEnded(m, progressIndicator) { + AlertManager.shared.showAlertMsg(generalGetString(MR.strings.database_encrypted)) + } + true + } + } + } catch (e: Exception) { + operationEnded(m, progressIndicator) { + AlertManager.shared.showAlertMsg(generalGetString(MR.strings.error_encrypting_database), e.stackTraceToString()) + } + false + } +} + // based on https://generatepasswords.org/how-to-calculate-entropy/ private fun passphraseEntropy(s: String): Double { var hasDigits = false diff --git a/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/database/DatabaseErrorView.kt b/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/database/DatabaseErrorView.kt index 710148168..bce8fdf4f 100644 --- a/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/database/DatabaseErrorView.kt +++ b/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/database/DatabaseErrorView.kt @@ -12,6 +12,9 @@ import androidx.compose.material.* import androidx.compose.runtime.* import androidx.compose.ui.Alignment import androidx.compose.ui.Modifier +import androidx.compose.ui.focus.FocusRequester +import androidx.compose.ui.focus.focusRequester +import androidx.compose.ui.input.key.* import androidx.compose.ui.text.font.FontWeight import androidx.compose.ui.unit.dp import chat.simplex.common.model.AppPreferences @@ -252,6 +255,11 @@ private fun mtrErrorDescription(err: MTRError): String = @Composable private fun DatabaseKeyField(text: MutableState<String>, enabled: Boolean, onClick: (() -> Unit)? = null) { + val focusRequester = remember { FocusRequester() } + LaunchedEffect(Unit) { + delay(100L) + focusRequester.requestFocus() + } PassphraseField( text, generalGetString(MR.strings.enter_passphrase), @@ -259,7 +267,15 @@ private fun DatabaseKeyField(text: MutableState<String>, enabled: Boolean, onCli keyboardActions = KeyboardActions(onDone = if (enabled) { { onClick?.invoke() } } else null - ) + ), + modifier = Modifier.focusRequester(focusRequester).onPreviewKeyEvent { + if (onClick != null && it.key == Key.Enter && it.type == KeyEventType.KeyUp) { + onClick() + true + } else { + false + } + } ) } diff --git a/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/database/DatabaseView.kt b/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/database/DatabaseView.kt index 05f38b74d..9ecd7dae3 100644 --- a/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/database/DatabaseView.kt +++ b/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/database/DatabaseView.kt @@ -39,7 +39,6 @@ fun DatabaseView( showSettingsModal: (@Composable (ChatModel) -> Unit) -> (() -> Unit) ) { val progressIndicator = remember { mutableStateOf(false) } - val runChat = remember { m.chatRunning } val prefs = m.controller.appPrefs val useKeychain = remember { mutableStateOf(prefs.storeDBPassphrase.get()) } val chatArchiveName = remember { mutableStateOf(prefs.chatArchiveName.get()) } @@ -60,20 +59,19 @@ fun DatabaseView( importArchiveAlert(m, to, appFilesCountAndSize, progressIndicator) } } - LaunchedEffect(m.chatRunning) { - runChat.value = m.chatRunning.value ?: true - } val chatItemTTL = remember { mutableStateOf(m.chatItemTTL.value) } Box( Modifier.fillMaxSize(), ) { DatabaseLayout( progressIndicator.value, - runChat.value != false, + remember { m.chatRunning }.value != false, m.chatDbChanged.value, useKeychain.value, m.chatDbEncrypted.value, + m.controller.appPrefs.storeDBPassphrase.state.value, m.controller.appPrefs.initialRandomDBPassphrase, + m.controller.appPrefs.developerTools.state.value, importArchiveLauncher, chatArchiveName, chatArchiveTime, @@ -82,8 +80,8 @@ fun DatabaseView( chatItemTTL, m.currentUser.value, m.users, - startChat = { startChat(m, runChat, chatLastStart, m.chatDbChanged) }, - stopChatAlert = { stopChatAlert(m, runChat) }, + startChat = { startChat(m, chatLastStart, m.chatDbChanged) }, + stopChatAlert = { stopChatAlert(m) }, exportArchive = { exportArchive(m, progressIndicator, chatArchiveName, chatArchiveTime, chatArchiveFile, saveArchiveLauncher) }, deleteChatAlert = { deleteChatAlert(m, progressIndicator) }, deleteAppFilesAndMedia = { deleteFilesAndMediaAlert(appFilesCountAndSize) }, @@ -122,7 +120,9 @@ fun DatabaseLayout( chatDbChanged: Boolean, useKeyChain: Boolean, chatDbEncrypted: Boolean?, + passphraseSaved: Boolean, initialRandomDBPassphrase: SharedPreference<Boolean>, + developerTools: Boolean, importArchiveLauncher: FileChooserLauncher, chatArchiveName: MutableState<String?>, chatArchiveTime: MutableState<Instant?>, @@ -182,9 +182,17 @@ fun DatabaseLayout( else painterResource(MR.images.ic_lock), stringResource(MR.strings.database_passphrase), click = showSettingsModal() { DatabaseEncryptionView(it) }, - iconColor = if (unencrypted) WarningOrange else MaterialTheme.colors.secondary, + iconColor = if (unencrypted || (appPlatform.isDesktop && passphraseSaved)) WarningOrange else MaterialTheme.colors.secondary, disabled = operationsDisabled ) + if (appPlatform.isDesktop && developerTools) { + SettingsActionItem( + painterResource(MR.images.ic_folder_open), + stringResource(MR.strings.open_database_folder), + ::desktopOpenDatabaseDir, + disabled = operationsDisabled + ) + } SettingsActionItem( painterResource(MR.images.ic_ios_share), stringResource(MR.strings.export_database), @@ -327,7 +335,7 @@ fun chatArchiveTitle(chatArchiveTime: Instant, chatLastStart: Instant): String { return stringResource(if (chatArchiveTime < chatLastStart) MR.strings.old_database_archive else MR.strings.new_database_archive) } -private fun startChat(m: ChatModel, runChat: MutableState<Boolean?>, chatLastStart: MutableState<Instant?>, chatDbChanged: MutableState<Boolean>) { +private fun startChat(m: ChatModel, chatLastStart: MutableState<Instant?>, chatDbChanged: MutableState<Boolean>) { withApi { try { if (chatDbChanged.value) { @@ -344,7 +352,6 @@ private fun startChat(m: ChatModel, runChat: MutableState<Boolean?>, chatLastSta return@withApi } else { m.controller.apiStartChat() - runChat.value = true m.chatRunning.value = true } val ts = Clock.System.now() @@ -352,19 +359,19 @@ private fun startChat(m: ChatModel, runChat: MutableState<Boolean?>, chatLastSta chatLastStart.value = ts platform.androidChatStartedAfterBeingOff() } catch (e: Error) { - runChat.value = false + m.chatRunning.value = false AlertManager.shared.showAlertMsg(generalGetString(MR.strings.error_starting_chat), e.toString()) } } } -private fun stopChatAlert(m: ChatModel, runChat: MutableState<Boolean?>) { +private fun stopChatAlert(m: ChatModel) { AlertManager.shared.showAlertDialog( title = generalGetString(MR.strings.stop_chat_question), text = generalGetString(MR.strings.stop_chat_to_export_import_or_delete_chat_database), confirmText = generalGetString(MR.strings.stop_chat_confirmation), - onConfirm = { authStopChat(m, runChat) }, - onDismiss = { runChat.value = true } + onConfirm = { authStopChat(m) }, + onDismiss = { m.chatRunning.value = true } ) } @@ -375,7 +382,7 @@ private fun exportProhibitedAlert() { ) } -private fun authStopChat(m: ChatModel, runChat: MutableState<Boolean?>) { +private fun authStopChat(m: ChatModel) { if (m.controller.appPrefs.performLA.get()) { authenticate( generalGetString(MR.strings.auth_stop_chat), @@ -383,30 +390,29 @@ private fun authStopChat(m: ChatModel, runChat: MutableState<Boolean?>) { completed = { laResult -> when (laResult) { LAResult.Success, is LAResult.Unavailable -> { - stopChat(m, runChat) + stopChat(m) } is LAResult.Error -> { - runChat.value = true + m.chatRunning.value = true } is LAResult.Failed -> { - runChat.value = true + m.chatRunning.value = true } } } ) } else { - stopChat(m, runChat) + stopChat(m) } } -private fun stopChat(m: ChatModel, runChat: MutableState<Boolean?>) { +private fun stopChat(m: ChatModel) { withApi { try { - runChat.value = false stopChatAsync(m) platform.androidChatStopped() } catch (e: Error) { - runChat.value = true + m.chatRunning.value = true AlertManager.shared.showAlertMsg(generalGetString(MR.strings.error_stopping_chat), e.toString()) } } @@ -657,7 +663,9 @@ fun PreviewDatabaseLayout() { chatDbChanged = false, useKeyChain = false, chatDbEncrypted = false, + passphraseSaved = false, initialRandomDBPassphrase = SharedPreference({ true }, {}), + developerTools = true, importArchiveLauncher = rememberFileChooserLauncher(true) {}, chatArchiveName = remember { mutableStateOf("dummy_archive") }, chatArchiveTime = remember { mutableStateOf(Clock.System.now()) }, diff --git a/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/helpers/AlertManager.kt b/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/helpers/AlertManager.kt index d96b9d8a1..d8466e9d9 100644 --- a/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/helpers/AlertManager.kt +++ b/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/helpers/AlertManager.kt @@ -101,6 +101,10 @@ class AlertManager { Modifier.fillMaxWidth().padding(horizontal = DEFAULT_PADDING).padding(bottom = DEFAULT_PADDING_HALF), horizontalArrangement = Arrangement.SpaceBetween ) { + val focusRequester = remember { FocusRequester() } + LaunchedEffect(Unit) { + focusRequester.requestFocus() + } TextButton(onClick = { onDismiss?.invoke() hideAlert() @@ -108,7 +112,7 @@ class AlertManager { TextButton(onClick = { onConfirm?.invoke() hideAlert() - }) { Text(confirmText, color = if (destructive) MaterialTheme.colors.error else Color.Unspecified) } + }, Modifier.focusRequester(focusRequester)) { Text(confirmText, color = if (destructive) MaterialTheme.colors.error else Color.Unspecified) } } }, shape = RoundedCornerShape(corner = CornerSize(25.dp)) diff --git a/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/helpers/DatabaseUtils.kt b/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/helpers/DatabaseUtils.kt index 10641b6d8..e7da47f8f 100644 --- a/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/helpers/DatabaseUtils.kt +++ b/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/helpers/DatabaseUtils.kt @@ -54,6 +54,12 @@ object DatabaseUtils { } else { dbKey = ksDatabasePassword.get() ?: "" } + } else if (appPlatform.isDesktop && !hasDatabase(dataDir.absolutePath)) { + // In case of database was deleted by hand + dbKey = randomDatabasePassword() + ksDatabasePassword.set(dbKey) + appPreferences.initialRandomDBPassphrase.set(true) + appPreferences.storeDBPassphrase.set(true) } return dbKey } diff --git a/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/helpers/SimpleButton.kt b/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/helpers/SimpleButton.kt index 5ab0e68c6..7db001a4b 100644 --- a/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/helpers/SimpleButton.kt +++ b/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/helpers/SimpleButton.kt @@ -12,6 +12,7 @@ import androidx.compose.ui.Modifier import androidx.compose.ui.draw.clip import androidx.compose.ui.graphics.Color import androidx.compose.ui.graphics.painter.Painter +import androidx.compose.ui.text.TextStyle import androidx.compose.ui.text.font.FontWeight import androidx.compose.ui.text.style.TextDecoration import androidx.compose.ui.unit.dp @@ -66,11 +67,13 @@ fun SimpleButton( fun SimpleButtonIconEnded( text: String, icon: Painter, + style: TextStyle = MaterialTheme.typography.caption, color: Color = MaterialTheme.colors.primary, + disabled: Boolean = false, click: () -> Unit ) { - SimpleButtonFrame(click) { - Text(text, style = MaterialTheme.typography.caption, color = color) + SimpleButtonFrame(click, disabled = disabled) { + Text(text, style = style, color = color) Icon( icon, text, tint = color, modifier = Modifier.padding(start = 8.dp) diff --git a/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/localauth/LocalAuthView.kt b/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/localauth/LocalAuthView.kt index 756e605dc..8b5c2a833 100644 --- a/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/localauth/LocalAuthView.kt +++ b/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/localauth/LocalAuthView.kt @@ -66,7 +66,6 @@ private fun deleteStorageAndRestart(m: ChatModel, password: String, completed: ( val createdUser = m.controller.apiCreateActiveUser(profile, pastTimestamp = true) m.currentUser.value = createdUser m.controller.appPrefs.onboardingStage.set(OnboardingStage.OnboardingComplete) - m.onboardingStage.value = OnboardingStage.OnboardingComplete if (createdUser != null) { m.controller.startChat(createdUser) } diff --git a/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/onboarding/CreateSimpleXAddress.kt b/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/onboarding/CreateSimpleXAddress.kt index 84d1ae639..72cbc3a62 100644 --- a/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/onboarding/CreateSimpleXAddress.kt +++ b/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/onboarding/CreateSimpleXAddress.kt @@ -14,8 +14,7 @@ import dev.icerock.moko.resources.compose.painterResource import dev.icerock.moko.resources.compose.stringResource import androidx.compose.ui.text.style.TextAlign import androidx.compose.ui.unit.dp -import chat.simplex.common.model.ChatModel -import chat.simplex.common.model.UserContactLinkRec +import chat.simplex.common.model.* import chat.simplex.common.platform.* import chat.simplex.common.ui.theme.* import chat.simplex.common.views.helpers.* @@ -29,6 +28,10 @@ fun CreateSimpleXAddress(m: ChatModel) { val clipboard = LocalClipboardManager.current val uriHandler = LocalUriHandler.current + LaunchedEffect(Unit) { + prepareChatBeforeAddressCreation() + } + CreateSimpleXAddressLayout( userAddress.value, share = { address: String -> clipboard.shareText(address) }, @@ -63,7 +66,6 @@ fun CreateSimpleXAddress(m: ChatModel) { OnboardingStage.OnboardingComplete } m.controller.appPrefs.onboardingStage.set(next) - m.onboardingStage.value = next }, ) @@ -172,3 +174,19 @@ private fun ProgressIndicator() { ) } } + +private fun prepareChatBeforeAddressCreation() { + if (chatModel.users.isNotEmpty()) return + withApi { + val user = chatModel.controller.apiGetActiveUser() ?: return@withApi + chatModel.currentUser.value = user + if (chatModel.users.isEmpty()) { + chatModel.controller.startChat(user) + } else { + val users = chatModel.controller.listUsers() + chatModel.users.clear() + chatModel.users.addAll(users) + chatModel.controller.getUserChatData() + } + } +} diff --git a/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/onboarding/HowItWorks.kt b/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/onboarding/HowItWorks.kt index 3b2e0b408..e3dfb2b73 100644 --- a/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/onboarding/HowItWorks.kt +++ b/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/onboarding/HowItWorks.kt @@ -13,8 +13,7 @@ import androidx.compose.ui.text.* import androidx.compose.ui.text.style.TextAlign import androidx.compose.ui.unit.dp import androidx.compose.ui.unit.sp -import chat.simplex.common.model.ChatController -import chat.simplex.common.model.User +import chat.simplex.common.model.* import chat.simplex.common.ui.theme.* import chat.simplex.common.views.chat.item.MarkdownText import chat.simplex.common.views.helpers.* @@ -22,7 +21,7 @@ import chat.simplex.res.MR import dev.icerock.moko.resources.StringResource @Composable -fun HowItWorks(user: User?, onboardingStage: MutableState<OnboardingStage?>? = null) { +fun HowItWorks(user: User?, onboardingStage: SharedPreference<OnboardingStage>? = null) { Column(Modifier .fillMaxWidth() .padding(horizontal = DEFAULT_PADDING), diff --git a/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/onboarding/OnboardingView.kt b/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/onboarding/OnboardingView.kt index e3190f875..119ed8cd4 100644 --- a/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/onboarding/OnboardingView.kt +++ b/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/onboarding/OnboardingView.kt @@ -14,6 +14,7 @@ import kotlinx.coroutines.launch enum class OnboardingStage { Step1_SimpleXInfo, Step2_CreateProfile, + Step2_5_SetupDatabasePassphrase, Step3_CreateSimpleXAddress, Step4_SetNotificationsMode, OnboardingComplete diff --git a/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/onboarding/SetNotificationsMode.kt b/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/onboarding/SetNotificationsMode.kt index af640d5b4..aa413016d 100644 --- a/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/onboarding/SetNotificationsMode.kt +++ b/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/onboarding/SetNotificationsMode.kt @@ -41,7 +41,7 @@ fun SetNotificationsMode(m: ChatModel) { } Spacer(Modifier.fillMaxHeight().weight(1f)) Box(Modifier.fillMaxWidth().padding(bottom = DEFAULT_PADDING_HALF), contentAlignment = Alignment.Center) { - OnboardingActionButton(MR.strings.use_chat, OnboardingStage.OnboardingComplete, m.onboardingStage, false) { + OnboardingActionButton(MR.strings.use_chat, OnboardingStage.OnboardingComplete, false) { changeNotificationsMode(currentMode.value, m) } } diff --git a/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/onboarding/SetupDatabasePassphrase.kt b/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/onboarding/SetupDatabasePassphrase.kt new file mode 100644 index 000000000..9bc5ae846 --- /dev/null +++ b/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/onboarding/SetupDatabasePassphrase.kt @@ -0,0 +1,233 @@ +package chat.simplex.common.views.onboarding + +import SectionBottomSpacer +import SectionItemView +import SectionItemViewSpaceBetween +import SectionTextFooter +import SectionView +import androidx.compose.foundation.layout.* +import androidx.compose.foundation.rememberScrollState +import androidx.compose.foundation.text.KeyboardActions +import androidx.compose.foundation.verticalScroll +import androidx.compose.material.* +import androidx.compose.runtime.* +import androidx.compose.runtime.saveable.rememberSaveable +import androidx.compose.ui.Alignment +import androidx.compose.ui.Modifier +import androidx.compose.ui.focus.* +import androidx.compose.ui.graphics.Color +import androidx.compose.ui.input.key.* +import androidx.compose.ui.platform.LocalFocusManager +import androidx.compose.ui.text.input.ImeAction +import dev.icerock.moko.resources.compose.painterResource +import dev.icerock.moko.resources.compose.stringResource +import androidx.compose.ui.text.style.TextAlign +import androidx.compose.ui.unit.Dp +import androidx.compose.ui.unit.dp +import chat.simplex.common.model.* +import chat.simplex.common.platform.* +import chat.simplex.common.ui.theme.* +import chat.simplex.common.views.database.* +import chat.simplex.common.views.helpers.* +import chat.simplex.res.MR +import kotlinx.coroutines.delay + +@Composable +fun SetupDatabasePassphrase(m: ChatModel) { + val progressIndicator = remember { mutableStateOf(false) } + val prefs = m.controller.appPrefs + val saveInPreferences = remember { mutableStateOf(prefs.storeDBPassphrase.get()) } + val initialRandomDBPassphrase = remember { mutableStateOf(prefs.initialRandomDBPassphrase.get()) } + // Do not do rememberSaveable on current key to prevent saving it on disk in clear text + val currentKey = remember { mutableStateOf(if (initialRandomDBPassphrase.value) DatabaseUtils.ksDatabasePassword.get() ?: "" else "") } + val newKey = rememberSaveable { mutableStateOf("") } + val confirmNewKey = rememberSaveable { mutableStateOf("") } + fun nextStep() { + m.controller.appPrefs.onboardingStage.set(OnboardingStage.Step3_CreateSimpleXAddress) + } + SetupDatabasePassphraseLayout( + currentKey, + newKey, + confirmNewKey, + progressIndicator, + onConfirmEncrypt = { + withApi { + if (m.chatRunning.value == true) { + // Stop chat if it's started before doing anything + stopChatAsync(m) + } + prefs.storeDBPassphrase.set(false) + + val newKeyValue = newKey.value + val success = encryptDatabase(currentKey, newKey, confirmNewKey, mutableStateOf(true), saveInPreferences, mutableStateOf(true), progressIndicator) + if (success) { + startChat(newKeyValue) + nextStep() + } else { + // Rollback in case of it is finished with error in order to allow to repeat the process again + prefs.storeDBPassphrase.set(true) + } + } + }, + nextStep = ::nextStep, + ) + + if (progressIndicator.value) { + ProgressIndicator() + } + + DisposableEffect(Unit) { + onDispose { + if (m.chatRunning.value != true) { + withBGApi { + val user = chatController.apiGetActiveUser() + if (user != null) { + m.controller.startChat(user) + } + } + } + } + } +} + +@Composable +private fun SetupDatabasePassphraseLayout( + currentKey: MutableState<String>, + newKey: MutableState<String>, + confirmNewKey: MutableState<String>, + progressIndicator: MutableState<Boolean>, + onConfirmEncrypt: () -> Unit, + nextStep: () -> Unit, +) { + Column( + Modifier.fillMaxSize().verticalScroll(rememberScrollState()).padding(top = DEFAULT_PADDING), + horizontalAlignment = Alignment.CenterHorizontally, + ) { + AppBarTitle(stringResource(MR.strings.setup_database_passphrase)) + + Spacer(Modifier.weight(1f)) + + Column(Modifier.width(600.dp)) { + val focusRequester = remember { FocusRequester() } + val focusManager = LocalFocusManager.current + LaunchedEffect(Unit) { + delay(100L) + focusRequester.requestFocus() + } + PassphraseField( + newKey, + generalGetString(MR.strings.new_passphrase), + modifier = Modifier + .padding(horizontal = DEFAULT_PADDING) + .focusRequester(focusRequester) + .onPreviewKeyEvent { + if (it.key == Key.Enter && it.type == KeyEventType.KeyUp) { + focusManager.moveFocus(FocusDirection.Down) + true + } else { + false + } + }, + showStrength = true, + isValid = ::validKey, + keyboardActions = KeyboardActions(onNext = { defaultKeyboardAction(ImeAction.Next) }), + ) + val onClickUpdate = { + // Don't do things concurrently. Shouldn't be here concurrently, just in case + if (!progressIndicator.value) { + encryptDatabaseAlert(onConfirmEncrypt) + } + } + val disabled = currentKey.value == newKey.value || + newKey.value != confirmNewKey.value || + newKey.value.isEmpty() || + !validKey(currentKey.value) || + !validKey(newKey.value) || + progressIndicator.value + + PassphraseField( + confirmNewKey, + generalGetString(MR.strings.confirm_new_passphrase), + modifier = Modifier + .padding(horizontal = DEFAULT_PADDING) + .onPreviewKeyEvent { + if (!disabled && it.key == Key.Enter && it.type == KeyEventType.KeyUp) { + onClickUpdate() + true + } else { + false + } + }, + isValid = { confirmNewKey.value == "" || newKey.value == confirmNewKey.value }, + keyboardActions = KeyboardActions(onDone = { + if (!disabled) onClickUpdate() + defaultKeyboardAction(ImeAction.Done) + }), + ) + + Box(Modifier.align(Alignment.CenterHorizontally).padding(vertical = DEFAULT_PADDING)) { + SetPassphraseButton(disabled, onClickUpdate) + } + + Column { + SectionTextFooter(generalGetString(MR.strings.you_have_to_enter_passphrase_every_time)) + SectionTextFooter(annotatedStringResource(MR.strings.impossible_to_recover_passphrase)) + } + } + + Spacer(Modifier.weight(1f)) + SkipButton(progressIndicator.value, nextStep) + + SectionBottomSpacer() + } +} + +@Composable +private fun SetPassphraseButton(disabled: Boolean, onClick: () -> Unit) { + SimpleButtonIconEnded( + stringResource(MR.strings.set_database_passphrase), + painterResource(MR.images.ic_check), + style = MaterialTheme.typography.h2, + color = if (disabled) MaterialTheme.colors.secondary else MaterialTheme.colors.primary, + disabled = disabled, + click = onClick + ) +} + +@Composable +private fun SkipButton(disabled: Boolean, onClick: () -> Unit) { + SimpleButtonIconEnded(stringResource(MR.strings.use_random_passphrase), painterResource(MR.images.ic_chevron_right), color = + if (disabled) MaterialTheme.colors.secondary else WarningOrange, disabled = disabled, click = onClick) + Text( + stringResource(MR.strings.you_can_change_it_later), + Modifier + .fillMaxWidth() + .padding(horizontal = DEFAULT_PADDING * 3), + style = MaterialTheme.typography.subtitle1, + color = MaterialTheme.colors.secondary, + textAlign = TextAlign.Center, + ) +} + +@Composable +private fun ProgressIndicator() { + Box( + Modifier.fillMaxSize(), + contentAlignment = Alignment.Center + ) { + CircularProgressIndicator( + Modifier + .padding(horizontal = 2.dp) + .size(30.dp), + color = MaterialTheme.colors.secondary, + strokeWidth = 3.dp + ) + } +} + +private suspend fun startChat(key: String?) { + val m = ChatModel + initChatController(key) + m.chatDbChanged.value = false + m.chatRunning.value = true +} diff --git a/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/onboarding/SimpleXInfo.kt b/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/onboarding/SimpleXInfo.kt index 8248194eb..f20c4508b 100644 --- a/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/onboarding/SimpleXInfo.kt +++ b/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/onboarding/SimpleXInfo.kt @@ -6,7 +6,6 @@ import androidx.compose.foundation.layout.* import androidx.compose.foundation.shape.RoundedCornerShape import androidx.compose.material.* import androidx.compose.runtime.Composable -import androidx.compose.runtime.MutableState import androidx.compose.ui.Alignment import androidx.compose.ui.Modifier import androidx.compose.ui.graphics.painter.Painter @@ -25,7 +24,7 @@ import dev.icerock.moko.resources.StringResource fun SimpleXInfo(chatModel: ChatModel, onboarding: Boolean = true) { SimpleXInfoLayout( user = chatModel.currentUser.value, - onboardingStage = if (onboarding) chatModel.onboardingStage else null, + onboardingStage = if (onboarding) chatModel.controller.appPrefs.onboardingStage else null, showModal = { modalView -> { if (onboarding) ModalManager.fullscreen.showModal { modalView(chatModel) } else ModalManager.start.showModal { modalView(chatModel) } } }, ) } @@ -33,7 +32,7 @@ fun SimpleXInfo(chatModel: ChatModel, onboarding: Boolean = true) { @Composable fun SimpleXInfoLayout( user: User?, - onboardingStage: MutableState<OnboardingStage?>?, + onboardingStage: SharedPreference<OnboardingStage>?, showModal: (@Composable (ChatModel) -> Unit) -> (() -> Unit), ) { Column( @@ -100,11 +99,11 @@ private fun InfoRow(icon: Painter, titleId: StringResource, textId: StringResour } @Composable -fun OnboardingActionButton(user: User?, onboardingStage: MutableState<OnboardingStage?>, onclick: (() -> Unit)? = null) { +fun OnboardingActionButton(user: User?, onboardingStage: SharedPreference<OnboardingStage>, onclick: (() -> Unit)? = null) { if (user == null) { - OnboardingActionButton(MR.strings.create_your_profile, onboarding = OnboardingStage.Step2_CreateProfile, onboardingStage, true, onclick) + OnboardingActionButton(MR.strings.create_your_profile, onboarding = OnboardingStage.Step2_CreateProfile, true, onclick) } else { - OnboardingActionButton(MR.strings.make_private_connection, onboarding = OnboardingStage.OnboardingComplete, onboardingStage, true, onclick) + OnboardingActionButton(MR.strings.make_private_connection, onboarding = OnboardingStage.OnboardingComplete, true, onclick) } } @@ -112,7 +111,6 @@ fun OnboardingActionButton(user: User?, onboardingStage: MutableState<Onboarding fun OnboardingActionButton( labelId: StringResource, onboarding: OnboardingStage?, - onboardingStage: MutableState<OnboardingStage?>, border: Boolean, onclick: (() -> Unit)? ) { @@ -129,7 +127,6 @@ fun OnboardingActionButton( SimpleButtonFrame(click = { onclick?.invoke() - onboardingStage.value = onboarding if (onboarding != null) { ChatController.appPrefs.onboardingStage.set(onboarding) } diff --git a/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/usersettings/Appearance.kt b/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/usersettings/Appearance.kt index d79b7b782..75e7d7201 100644 --- a/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/usersettings/Appearance.kt +++ b/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/usersettings/Appearance.kt @@ -201,12 +201,15 @@ object AppearanceScope { val supportedLanguages = mapOf( "system" to generalGetString(MR.strings.language_system), "en" to "English", + "ar" to "العربية", "bg" to "Български", "cs" to "Čeština", "de" to "Deutsch", "es" to "Español", + "fi" to "Suomi", "fr" to "Français", "it" to "Italiano", + "iw" to "עִברִית", "ja" to "日本語", "nl" to "Nederlands", "pl" to "Polski", diff --git a/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/usersettings/SettingsView.kt b/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/usersettings/SettingsView.kt index baffc02f6..8969e48b2 100644 --- a/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/usersettings/SettingsView.kt +++ b/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/usersettings/SettingsView.kt @@ -43,6 +43,7 @@ fun SettingsView(chatModel: ChatModel, setPerformLA: (Boolean) -> Unit, drawerSt profile = user.profile, stopped, chatModel.chatDbEncrypted.value == true, + remember { chatModel.controller.appPrefs.storeDBPassphrase.state }.value, remember { chatModel.controller.appPrefs.notificationsMode.state }, user.displayName, setPerformLA = setPerformLA, @@ -115,6 +116,7 @@ fun SettingsLayout( profile: LocalProfile, stopped: Boolean, encrypted: Boolean, + passphraseSaved: Boolean, notificationsMode: State<NotificationsMode>, userDisplayName: String, setPerformLA: (Boolean) -> Unit, @@ -162,7 +164,7 @@ fun SettingsLayout( SettingsActionItem(painterResource(MR.images.ic_videocam), stringResource(MR.strings.settings_audio_video_calls), showSettingsModal { CallSettingsView(it, showModal) }, disabled = stopped, extraPadding = true) SettingsActionItem(painterResource(MR.images.ic_lock), stringResource(MR.strings.privacy_and_security), showSettingsModal { PrivacySettingsView(it, showSettingsModal, setPerformLA) }, disabled = stopped, extraPadding = true) SettingsActionItem(painterResource(MR.images.ic_light_mode), stringResource(MR.strings.appearance_settings), showSettingsModal { AppearanceView(it, showSettingsModal) }, extraPadding = true) - DatabaseItem(encrypted, showSettingsModal { DatabaseView(it, showSettingsModal) }, stopped) + DatabaseItem(encrypted, passphraseSaved, showSettingsModal { DatabaseView(it, showSettingsModal) }, stopped) } SectionDividerSpaced() @@ -207,7 +209,7 @@ expect fun SettingsSectionApp( withAuth: (title: String, desc: String, block: () -> Unit) -> Unit ) -@Composable private fun DatabaseItem(encrypted: Boolean, openDatabaseView: () -> Unit, stopped: Boolean) { +@Composable private fun DatabaseItem(encrypted: Boolean, saved: Boolean, openDatabaseView: () -> Unit, stopped: Boolean) { SectionItemViewWithIcon(openDatabaseView) { Row( Modifier.fillMaxWidth(), @@ -217,7 +219,7 @@ expect fun SettingsSectionApp( Icon( painterResource(MR.images.ic_database), contentDescription = stringResource(MR.strings.database_passphrase_and_export), - tint = if (encrypted) MaterialTheme.colors.secondary else WarningOrange, + tint = if (encrypted && (appPlatform.isAndroid || !saved)) MaterialTheme.colors.secondary else WarningOrange, ) TextIconSpaced(true) Text(stringResource(MR.strings.database_passphrase_and_export)) @@ -393,9 +395,13 @@ fun SettingsActionItemWithContent(icon: Painter?, text: String? = null, click: ( val padding = with(LocalDensity.current) { 6.sp.toDp() } Text(text, Modifier.weight(1f).padding(vertical = padding), color = if (disabled) MaterialTheme.colors.secondary else MaterialTheme.colors.onBackground) Spacer(Modifier.width(DEFAULT_PADDING)) - } - Row(Modifier.widthIn(max = (windowWidth() - DEFAULT_PADDING * 2) / 2)) { - content() + Row(Modifier.widthIn(max = (windowWidth() - DEFAULT_PADDING * 2) / 2)) { + content() + } + } else { + Row { + content() + } } } } @@ -469,6 +475,7 @@ fun PreviewSettingsLayout() { profile = LocalProfile.sampleData, stopped = false, encrypted = false, + passphraseSaved = false, notificationsMode = remember { mutableStateOf(NotificationsMode.OFF) }, userDisplayName = "Alice", setPerformLA = { _ -> }, diff --git a/apps/multiplatform/common/src/commonMain/resources/MR/ar/strings.xml b/apps/multiplatform/common/src/commonMain/resources/MR/ar/strings.xml index 03adcb4ec..3c39d8f80 100644 --- a/apps/multiplatform/common/src/commonMain/resources/MR/ar/strings.xml +++ b/apps/multiplatform/common/src/commonMain/resources/MR/ar/strings.xml @@ -24,7 +24,7 @@ <string name="member_role_will_be_changed_with_notification">سيتم تغيير الدور إلى \"%s\". سيتم إبلاغ كل فرد في المجموعة.</string> <string name="member_role_will_be_changed_with_invitation">سيتم تغيير الدور إلى \"%s\". سيتلقى العضو دعوة جديدة.</string> <string name="smp_servers_per_user">خوادم الاتصالات الجديدة لملف تعريف الدردشة الحالي الخاص بك</string> - <string name="switch_receiving_address_desc">هذه الميزة تجريبية! ستعمل فقط إذا كان لدى العميل الآخر الإصدار 4.2 مثبتًا. يجب أن ترى الرسالة في المحادثة بمجرد اكتمال تغيير العنوان - يرجى التحقق من أنه لا يزال بإمكانك تلقي الرسائل من جهة الاتصال هذه (أو عضو المجموعة).</string> + <string name="switch_receiving_address_desc">سيتم تغيير عنوان الاستلام إلى خادم مختلف. سيتم إكمال تغيير العنوان بعد اتصال المرسل بالإنترنت.</string> <string name="this_link_is_not_a_valid_connection_link">هذا الارتباط ليس ارتباط اتصال صالح!</string> <string name="allow_verb">يسمح</string> <string name="smp_servers_preset_add">أضف خوادم محددة مسبقًا</string> diff --git a/apps/multiplatform/common/src/commonMain/resources/MR/base/strings.xml b/apps/multiplatform/common/src/commonMain/resources/MR/base/strings.xml index ea6d13a35..2b83ff869 100644 --- a/apps/multiplatform/common/src/commonMain/resources/MR/base/strings.xml +++ b/apps/multiplatform/common/src/commonMain/resources/MR/base/strings.xml @@ -769,6 +769,11 @@ <string name="onboarding_notifications_mode_periodic_desc"><![CDATA[<b>Good for battery</b>. Background service checks messages every 10 minutes. You may miss calls or urgent messages.]]></string> <string name="onboarding_notifications_mode_service_desc"><![CDATA[<b>Uses more battery</b>! Background service always runs – notifications are shown as soon as messages are available.]]></string> + <!-- SetupDatabasePassphrase.kt --> + <string name="setup_database_passphrase">Setup database passphrase</string> + <string name="you_can_change_it_later">Random passphrase is stored in settings as plaintext.\nYou can change it later.</string> + <string name="use_random_passphrase">Use random passphrase</string> + <!-- MakeConnection --> <string name="paste_the_link_you_received">Paste received link</string> @@ -939,6 +944,7 @@ <string name="import_database">Import database</string> <string name="new_database_archive">New database archive</string> <string name="old_database_archive">Old database archive</string> + <string name="open_database_folder">Open database folder</string> <string name="delete_database">Delete database</string> <string name="error_starting_chat">Error starting chat</string> <string name="stop_chat_question">Stop chat?</string> @@ -984,9 +990,11 @@ <!-- DatabaseEncryptionView.kt --> <string name="save_passphrase_in_keychain">Save passphrase in Keystore</string> + <string name="save_passphrase_in_settings">Save passphrase in settings</string> <string name="database_encrypted">Database encrypted!</string> <string name="error_encrypting_database">Error encrypting database</string> <string name="remove_passphrase_from_keychain">Remove passphrase from Keystore?</string> + <string name="remove_passphrase_from_settings">Remove passphrase from settings?</string> <string name="notifications_will_be_hidden">Notifications will be delivered only until the app stops!</string> <string name="remove_passphrase">Remove</string> <string name="encrypt_database">Encrypt</string> @@ -995,18 +1003,23 @@ <string name="new_passphrase">New passphrase…</string> <string name="confirm_new_passphrase">Confirm new passphrase…</string> <string name="update_database_passphrase">Update database passphrase</string> + <string name="set_database_passphrase">Set database passphrase</string> <string name="enter_correct_current_passphrase">Please enter correct current passphrase.</string> <string name="database_is_not_encrypted">Your chat database is not encrypted - set passphrase to protect it.</string> <string name="keychain_is_storing_securely">Android Keystore is used to securely store passphrase - it allows notification service to work.</string> + <string name="settings_is_storing_in_clear_text">The passphrase is stored in settings as plaintext.</string> <string name="encrypted_with_random_passphrase">Database is encrypted using a random passphrase, you can change it.</string> <string name="impossible_to_recover_passphrase"><![CDATA[<b>Please note</b>: you will NOT be able to recover or change passphrase if you lose it.]]></string> <string name="keychain_allows_to_receive_ntfs">Android Keystore will be used to securely store passphrase after you restart the app or change passphrase - it will allow receiving notifications.</string> + <string name="passphrase_will_be_saved_in_settings">The passphrase will be stored in settings as plaintext after you change it or restart the app.</string> <string name="you_have_to_enter_passphrase_every_time">You have to enter passphrase every time the app starts - it is not stored on the device.</string> <string name="encrypt_database_question">Encrypt database?</string> <string name="change_database_passphrase_question">Change database passphrase?</string> <string name="database_will_be_encrypted">Database will be encrypted.</string> <string name="database_will_be_encrypted_and_passphrase_stored">Database will be encrypted and the passphrase stored in the Keystore.</string> + <string name="database_will_be_encrypted_and_passphrase_stored_in_settings">Database will be encrypted and the passphrase stored in settings.</string> <string name="database_encryption_will_be_updated">Database encryption passphrase will be updated and stored in the Keystore.</string> + <string name="database_encryption_will_be_updated_in_settings">Database encryption passphrase will be updated and stored in settings.</string> <string name="database_passphrase_will_be_updated">Database encryption passphrase will be updated.</string> <string name="store_passphrase_securely">Please store passphrase securely, you will NOT be able to change it if you lose it.</string> <string name="store_passphrase_securely_without_recover">Please store passphrase securely, you will NOT be able to access chat if you lose it.</string> diff --git a/apps/multiplatform/common/src/commonMain/resources/MR/cs/strings.xml b/apps/multiplatform/common/src/commonMain/resources/MR/cs/strings.xml index badd8cbc3..4b4bac96d 100644 --- a/apps/multiplatform/common/src/commonMain/resources/MR/cs/strings.xml +++ b/apps/multiplatform/common/src/commonMain/resources/MR/cs/strings.xml @@ -1337,7 +1337,7 @@ <string name="v5_2_message_delivery_receipts_descr">Druhé zaškrtnutí jsme přehlédli! ✅</string> <string name="switch_receiving_address_desc">Přijímací adresa bude změněna na jiný server. Změna adresy bude dokončena po připojení odesílatele.</string> <string name="choose_file_title">Vybrat soubor</string> - <string name="connect_via_link_incognito">Připojit se inkognito</string> + <string name="connect_via_link_incognito">Spojit se inkognito</string> <string name="turn_off_battery_optimization_button">Povolit</string> <string name="disable_notifications_button">Vypnout upozornění</string> <string name="turn_off_system_restriction_button">Otevřít nastavení aplikace</string> @@ -1355,4 +1355,25 @@ <string name="send_receipts_disabled">vypnut</string> <string name="send_receipts_disabled_alert_title">Receipts jsou zakázány</string> <string name="in_developing_title">Již brzy!</string> + <string name="connect_use_current_profile">Použít aktuální profil</string> + <string name="connect_use_new_incognito_profile">Použít nový incognito profil</string> + <string name="system_restricted_background_desc">SimpleX nemůže běžet na pozadí. Pouze při spuštěné aplikaci obdržíte upozornění.</string> + <string name="system_restricted_background_warn"><![CDATA[Chcete-li povolit oznámení, vyberte prosím <b>Baterii</b> / <b>bez omezení</b> v nastavení aplikace.]]></string> + <string name="system_restricted_background_in_call_desc">Aplikace může být uzavřena po 1 minutě na pozadí.</string> + <string name="system_restricted_background_in_call_warn"><![CDATA[Chcete-li volat na pozadí, vyberte prosím <b>Baterii</b> / <b>bez omezení</b> v nastavení aplikace.]]></string> + <string name="connect__your_profile_will_be_shared">Váš profil %1$s bude sdílen.</string> + <string name="connect_via_member_address_alert_desc">Požadavek na připojení bude zaslán tomuto členu skupiny.</string> + <string name="delivery">Doručenka</string> + <string name="receipts_groups_title_disable">Zakázat doručenky pro skupiny\?</string> + <string name="receipts_groups_title_enable">Povolit doručenky pro skupiny\?</string> + <string name="receipts_groups_override_enabled">Odeslání doručenek je povoleno pro %d skupiny</string> + <string name="receipts_section_groups">Malé skupiny (max. 20)</string> + <string name="recipient_colon_delivery_status">%s: %s</string> + <string name="rcv_group_event_2_members_connected">%s a %s připojen</string> + <string name="rcv_group_event_3_members_connected">%s, %s a %s připojeni</string> + <string name="rcv_group_event_n_members_connected">%s, %s a %d dalších členů připojeno</string> + <string name="privacy_message_draft">Rozepsáno</string> + <string name="privacy_show_last_messages">Zobrazit poslední zprávy</string> + <string name="send_receipts_disabled_alert_msg">Tato skupina má více než %1$d členů, doručenky nejsou odeslány.</string> + <string name="in_developing_desc">Tato funkce zatím není podporována. Vyzkoušejte další vydání.</string> </resources> \ No newline at end of file diff --git a/apps/multiplatform/common/src/commonMain/resources/MR/es/strings.xml b/apps/multiplatform/common/src/commonMain/resources/MR/es/strings.xml index b3630c236..dffad174f 100644 --- a/apps/multiplatform/common/src/commonMain/resources/MR/es/strings.xml +++ b/apps/multiplatform/common/src/commonMain/resources/MR/es/strings.xml @@ -146,7 +146,7 @@ <string name="smp_server_test_disconnect">Desconectar</string> <string name="notification_preview_mode_contact">Contacto</string> <string name="copy_verb">Copiar</string> - <string name="create_your_profile">Crear tu perfil</string> + <string name="create_your_profile">Crea tu perfil</string> <string name="always_use_relay">Usar siempre retransmisor</string> <string name="set_password_to_export_desc">La base de datos está cifrada con una contraseña aleatoria. Cámbiala antes de exportar.</string> <string name="total_files_count_and_size">%d archivo(s) con tamaño total de %s</string> @@ -192,7 +192,7 @@ <string name="delete_address__question">¿Eliminar la dirección\?</string> <string name="display_name__field">Nombre mostrado:</string> <string name="callstate_connecting">conectando…</string> - <string name="decentralized">Descentralizado</string> + <string name="decentralized">Descentralizada</string> <string name="database_will_be_encrypted">La base de datos será cifrada.</string> <string name="delete_chat_archive_question">¿Eliminar archivo del chat\?</string> <string name="create_group_link">Crear enlace de grupo</string> @@ -485,7 +485,7 @@ <string name="mark_unread">Marcar como no leído</string> <string name="invalid_QR_code">Código QR inválido</string> <string name="incorrect_code">¡Código de seguridad incorrecto!</string> - <string name="markdown_in_messages">Sintaxis markdown en los mensajes</string> + <string name="markdown_in_messages">Sintaxis Markdown</string> <string name="network_use_onion_hosts_no">No</string> <string name="callstatus_missed">llamada perdida</string> <string name="import_database_confirmation">Importar</string> @@ -717,7 +717,7 @@ <string name="la_notice_title_simplex_lock">Bloqueo SimpleX</string> <string name="auth_unlock">Desbloquear</string> <string name="this_text_is_available_in_settings">Este texto está disponible en Configuración</string> - <string name="switch_receiving_address_desc">La dirección de recepción se cambiará. El cambio se completará cuando el remitente esté en línea.</string> + <string name="switch_receiving_address_desc">La dirección de recepción pasará a otro servidor. El cambio se completará cuando el remitente esté en línea.</string> <string name="chat_lock">Bloqueo SimpleX</string> <string name="using_simplex_chat_servers">Usando servidores SimpleX Chat.</string> <string name="network_session_mode_transport_isolation">Aislamiento de transporte</string> @@ -749,11 +749,11 @@ <string name="share_invitation_link">Compartir enlace de un uso</string> <string name="update_network_session_mode_question">¿Actualizar el modo de aislamiento de transporte\?</string> <string name="icon_descr_speaker_on">Altavoz activado</string> - <string name="stop_chat_to_enable_database_actions">Para habilitar las acciones sobre la base de datos, previamente debes detener Chat</string> + <string name="stop_chat_to_enable_database_actions">Detén SimpleX para habilitar las acciones sobre la base de datos.</string> <string name="connection_you_accepted_will_be_cancelled">¡La conexión que has aceptado se cancelará!</string> <string name="database_initialization_error_desc">La base de datos no funciona correctamente. Pulsa para saber más</string> <string name="moderate_message_will_be_marked_warning">El mensaje será marcado como moderado para todos los miembros.</string> - <string name="next_generation_of_private_messaging">La próxima generación de mensajería privada</string> + <string name="next_generation_of_private_messaging">La nueva generación de mensajería privada</string> <string name="delete_files_and_media_desc">Esta acción no se puede deshacer. Se eliminarán todos los archivos y multimedia recibidos y enviados. Las imágenes de baja resolución permanecerán.</string> <string name="enable_automatic_deletion_message">Esta acción no se puede deshacer. Se eliminarán los mensajes enviados y recibidos anteriores a la selección. Puede tardar varios minutos.</string> <string name="messages_section_description">Esta configuración se aplica a los mensajes del perfil actual</string> @@ -914,7 +914,7 @@ <string name="your_settings">Configuración</string> <string name="your_SMP_servers">Servidores SMP</string> <string name="you_control_your_chat">¡Tú controlas tu chat!</string> - <string name="your_profile_is_stored_on_your_device">Tu perfil, contactos y mensajes entregados se almacenan en tu dispositivo.</string> + <string name="your_profile_is_stored_on_your_device">Tu perfil, contactos y mensajes se almacenan en tu dispositivo.</string> <string name="callstate_waiting_for_answer">esperando respuesta…</string> <string name="callstate_waiting_for_confirmation">esperando confirmación…</string> <string name="onboarding_notifications_mode_off">Cuando la aplicación se está ejecutando</string> @@ -923,7 +923,7 @@ <string name="your_ice_servers">Servidores ICE</string> <string name="your_privacy">Privacidad</string> <string name="settings_section_title_you">MIS DATOS</string> - <string name="your_chat_database">Base de datos Chat</string> + <string name="your_chat_database">Base de datos</string> <string name="you_can_start_chat_via_setting_or_by_restarting_the_app">Puedes iniciar el chat en Configuración / Base de datos o reiniciando la aplicación.</string> <string name="you_sent_group_invitation">Has enviado una invitación de grupo</string> <string name="num_contacts_selected">%d contacto(s) seleccionado(s)</string> @@ -1126,7 +1126,7 @@ <string name="learn_more">Más información</string> <string name="if_you_cant_meet_in_person">Si no puedes reunirte en persona, **muestra el código QR por videollamada**, o comparte el enlace.</string> <string name="scan_qr_to_connect_to_contact">Para conectarse, tu contacto puede escanear el código QR o usar el enlace en la aplicación.</string> - <string name="create_simplex_address">Crear dirección SimpleX</string> + <string name="create_simplex_address">Crear tu dirección SimpleX</string> <string name="auto_accept_contact">Auto aceptar</string> <string name="group_welcome_preview">Vista previa</string> <string name="opening_database">Abriendo base de datos…</string> @@ -1144,15 +1144,15 @@ <string name="export_theme">Exportar tema</string> <string name="color_surface">Menús y alertas</string> <string name="add_address_to_your_profile">Añade la dirección a tu perfil para que tus contactos puedan compartirla con otros. La actualización del perfil se enviará a tus contactos.</string> - <string name="learn_more_about_address">Acerca de dirección SimpleX</string> + <string name="learn_more_about_address">Acerca de la dirección SimpleX</string> <string name="address_section_title">Dirección</string> <string name="all_your_contacts_will_remain_connected_update_sent">Todos tus contactos permanecerán conectados. La actualización del perfil se enviará a tus contactos.</string> <string name="continue_to_next_step">Continuar</string> <string name="dark_theme">Tema oscuro</string> <string name="customize_theme_title">Personalizar tema</string> <string name="enter_welcome_message_optional">Introduce mensaje de bienvenida… (opcional)</string> - <string name="create_address_and_let_people_connect">Crear una dirección para que otras personas se puedan conectar contigo.</string> - <string name="dont_create_address">No crear dirección</string> + <string name="create_address_and_let_people_connect">Crea una dirección para que otras personas puedan conectar contigo.</string> + <string name="dont_create_address">No crear dirección SimpleX</string> <string name="email_invite_body">¡Hola! \nConecta conmigo a través de SimpleX Chat: %s</string> <string name="import_theme">Importar tema</string> @@ -1170,7 +1170,7 @@ <string name="stop_sharing">Dejar de compartir</string> <string name="stop_sharing_address">¿Dejar de compartir la dirección\?</string> <string name="theme_colors_section_title">COLORES DEL TEMA</string> - <string name="you_can_create_it_later">Puedes crearlo más tarde</string> + <string name="you_can_create_it_later">Puedes crearla más tarde</string> <string name="share_address_with_contacts_question">¿Compartir la dirección con los contactos\?</string> <string name="share_with_contacts">Compartir con contactos</string> <string name="color_title">Título</string> diff --git a/apps/multiplatform/common/src/commonMain/resources/MR/fi/strings.xml b/apps/multiplatform/common/src/commonMain/resources/MR/fi/strings.xml index ef116dce8..cee4837a7 100644 --- a/apps/multiplatform/common/src/commonMain/resources/MR/fi/strings.xml +++ b/apps/multiplatform/common/src/commonMain/resources/MR/fi/strings.xml @@ -82,7 +82,7 @@ <string name="info_row_connection">Yhteys</string> <string name="ttl_d">%dd</string> <string name="ttl_days">%d päivää</string> - <string name="disappearing_prohibited_in_this_chat">Tuhoutuvat viestit ovat kiellettyjä tässä keskustelussa.</string> + <string name="disappearing_prohibited_in_this_chat">Katoavat viestit ovat kiellettyjä tässä keskustelussa.</string> <string name="network_session_mode_user_description"><![CDATA[Erillistä TCP-yhteyttä (ja SOCKS-tunnistetietoja) käytetään <b>jokaisessa käyttämässäsi sovelluksen chat-profiilissa</b>.]]></string> <string name="app_version_code">Sovellusversio: %s</string> <string name="delete_address">Poista osoite</string> @@ -140,7 +140,7 @@ <string name="delete_verb">Poista</string> <string name="delete_message__question">Poista viesti\?</string> <string name="group_connection_pending">yhdistää…</string> - <string name="disappearing_message">Tuhoutuva viesti</string> + <string name="disappearing_message">Katoava viesti</string> <string name="send_disappearing_message_custom_time">Mukautettu aika</string> <string name="delete_contact_menu_action">Poista</string> <string name="delete_group_menu_action">Poista</string> @@ -158,7 +158,7 @@ <string name="settings_developer_tools">Kehittäjän työkalut</string> <string name="cannot_access_keychain">Ei pääsyä Keystoreen tietokannan salasanan tallentamiseksi</string> <string name="share_text_database_id">Tietokannan tunnus: %d</string> - <string name="info_row_disappears_at">Tuhoutuu klo</string> + <string name="info_row_disappears_at">Katoaa klo</string> <string name="chat_database_deleted">Keskustelujen tietokanta poistettu</string> <string name="delete_chat_profile_question">Poista keskusteluprofiili\?</string> <string name="delete_messages_after">Poista viestit tämän jälkeen</string> @@ -187,7 +187,7 @@ <string name="audio_call_no_encryption">äänipuhelu (ei e2e-salattu)</string> <string name="always_use_relay">Käytä aina relettä</string> <string name="allow_your_contacts_to_send_disappearing_messages">Salli kontaktiesi lähettää katoavia viestejä.</string> - <string name="timed_messages">Tuhoutuvat viestit</string> + <string name="timed_messages">Katoavat viestit</string> <string name="icon_descr_context">Kontekstikuvake</string> <string name="scan_QR_code_to_connect_to_contact_who_shows_QR_code"><![CDATA[<b>Skannaa QR-koodi</b>: muodostaaksesi yhteyden kontaktiisi, joka näyttää QR-koodin sinulle.]]></string> <string name="icon_descr_cancel_live_message">Peruuta live-viesti</string> @@ -199,7 +199,7 @@ <string name="settings_audio_video_calls">Ääni- ja videopuhelut</string> <string name="call_on_lock_screen">Puhelut lukitusnäytöllä:</string> <string name="conn_level_desc_direct">suora</string> - <string name="disappearing_messages_are_prohibited">Tuhoutuvat viestit ovat kiellettyjä tässä ryhmässä.</string> + <string name="disappearing_messages_are_prohibited">Katoavat viestit ovat kiellettyjä tässä ryhmässä.</string> <string name="server_connected">yhdistetty</string> <string name="display_name_connecting">yhdistää…</string> <string name="display_name_connection_established">yhteys luotu</string> @@ -240,7 +240,7 @@ <string name="both_you_and_your_contact_can_send_disappearing">Sekä sinä että kontaktisi voitte lähettää katoavia viestejä.</string> <string name="chat_preferences_contact_allows">Kontakti sallii</string> <string name="chat_preferences_default">oletus (%s)</string> - <string name="v4_4_disappearing_messages">Tuhoutuvat viestit</string> + <string name="v4_4_disappearing_messages">Katoavat viestit</string> <string name="copied">Kopioitu leikepöydälle</string> <string name="share_one_time_link">Luo kertaluonteinen kutsulinkki</string> <string name="mtr_error_no_down_migration">tietokantaversio on uudempi kuin sovellus, mutta ei alaspäin siirtymistä: %s</string> @@ -336,7 +336,7 @@ <string name="change_verb">Muuta</string> <string name="item_info_current">(nykyinen)</string> <string name="share_text_deleted_at">Poistettu: %s</string> - <string name="share_text_disappears_at">Tuhoutuu klo: %s</string> + <string name="share_text_disappears_at">Katoaa klo: %s</string> <string name="create_secret_group_title">Luo salainen ryhmä</string> <string name="chat_preferences_always">aina</string> <string name="cant_delete_user_profile">Käyttäjäprofiilia ei voi poistaa!</string> @@ -406,7 +406,7 @@ <string name="join_group_question">Liity ryhmään\?</string> <string name="network_option_enable_tcp_keep_alive">Ota TCP-säilytys käyttöön</string> <string name="incognito">Incognito</string> - <string name="incognito_info_protects">Incognito-tila suojaa pääprofiilisi nimen ja kuvan yksityisyyttä – jokaiselle uudelle yhteyshenkilölle luodaan uusi satunnainen profiili.</string> + <string name="incognito_info_protects">Incognito-tila suojaa yksityisyyttäsi käyttämällä uutta satunnaista profiilia jokaiselle kontaktille.</string> <string name="ttl_mth">%dmth</string> <string name="ttl_m">%dm</string> <string name="v4_6_group_welcome_message">Ryhmän tervetuloviesti</string> @@ -451,7 +451,7 @@ <string name="import_database_question">Tuo keskustelujen-tietokanta\?</string> <string name="file_with_path">Tiedosto: %s</string> <string name="error_removing_member">Virhe poistettaessa jäsentä</string> - <string name="message_deletion_prohibited">Viestien peruuttamaton poistaminen on kielletty tässä keskustelussa.</string> + <string name="message_deletion_prohibited">Viestien peruuttamaton poisto on kielletty tässä keskustelussa.</string> <string name="join_group_button">Liity</string> <string name="join_group_incognito_button">Liity incognito-tilassa</string> <string name="joining_group">Liittyy ryhmään</string> @@ -474,10 +474,10 @@ <string name="image_will_be_received_when_contact_is_online">Kuva vastaanotetaan, kun kontaktisi on verkossa, odota tai tarkista myöhemmin!</string> <string name="if_you_cant_meet_in_person">Jos et voi tavata henkilökohtaisesti, näytä QR-koodi videopuhelussa tai jaa linkki.</string> <string name="onboarding_notifications_mode_subtitle">Voit muuttaa sitä myöhemmin asetuksista.</string> - <string name="encrypt_database_question">Salataanko tietokanta\?</string> + <string name="encrypt_database_question">Salaa tietokanta\?</string> <string name="button_edit_group_profile">Muokkaa ryhmäprofiilia</string> <string name="delete_group_for_all_members_cannot_undo_warning">Ryhmä poistetaan kaikilta jäseniltä - tätä ei voi kumota!</string> - <string name="delete_group_for_self_cannot_undo_warning">Ryhmä poistetaan sinulta - tätä ei voi peruuttaa!</string> + <string name="delete_group_for_self_cannot_undo_warning">Ryhmä poistetaan sinulta - tätä ei voi perua!</string> <string name="error_creating_link_for_group">Virhe ryhmälinkin luomisessa</string> <string name="info_row_group">Ryhmä</string> <string name="incognito_info_allows">Se mahdollistaa useiden nimettömien yhteyksien muodostamisen yhdessä keskusteluprofiilissa ilman, että niiden välillä on jaettuja tietoja.</string> @@ -514,7 +514,7 @@ <string name="onboarding_notifications_mode_service">Välitön</string> <string name="encrypted_audio_call">e2e-salattu äänipuhelu</string> <string name="allow_accepting_calls_from_lock_screen">Ota puhelut käyttöön lukitusnäytöltä asetuksista.</string> - <string name="status_e2e_encrypted">e2e salattu</string> + <string name="status_e2e_encrypted">e2e-salattu</string> <string name="settings_section_title_incognito">Incognito-tila</string> <string name="error_with_info">Virhe: %s</string> <string name="alert_message_group_invitation_expired">Ryhmäkutsu ei ole enää voimassa, lähettäjä poisti sen.</string> @@ -543,15 +543,15 @@ <string name="downgrade_and_open_chat">Alenna ja avaa chat</string> <string name="icon_descr_group_inactive">Ei-aktiivinen ryhmä</string> <string name="v4_3_improved_privacy_and_security_desc">Piilota sovellusnäyttö viimeisimmissä sovelluksissa.</string> - <string name="v4_5_italian_interface">Italian käyttöliittymä</string> - <string name="v5_0_large_files_support_descr">Nopea ja ei odota, kunnes lähettäjä on online-tilassa!</string> + <string name="v4_5_italian_interface">Italialainen käyttöliittymä</string> + <string name="v5_0_large_files_support_descr">Nopea ja ei odotusta, kunnes lähettäjä on online-tilassa!</string> <string name="v4_6_reduced_battery_usage">Entisestä vähentynyt akun käyttö</string> <string name="v5_1_japanese_portuguese_interface">Japanin ja portugalin käyttöliittymä</string> <string name="error_saving_file">Virhe tiedoston tallentamisessa</string> <string name="icon_descr_help">apua</string> <string name="incorrect_code">Väärä turvakoodi!</string> <string name="error_sending_message">Virhe viestin lähettämisessä</string> - <string name="turn_off_battery_optimization"><![CDATA[Jotta voit käyttää sitä, <b>poista akun optimointi käytöstä</b> kohteelle SimpleX seuraavassa valintaikkunassa. Muussa tapauksessa ilmoitukset poistetaan käytöstä.]]></string> + <string name="turn_off_battery_optimization"><![CDATA[Käyttääksesi sitä, <b> salli SimpleX:n toimia taustalla </b> seuraavassa ikkunassa. Muutoin ilmoitukset poistetaan käytöstä.]]></string> <string name="notification_preview_mode_hidden">Piilotettu</string> <string name="la_immediately">Heti</string> <string name="la_enter_app_passcode">Syötä pääsykoodi</string> @@ -570,7 +570,7 @@ <string name="enable_lock">Ota lukitus käyttöön</string> <string name="group_invitation_item_description">kutsu ryhmään %1$s</string> <string name="icon_descr_add_members">Kutsu jäseniä</string> - <string name="group_invitation_expired">Ryhmäkutsu on vanhentunut</string> + <string name="group_invitation_expired">Vanhentunut ryhmäkutsu</string> <string name="rcv_group_event_member_added">kutsuttu %1$s</string> <string name="group_full_name_field">Ryhmän koko nimi:</string> <string name="full_name_optional__prompt">Koko nimi (valinnainen)</string> @@ -592,7 +592,7 @@ <string name="section_title_for_console">KONSOLIIN</string> <string name="group_member_status_group_deleted">poistettu ryhmä</string> <string name="snd_group_event_group_profile_updated">ryhmäprofiili päivitetty</string> - <string name="alert_title_group_invitation_expired">Kutsu on vanhentunut!</string> + <string name="alert_title_group_invitation_expired">Vanhentunut kutsu!</string> <string name="group_member_status_invited">kutsuttu</string> <string name="conn_level_desc_indirect">epäsuora (%1$s)</string> <string name="group_display_name_field">Ryhmän näyttönimi:</string> @@ -602,10 +602,10 @@ <string name="group_members_can_delete">Ryhmän jäsenet voivat poistaa lähetetyt viestit peruuttamattomasti.</string> <string name="group_members_can_send_dms">Ryhmän jäsenet voivat lähettää suoraviestejä.</string> <string name="group_members_can_send_voice">Ryhmän jäsenet voivat lähettää ääniviestejä.</string> - <string name="message_deletion_prohibited_in_chat">Viestien peruuttamaton poistaminen on kielletty tässä ryhmässä.</string> + <string name="message_deletion_prohibited_in_chat">Viestien peruuttamaton poisto on kielletty tässä ryhmässä.</string> <string name="ttl_months">%d kuukautta</string> <string name="ttl_sec">%d sek</string> - <string name="v5_1_message_reactions_descr">Vihdoinkin meillä on ne! 🚀</string> + <string name="v5_1_message_reactions_descr">Vihdoinkin meillä! 🚀</string> <string name="custom_time_unit_hours">tuntia</string> <string name="please_check_correct_link_and_maybe_ask_for_a_new_one">Tarkista, että käytit oikeaa linkkiä tai pyydä kontaktiasi lähettämään sinulle uusi linkki.</string> <string name="sender_may_have_deleted_the_connection_request">Lähettäjä on saattanut poistaa yhteyspyynnön.</string> @@ -619,7 +619,7 @@ <string name="notification_preview_mode_message">Viestin teksti</string> <string name="notification_preview_mode_contact_desc">Näytä vain kontakti</string> <string name="notification_preview_new_message">uusi viesti</string> - <string name="la_notice_title_simplex_lock">Simplex Lock</string> + <string name="la_notice_title_simplex_lock">SimpleX Lock</string> <string name="auth_simplex_lock_turned_on">SimpleX Lock päällä</string> <string name="auth_open_chat_console">Avaa keskustelukonsoli</string> <string name="message_delivery_error_title">Viestin toimitusvirhe</string> @@ -848,7 +848,7 @@ <string name="read_more_in_github_with_link"><![CDATA[Lue lisää <font color="#0088ff">GitHub-arkistostamme</font>.]]></string> <string name="onboarding_notifications_mode_periodic">Säännölliset</string> <string name="only_client_devices_store_contacts_groups_e2e_encrypted_messages"><![CDATA[Vain asiakaslaitteet tallentavat käyttäjäprofiileja, yhteystietoja, ryhmiä ja viestejä, jotka on lähetetty <b>2-kerroksisella päästä päähän -salauksella</b>.]]></string> - <string name="read_more_in_github">Lue lisää GitHub-arkistostamme.</string> + <string name="read_more_in_github">Lue lisää GitHub-tietovarastostamme.</string> <string name="paste_the_link_you_received">Liitä vastaanotettu linkki</string> <string name="relay_server_protects_ip">Välityspalvelin suojaa IP-osoitteesi, mutta se voi tarkkailla puhelun kestoa.</string> <string name="open_simplex_chat_to_accept_call">Avaa SimpleX Chat hyväksyäksesi puhelun</string> @@ -1007,7 +1007,7 @@ <string name="prohibit_message_reactions">Estä viestireaktiot.</string> <string name="prohibit_sending_disappearing_messages">Estä katoavien viestien lähettäminen.</string> <string name="accept_feature_set_1_day">Aseta 1 päivä</string> - <string name="only_your_contact_can_make_calls">Vain yhteyshenkilösi voi soittaa puheluita.</string> + <string name="only_your_contact_can_make_calls">Vain kontaktisi voi soittaa puheluita.</string> <string name="prohibit_calls">Estä ääni- ja videopuhelut.</string> <string name="prohibit_direct_messages">Estä suorien viestien lähettäminen jäsenille.</string> <string name="message_reactions_prohibited_in_this_chat">Viestireaktiot ovat kiellettyjä tässä keskustelussa.</string> @@ -1043,7 +1043,7 @@ <string name="xftp_servers">XFTP-palvelimet</string> <string name="smp_servers_your_server">Palvelimesi</string> <string name="smp_servers_your_server_address">Palvelimesi osoite</string> - <string name="enable_automatic_deletion_message">Tätä toimintoa ei voi kumota - valittua aikaisemmin lähetetyt ja vastaanotetut viestit poistetaan. Se voi kestää useita minuutteja.</string> + <string name="enable_automatic_deletion_message">Tätä toimintoa ei voi kumota - valittua aikaisemmin lähetetyt ja vastaanotetut viestit poistetaan. Tämä voi kestää useita minuutteja.</string> <string name="whats_new">Uusimmat</string> <string name="you_will_still_receive_calls_and_ntfs">Saat edelleen puheluita ja ilmoituksia mykistetyiltä profiileilta, kun ne ovat aktiivisia.</string> <string name="network_disable_socks">Käytä suoraa Internet-yhteyttä\?</string> @@ -1199,7 +1199,7 @@ <string name="videos_limit_title">Liikaa videoita!</string> <string name="voice_message">Ääniviesti</string> <string name="waiting_for_video">Odottaa videota</string> - <string name="switch_receiving_address_desc">Tämä ominaisuus on kokeellinen! Se toimii vain, jos toisella on asennettuna versio 4.2. Sinun pitäisi nähdä viesti keskustelussa, kun osoitteenmuutos on valmis - tarkista, että voit edelleen vastaanottaa viestejä kyseiseltä kontaktilta (tai ryhmän jäseneltä).</string> + <string name="switch_receiving_address_desc">Vastaanotto-osoite vaihdetaan toiseen palvelimeen. Osoitteenmuutos tehdään sen jälkeen, kun lähettäjä tulee verkkoon.</string> <string name="you_need_to_allow_to_send_voice">Sinun on sallittava kontaktiesi lähettää ääniviestejä, jotta voit lähettää niitä.</string> <string name="you_can_connect_to_simplex_chat_founder"><![CDATA[Voit <font color="#0088ff">olla yhteydessä SimpleX Chatin -kehittäjiin kysyäksesi kysymyksiä ja saadaksesi päivityksiä</font>.]]></string> <string name="this_QR_code_is_not_a_link">Tämä QR-koodi ei ole linkki!</string> @@ -1260,4 +1260,118 @@ <string name="settings_restart_app">Käynnistä uudelleen</string> <string name="shutdown_alert_question">Sulje\?</string> <string name="la_mode_off">Pois</string> + <string name="v5_2_message_delivery_receipts">Viestien toimituskuittaukset!</string> + <string name="v5_2_more_things_descr">- vakaampi viestien toimitus. +\n- hieman paremmat ryhmät. +\n- ja paljon muuta!</string> + <string name="delivery_receipts_are_disabled">Toimituskuittaukset poissa käytöstä!</string> + <string name="you_can_enable_delivery_receipts_later">Voit ottaa käyttöön myöhemmin asetusten kautta</string> + <string name="you_can_enable_delivery_receipts_later_alert">Voit ottaa ne käyttöön myöhemmin sovelluksen Yksityisyys & Turvallisuus -asetuksista.</string> + <string name="error_aborting_address_change">Virhe osoitteenmuutoksen keskeytyksessä</string> + <string name="abort_switch_receiving_address">Keskeytä osoitteenvaihto</string> + <string name="network_option_protocol_timeout_per_kb">Protokollan aikakatkaisu per KB</string> + <string name="files_are_prohibited_in_group">Tiedostot ja media ovat tässä ryhmässä kiellettyjä.</string> + <string name="group_members_can_send_files">Ryhmän jäsenet voivat lähettää tiedostoja ja mediaa.</string> + <string name="connect_via_link_incognito">Yhdistä Incognito</string> + <string name="connect_use_current_profile">Käytä nykyistä profiilia</string> + <string name="connect_use_new_incognito_profile">Käytä uutta incognito-profiilia</string> + <string name="turn_off_battery_optimization_button">Salli</string> + <string name="disable_notifications_button">Poista ilmoitukset käytöstä</string> + <string name="turn_off_system_restriction_button">Avaa asetukset</string> + <string name="system_restricted_background_desc">SimpleX ei toimi tausta-ajossa. Saat ilmoitukset ainostaan, kun sovellus on käynnissä.</string> + <string name="system_restricted_background_warn"><![CDATA[Ilmoitusten sallimiseksi valitse <b> Sovelluksen akun käyttö </b> / <b> rajoittamaton </b> sovellusasetuksista.]]></string> + <string name="system_restricted_background_in_call_title">Ei taustapuheluita</string> + <string name="system_restricted_background_in_call_desc">Sovellus voi sulkeutua 1 minuutin jälkeen tausta-ajossa.</string> + <string name="system_restricted_background_in_call_warn"><![CDATA[Puheluiden soittamiseksi taustalla, valitse <b>Sovelluksen akun käyttö </b> / <b> rajoittamaton </b> sovellusasetuksista.]]></string> + <string name="in_reply_to">Vastauksena</string> + <string name="abort_switch_receiving_address_question">Keskeytä osoitteenvaihto\?</string> + <string name="favorite_chat">Suosikki</string> + <string name="unfavorite_chat">Epäsuosikki</string> + <string name="connect__a_new_random_profile_will_be_shared">Uusi satunnainen profiili jaetaan.</string> + <string name="paste_the_link_you_received_to_connect_with_your_contact">Liitä linkki, jonka sait yhteydenottoon kontaktisi kanssa…</string> + <string name="connect__your_profile_will_be_shared">Profiilisi %1$s jaetaan.</string> + <string name="receipts_groups_title_disable">Kuittaukset pois käytöstä ryhmiltä\?</string> + <string name="in_developing_title">Tulossa pian!</string> + <string name="receipts_groups_disable_for_all">Poista käytöstä kaikilta ryhmiltä</string> + <string name="files_and_media">Tiedostot ja media</string> + <string name="receipts_groups_enable_keep_overrides">Salli (pidä ryhmäohitukset)</string> + <string name="sending_delivery_receipts_will_be_enabled_all_profiles">"Toimituskuittauksien lähettäminen otetaan käyttöön kaikille kontakteille näkyvissä keskusteluprofiileissa."</string> + <string name="receipts_groups_override_enabled">Kuittauksien lähettäminen on käytössä %d ryhmille</string> + <string name="abort_switch_receiving_address_confirm">Keskeytä</string> + <string name="sync_connection_force_confirm">Uudelleenneuvottele</string> + <string name="sync_connection_force_question">Uudelleenneuvottele salaus\?</string> + <string name="sync_connection_force_desc">Salaus toimii ja uutta salaussopimusta ei tarvita. Tämä voi johtaa yhteysvirheisiin!</string> + <string name="receipts_section_description">Nämä asetukset koskevat nykyistä profiiliasi</string> + <string name="receipts_section_description_1">Ne voidaan ohittaa kontakti- ja ryhmäasetuksissa.</string> + <string name="conn_event_ratchet_sync_ok">salaus ok</string> + <string name="conn_event_ratchet_sync_allowed">salauksen uudelleenneuvottelu sallittu</string> + <string name="fix_connection_confirm">Korjaa</string> + <string name="fix_connection_not_supported_by_contact">Kontakti ei tue korjausta</string> + <string name="fix_connection_not_supported_by_group_member">Ryhmän jäsen ei tue korjausta</string> + <string name="renegotiate_encryption">Uudelleenneuvottele salaus</string> + <string name="in_developing_desc">Tätä ominaisuutta ei vielä tueta. Kokeile seuraavaa versiota.</string> + <string name="delivery">Toimitus</string> + <string name="no_info_on_delivery">Ei toimitustietoja</string> + <string name="no_filtered_chats">Ei suodatettuja keskusteluja</string> + <string name="no_selected_chat">Ei valittua keskustelua</string> + <string name="only_owners_can_enable_files_and_media">Vain ryhmän omistajat voivat sallia tiedostoja ja mediaa.</string> + <string name="files_and_media_prohibited">Tiedostot ja media kielletty!</string> + <string name="abort_switch_receiving_address_desc">Osoitteenmuutos keskeytetään. Käytetään vanhaa vastaanotto-osoitetta.</string> + <string name="choose_file_title">Valitse tiedosto</string> + <string name="receipts_section_groups">Pienryhmät (max 20)</string> + <string name="send_receipts_disabled_alert_title">Kuittaukset pois käytöstä</string> + <string name="send_receipts_disabled_alert_msg">Ryhmällä on yli %1$d jäsentä, toimituskuittauksia ei lähetetä.</string> + <string name="recipient_colon_delivery_status">%s: %s</string> + <string name="v5_2_more_things">Muutama asia lisää</string> + <string name="v5_2_fix_encryption">Pidä kontaktisi</string> + <string name="receipts_section_contacts">Kontaktit</string> + <string name="receipts_contacts_disable_for_all">Poista käytöstä kaikilta</string> + <string name="receipts_contacts_enable_for_all">Salli kaikille</string> + <string name="receipts_contacts_enable_keep_overrides">Salli (pidä ohitukset)</string> + <string name="receipts_contacts_disable_keep_overrides">Poista käytöstä (pidä ohitukset)</string> + <string name="receipts_contacts_title_disable">Kuittaukset pois käytöstä\?</string> + <string name="receipts_contacts_title_enable">Salli kuittaukset\?</string> + <string name="receipts_contacts_override_disabled">Kuittauksien lähettäminen on pois käytöstä %d kontakteilta</string> + <string name="receipts_contacts_override_enabled">Kuittauksien lähettäminen on käytössä %d kontakteille</string> + <string name="settings_section_title_delivery_receipts">LÄHETÄ TOIMITUSKUITTAUKSET VASTAANOTTAJALLE</string> + <string name="rcv_conn_event_verification_code_reset">turvakoodi on muuttunut</string> + <string name="conn_event_ratchet_sync_started">hyväksyy salausta…</string> + <string name="snd_conn_event_ratchet_sync_allowed">salauksen uudelleenneuvottelu sallittu %s:lle</string> + <string name="conn_event_ratchet_sync_required">tarvitaan salauksen uudelleenneuvottelua</string> + <string name="snd_conn_event_ratchet_sync_started">hyväksyy salausta %s:lle…</string> + <string name="conn_event_ratchet_sync_agreed">salaus sovittu</string> + <string name="snd_conn_event_ratchet_sync_agreed">salaus sovittu %s:lle</string> + <string name="snd_conn_event_ratchet_sync_ok">salaus ok %s:lle</string> + <string name="snd_conn_event_ratchet_sync_required">tarvitaan salauksen uudelleenneuvottelua %s:lle</string> + <string name="sender_at_ts">"%s klo %s"</string> + <string name="send_receipts">Lähetä kuittaukset</string> + <string name="fix_connection">Korjaa yhteys</string> + <string name="fix_connection_question">Korjaa yhteys\?</string> + <string name="allow_to_send_files">Salli tiedostojen ja median lähettäminen.</string> + <string name="prohibit_sending_files">Estä tiedostojen ja median lähettäminen.</string> + <string name="v5_2_disappear_one_message">Hävitä yksi viesti</string> + <string name="v5_2_fix_encryption_descr">Korjaa salaus varmuuskopioiden palauttamisen jälkeen.</string> + <string name="v5_2_disappear_one_message_descr">Jopa kun ei käytössä keskustelussa.</string> + <string name="receipts_groups_disable_keep_overrides">Poista käytöstä (pidä ryhmäohitukset)</string> + <string name="receipts_groups_enable_for_all">Salli kaikille ryhmille</string> + <string name="receipts_groups_title_enable">Salli kuittaukset ryhmille\?</string> + <string name="privacy_message_draft">Viestiluonnos</string> + <string name="receipts_groups_override_disabled">Kuittauksien lähettäminen on pois käytöstä %d ryhmiltä</string> + <string name="privacy_show_last_messages">Näytä viimeiset viestit</string> + <string name="send_receipts_disabled">ei käytössä</string> + <string name="rcv_group_event_2_members_connected">%s ja %s yhdistetty</string> + <string name="rcv_group_event_n_members_connected">%s, %s ja %d muut jäsenet yhdistetty</string> + <string name="rcv_group_event_3_members_connected">%s, %s ja %s yhdistetty</string> + <string name="dont_enable_receipts">Älä salli</string> + <string name="error_enabling_delivery_receipts">Virhe toimituskuittauksien sallimisessa!</string> + <string name="connect_via_member_address_alert_title">Yhdistä suoraan\?</string> + <string name="connect_via_member_address_alert_desc">Yhteyspyyntö lähetetään tälle ryhmän jäsenelle.</string> + <string name="delivery_receipts_title">Toimituskuittaukset!</string> + <string name="enable_receipts_all">Salli</string> + <string name="v5_2_favourites_filter_descr">Suodata lukemattomia- ja suosikkikeskusteluja.</string> + <string name="v5_2_favourites_filter">Löydä keskustelut nopeammin</string> + <string name="sending_delivery_receipts_will_be_enabled">Toimituskuittauksien lähettäminen otetaan käyttöön kaikille kontakteille.</string> + <string name="v5_2_message_delivery_receipts_descr">Toinen kuittaus, joka uupui! ✅</string> + <string name="error_synchronizing_connection">Virhe yhteyden synkronoinnissa</string> + <string name="no_history">Ei historiaa</string> </resources> \ 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 562d51acf..1611b413f 100644 --- a/apps/multiplatform/common/src/commonMain/resources/MR/fr/strings.xml +++ b/apps/multiplatform/common/src/commonMain/resources/MR/fr/strings.xml @@ -1316,7 +1316,7 @@ <string name="receipts_contacts_override_enabled">L\'envoi d\'accusés de réception est activé pour les contacts de %d</string> <string name="conn_event_ratchet_sync_started">accord sur le chiffrement…</string> <string name="conn_event_ratchet_sync_agreed">chiffrement accepté</string> - <string name="conn_event_ratchet_sync_ok">chiffrement ok</string> + <string name="conn_event_ratchet_sync_ok">chiffrement OK</string> <string name="conn_event_ratchet_sync_allowed">renégociation de chiffrement autorisée</string> <string name="conn_event_ratchet_sync_required">renégociation de chiffrement requise</string> <string name="snd_conn_event_ratchet_sync_ok">chiffrement ok pour %s</string> diff --git a/apps/multiplatform/common/src/commonMain/resources/MR/iw/strings.xml b/apps/multiplatform/common/src/commonMain/resources/MR/iw/strings.xml index 2367ae81a..8467199bf 100644 --- a/apps/multiplatform/common/src/commonMain/resources/MR/iw/strings.xml +++ b/apps/multiplatform/common/src/commonMain/resources/MR/iw/strings.xml @@ -689,7 +689,7 @@ <string name="old_database_archive">ארכיון מסד נתונים ישן</string> <string name="chat_item_ttl_none">לעולם לא</string> <string name="no_received_app_files">לא התקבלו או נשלחו קבצים</string> - <string name="new_member_role">תפקיד חבר קבוצה</string> + <string name="new_member_role">תפקיד חבר קבוצה חדש</string> <string name="no_contacts_selected">לא נבחרו אנשי קשר</string> <string name="member_info_section_title_member">חבר קבוצה</string> <string name="member_will_be_removed_from_group_cannot_be_undone">חבר הקבוצה יוסר מהקבוצה – לא ניתן לבטל זאת!</string> @@ -1257,7 +1257,7 @@ <string name="your_preferences">ההעדפות שלך</string> <string name="you_will_still_receive_calls_and_ntfs">עדיין תקבלו שיחות והתראות מפרופילים מושתקים כאשר הם פעילים.</string> <string name="abort_switch_receiving_address_confirm">בטל</string> - <string name="abort_switch_receiving_address_question">בטל שינוי כתובת\?</string> + <string name="abort_switch_receiving_address_question">האם לבטל שינוי כתובת\?</string> <string name="abort_switch_receiving_address_desc">שינוי הכתובת יבוטל. ייעשה שימוש בכתובת הקבלה הישנה.</string> <string name="shutdown_alert_desc">ההתראות יפסיקו לפעול עד שתפעיל את האפליקציה מחדש</string> <string name="abort_switch_receiving_address">בטל שינוי כתובת</string> diff --git a/apps/multiplatform/common/src/commonMain/resources/MR/ja/strings.xml b/apps/multiplatform/common/src/commonMain/resources/MR/ja/strings.xml index ca0905652..b82f10b25 100644 --- a/apps/multiplatform/common/src/commonMain/resources/MR/ja/strings.xml +++ b/apps/multiplatform/common/src/commonMain/resources/MR/ja/strings.xml @@ -167,7 +167,6 @@ <string name="icon_descr_call_ended">通話が終了しました。</string> <string name="if_you_cannot_meet_in_person_scan_QR_in_video_call_or_ask_for_invitation_link"><![CDATA[直接会えない時は、 <b>ビデオ通話中にQRコードを見せてもらうか</b>、招待リンクを送ってもらえば相手に繋がります。]]></string> <string name="member_will_be_removed_from_group_cannot_be_undone">メンバーをグループから除名する (※元に戻せません※)!</string> - <string name="message_delivery_error_title">メッセージ送信エラー</string> <string name="call_on_lock_screen">通話をロック画面に表示</string> <string name="icon_descr_cancel_link_preview">リンクのプレビューを中止</string> <string name="icon_descr_cancel_live_message">ライブメッセージを中止</string> @@ -485,7 +484,7 @@ <string name="smp_server_test_create_queue">サーバの待ち行列を作成する</string> <string name="smp_server_test_delete_queue">待ち行列を削除</string> <string name="smp_server_test_disconnect">切断</string> - <string name="turn_off_battery_optimization"><![CDATA[利用するには次の画面にてSimpleXに対する <b>電気省電力の設定をオフ</b> for SimpleX してください。そうしないと通知が無効になります。]]></string> + <string name="turn_off_battery_optimization"><![CDATA[利用するには次の画面にてSimpleXに対する <b>SimpleX のバックグラウンドでの実行を許可</b> してください。そうしないと通知が無効になります。]]></string> <string name="database_initialization_error_title">データベースを起動できません。</string> <string name="settings_notification_preview_title">通知のプレビュー</string> <string name="simplex_service_notification_text">メッセージ受信中…</string> @@ -588,7 +587,7 @@ <string name="error_removing_member">メンバー除名にエラー発生</string> <string name="conn_level_desc_indirect">関節 (%1$s)</string> <string name="incognito">シークレットモード</string> - <string name="incognito_info_protects">シークレットモードとは、メインのプロフィールとプロフィール画像を守るために、新しい連絡先を追加する時に、その連絡先に対してランダムなプロフィールが作成されるという対策です。</string> + <string name="incognito_info_protects">シークレット モードでは、連絡先ごとに新しいランダムなプロファイルを使用してプライバシーを保護します。</string> <string name="chat_preferences_no">いいえ</string> <string name="chat_preferences_on">オン</string> <string name="direct_messages">ダイレクトメッセージ</string> @@ -904,7 +903,7 @@ <string name="simplex_link_mode">SimpleXリンク</string> <string name="settings_section_title_support">SIMPLEX CHATを支援</string> <string name="smp_servers_test_servers">テストサーバ</string> - <string name="switch_receiving_address_desc">開発中の機能です!相手のクライアントが4.2でなければ機能しません。アドレス変更が完了すると、会話にメッセージが出ます。連絡相手 (またはグループのメンバー) からメッセージを受信できないかをご確認ください。</string> + <string name="switch_receiving_address_desc">受信アドレスは別のサーバーに変更されます。アドレス変更は送信者がオンラインになった後に完了します。</string> <string name="to_preserve_privacy_simplex_has_background_service_instead_of_push_notifications_it_uses_a_few_pc_battery"><![CDATA[あなたのプライバシーを守るために、このアプリはプッシュ通知の変わりに <b>SimpleX バックグラウンド・サービス</b> を使ってます。一日の電池使用量は約3%です。]]></string> <string name="to_protect_privacy_simplex_has_ids_for_queues">あなたのプライバシーを守るために、他のアプリと違って、ユーザーIDの変わりに SimpleX メッセージ束毎にIDを配布し、各連絡先が別々と扱います。</string> <string name="group_main_profile_sent">あなたのチャットプロフィールが他のグループメンバーに送られます。</string> @@ -1258,9 +1257,9 @@ <string name="choose_file_title">ファイルを選択</string> <string name="unfavorite_chat">お気に入りを解除</string> <string name="favorite_chat">お気に入り</string> - <string name="receipts_contacts_override_enabled">連絡先 %d に対して既読の通知が有効です</string> + <string name="receipts_contacts_override_enabled">Sending receipts is enabled for %d contacts</string> <string name="receipts_contacts_enable_for_all">すべて有効</string> - <string name="receipts_contacts_override_disabled">連絡先 %d に対して既読の通知が無効です</string> + <string name="receipts_contacts_override_disabled">Sending receipts is disabled for %d contacts</string> <string name="receipts_contacts_disable_for_all">すべて無効</string> <string name="settings_shutdown">終了</string> <string name="conn_event_ratchet_sync_started">暗号化に同意しています…</string> @@ -1289,7 +1288,7 @@ <string name="error_aborting_address_change">アドレス変更中止エラー</string> <string name="no_filtered_chats">フィルタされたチャットはありません</string> <string name="files_and_media_prohibited">ファイルとメディアは禁止されています!</string> - <string name="abort_switch_receiving_address_desc">住所変更は中止されます。古い受信アドレスが使用されます。</string> + <string name="abort_switch_receiving_address_desc">アドレス変更は中止されます。古い受信アドレスが使用されます。</string> <string name="abort_switch_receiving_address_question">アドレス変更を中止しますか?</string> <string name="settings_section_title_app">アプリ</string> <string name="non_fatal_errors_occured_during_import">インポート中に致命的でないエラーが発生しました - 詳細はチャットコンソールを参照してください。</string> @@ -1304,18 +1303,17 @@ <string name="v5_2_fix_encryption_descr">バックアップの復元後に暗号化を修正します。</string> <string name="snd_conn_event_ratchet_sync_started">暗号化に同意しています: %s</string> <string name="conn_event_ratchet_sync_agreed">暗号化に同意しました</string> - <string name="receipts_contacts_title_disable">既読通知を無効にしますか?</string> + <string name="receipts_contacts_title_disable">Disable receipts\?</string> <string name="fix_connection_not_supported_by_contact">連絡先による修正はサポートされていません</string> <string name="fix_connection_not_supported_by_group_member">グループメンバーによる修正はサポートされていません</string> <string name="receipts_section_contacts">連絡先</string> - <string name="receipts_section_description_1">これらは連絡先の設定が優先します</string> + <string name="receipts_section_description_1">これらは連絡先とグループの設定が優先されます。</string> <string name="receipts_section_description">これらの設定は現在のプロファイル用です</string> - <string name="receipts_contacts_title_enable">既読通知を有効にしますか?</string> + <string name="receipts_contacts_title_enable">Enable receipts\?</string> <string name="sender_at_ts">%s : %s</string> <string name="fix_connection">接続を修正</string> <string name="files_and_media">ファイルとメディア</string> <string name="you_can_enable_delivery_receipts_later_alert">あとでアプリのプライバシーとセキュリティの設定から有効にすることができます。</string> - <string name="error_enabling_delivery_receipts">既読通知の有効化でエラーが発生しました!</string> <string name="item_info_no_text">テキストなし</string> <string name="error_synchronizing_connection">接続の同期エラー</string> <string name="no_history">履歴はありません</string> @@ -1326,4 +1324,56 @@ <string name="only_owners_can_enable_files_and_media">ファイルやメディアを有効にできるのは、グループオーナーだけです。</string> <string name="sync_connection_force_confirm">再ネゴシエート</string> <string name="settings_restart_app">再起動</string> + <string name="connect_via_link_incognito">シークレットモードで接続</string> + <string name="turn_off_battery_optimization_button">許可</string> + <string name="connect_via_member_address_alert_title">直接接続しますか\?</string> + <string name="connect__a_new_random_profile_will_be_shared">新しいランダムなプロファイルが共有されます。</string> + <string name="delivery">送信</string> + <string name="in_developing_title">近日公開!</string> + <string name="v5_2_disappear_one_message">メッセージを1つ消す</string> + <string name="connect_use_current_profile">現在のプロファイルを使用する</string> + <string name="connect_use_new_incognito_profile">新しいシークレットプロファイルを使用する</string> + <string name="turn_off_system_restriction_button">アプリの設定を開く</string> + <string name="disable_notifications_button">通知を無効にする</string> + <string name="system_restricted_background_warn"><![CDATA[通知を有効にするには、アプリの設定で<b>アプリのバッテリー使用量</b> / <b>制限なし</b> を選択してください。]]></string> + <string name="system_restricted_background_in_call_desc">アプリはバックグラウンドで1分経過すると終了します。</string> + <string name="system_restricted_background_in_call_warn"><![CDATA[バックグラウンドで通話を行うには、アプリの設定で<b>アプリのバッテリー使用量</b> / <b>制限なし</b> を選択してください。]]></string> + <string name="connect__your_profile_will_be_shared">あなたのプロフィール %1$s が共有されます。</string> + <string name="receipts_groups_title_enable">Enable receipts for groups\?</string> + <string name="receipts_groups_title_disable">Disable receipts for groups\?</string> + <string name="receipts_groups_override_enabled">Sending receipts is enabled for %d groups</string> + <string name="receipts_groups_override_disabled">Sending receipts is disabled for %d groups</string> + <string name="send_receipts_disabled">無効</string> + <string name="system_restricted_background_in_call_title">バックグラウンド通話なし</string> + <string name="paste_the_link_you_received_to_connect_with_your_contact">受信したリンクを貼り付け、連絡先に接続する。</string> + <string name="rcv_group_event_2_members_connected">%s と %s は接続中</string> + <string name="system_restricted_background_desc">SimpleXはバックグラウンドでは動作できません。アプリが起動している時のみ通知を受け取ることができます。</string> + <string name="no_info_on_delivery">送信情報なし</string> + <string name="no_selected_chat">チャットが選択されていません</string> + <string name="receipts_section_groups">小グループ(最大20名)</string> + <string name="receipts_groups_enable_for_all">すべてのグループで有効にする</string> + <string name="recipient_colon_delivery_status">%s: %s</string> + <string name="connect_via_member_address_alert_desc">接続リクエストはこのグループ メンバーに送信されます。</string> + <string name="receipts_groups_disable_for_all">すべてのグループで無効にする</string> + <string name="privacy_message_draft">メッセージの下書き</string> + <string name="privacy_show_last_messages">最新のメッセージを表示</string> + <string name="send_receipts">Send receipts</string> + <string name="rcv_group_event_n_members_connected">%s, %s および %d 人の他のメンバーが接続しています。</string> + <string name="rcv_group_event_3_members_connected">%s, %s と %s は接続中</string> + <string name="in_developing_desc">この機能はまだサポートされていません。次のリリースをお試しください。</string> + <string name="receipts_contacts_enable_keep_overrides">有効にする(設定の優先を維持)</string> + <string name="receipts_groups_disable_keep_overrides">無効にする(グループの設定の優先を維持)</string> + <string name="receipts_groups_enable_keep_overrides">有効にする(グループの設定の優先を維持)</string> + <string name="receipts_contacts_disable_keep_overrides">無効にする(設定の優先を維持)</string> + <string name="v5_2_disappear_one_message_descr">会話中に無効になっている場合でも。</string> + <string name="v5_2_message_delivery_receipts">Message delivery receipts!</string> + <string name="delivery_receipts_title">Delivery receipts!</string> + <string name="sending_delivery_receipts_will_be_enabled_all_profiles">Sending delivery receipts will be enabled for all contacts in all visible chat profiles.</string> + <string name="message_delivery_error_title">Message delivery error</string> + <string name="send_receipts_disabled_alert_title">Receipts are disabled</string> + <string name="settings_section_title_delivery_receipts">SEND DELIVERY RECEIPTS TO</string> + <string name="sending_delivery_receipts_will_be_enabled">Sending delivery receipts will be enabled for all contacts.</string> + <string name="send_receipts_disabled_alert_msg">This group has over %1$d members, delivery receipts are not sent.</string> + <string name="error_enabling_delivery_receipts">Error enabling delivery receipts!</string> + <string name="delivery_receipts_are_disabled">Delivery receipts are disabled!</string> </resources> \ 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 791a2ff64..e69ad19fb 100644 --- a/apps/multiplatform/common/src/commonMain/resources/MR/nl/strings.xml +++ b/apps/multiplatform/common/src/commonMain/resources/MR/nl/strings.xml @@ -1086,13 +1086,13 @@ <string name="decryption_error">Decodering fout</string> <string name="alert_text_msg_bad_hash">De hash van het vorige bericht is anders.</string> <string name="alert_text_decryption_error_too_many_skipped">%1$d berichten overgeslagen.</string> - <string name="alert_text_fragment_encryption_out_of_sync_old_database">Het kan gebeuren wanneer u of uw verbinding de oude databaseback-up gebruikte.</string> + <string name="alert_text_fragment_encryption_out_of_sync_old_database">Het kan gebeuren wanneer u of de ander een oude databaseback-up gebruikt.</string> <string name="alert_text_fragment_please_report_to_developers">Meld het alsjeblieft aan de ontwikkelaars.</string> <string name="alert_title_msg_bad_hash">Onjuiste bericht hash</string> <string name="alert_title_msg_bad_id">Onjuiste bericht-ID</string> <string name="alert_text_msg_bad_id">De ID van het volgende bericht is onjuist (minder of gelijk aan het vorige). \nHet kan gebeuren vanwege een bug of wanneer de verbinding is aangetast.</string> - <string name="alert_text_decryption_error_n_messages_failed_to_decrypt">%1$d-berichten konden niet worden ontsleuteld.</string> + <string name="alert_text_decryption_error_n_messages_failed_to_decrypt">%1$d berichten konden niet worden ontsleuteld.</string> <string name="no_spaces">Geen spaties!</string> <string name="stop_rcv_file__message">Het ontvangen van het bestand wordt gestopt.</string> <string name="revoke_file__confirm">Intrekken</string> @@ -1369,7 +1369,7 @@ <string name="connect_via_member_address_alert_desc">Verzoek voor het verbinden wordt naar dit groepslid verzonden.</string> <string name="paste_the_link_you_received_to_connect_with_your_contact">Plak de link die je hebt ontvangen om verbinding te maken met je contact…</string> <string name="system_restricted_background_warn"><![CDATA[Als u meldingen wilt inschakelen, kiest u <b> App-batterijgebruik</b> /<b> Onbeperkt</b> in de app-instellingen.]]></string> - <string name="connect_use_new_incognito_profile">Gebruik een nieuw incognito -profiel</string> + <string name="connect_use_new_incognito_profile">Gebruik een nieuw incognito profiel</string> <string name="privacy_message_draft">Concept bericht</string> <string name="rcv_group_event_n_members_connected">%s, %s en %d andere leden verbonden</string> <string name="privacy_show_last_messages">Laat laatste berichten zien</string> diff --git a/apps/multiplatform/common/src/commonMain/resources/MR/pt-rBR/strings.xml b/apps/multiplatform/common/src/commonMain/resources/MR/pt-rBR/strings.xml index 2b735f594..07836dc98 100644 --- a/apps/multiplatform/common/src/commonMain/resources/MR/pt-rBR/strings.xml +++ b/apps/multiplatform/common/src/commonMain/resources/MR/pt-rBR/strings.xml @@ -414,7 +414,7 @@ <string name="initial_member_role">Função inicial</string> <string name="snd_group_event_group_profile_updated">perfil do grupo atualizado</string> <string name="group_member_status_group_deleted">Grupo excluído</string> - <string name="incognito_info_protects">O modo de navegação anônima protege a privacidade do nome e da imagem do seu perfil principal — para cada novo contato, um novo perfil aleatório é criado.</string> + <string name="incognito_info_protects">O modo Incognito protege sua privacidade usando um novo perfil aleatório para cada contato.</string> <string name="group_members_can_delete">Os membros do grupo podem excluir mensagens enviadas de forma irreversível.</string> <string name="ttl_w">%dsemana</string> <string name="v4_3_improved_server_configuration">Configuração de servidor aprimorada</string> @@ -631,7 +631,7 @@ <string name="images_limit_desc">Apenas 10 imagens podem ser enviadas ao mesmo tempo</string> <string name="notifications">Notificações</string> <string name="text_field_set_contact_placeholder">Definir nome do contato…</string> - <string name="switch_receiving_address_desc">Esse recurso é experimental! Ele só funcionará se o outro cliente tiver a versão 4.2 instalada. Você deve ver a mensagem na conversa assim que a alteração de endereço for concluída – verifique se você ainda pode receber mensagens desse contato (ou membro do grupo).</string> + <string name="switch_receiving_address_desc">O endereço de recebimento será alterado para um servidor diferente. A mudança de endereço terminará após o remetente entrar on-line.</string> <string name="send_verb">Enviar</string> <string name="reset_verb">Redefinir</string> <string name="live_message">Mensagem ao vivo!</string> @@ -1327,4 +1327,28 @@ <string name="enable_receipts_all">Ativar</string> <string name="sending_delivery_receipts_will_be_enabled">Enviar confirmações de entrega serão ativadas para todos os contatos.</string> <string name="error_enabling_delivery_receipts">Ocorreu um erro ao ativar as confirmações de entrega!</string> + <string name="choose_file_title">Escolher arquivo</string> + <string name="connect_via_link_incognito">Conectar incógnito</string> + <string name="turn_off_battery_optimization_button">Permitir</string> + <string name="disable_notifications_button">Desativar notificações</string> + <string name="system_restricted_background_in_call_title">Sem chamadas de fundo</string> + <string name="turn_off_system_restriction_button">Abrir configurações do aplicativo</string> + <string name="connect__a_new_random_profile_will_be_shared">Um novo perfil aleatório será compartilhado.</string> + <string name="paste_the_link_you_received_to_connect_with_your_contact">Cole o link que você recebeu para se conectar com seu contato..</string> + <string name="receipts_groups_title_disable">Desativar recibos para grupos\?</string> + <string name="receipts_groups_title_enable">Ativar recibos para grupos\?</string> + <string name="receipts_groups_override_disabled">Recibos de entrega estão desativados para %d grupos</string> + <string name="receipts_groups_enable_for_all">Ativar para todos os grupos</string> + <string name="receipts_groups_enable_keep_overrides">Ativar (manter sobreposições do grupo)</string> + <string name="receipts_groups_disable_keep_overrides">Desativar (manter sobreposições do grupo)</string> + <string name="receipts_groups_disable_for_all">Desativar para todos os grupos</string> + <string name="in_developing_title">Em breve!</string> + <string name="delivery">Entrega</string> + <string name="no_info_on_delivery">Nenhuma informação de entrega</string> + <string name="sending_delivery_receipts_will_be_enabled_all_profiles">Enviar recibos de entrega será ativado para todos os contatos em todos os perfis de chat visíveis.</string> + <string name="rcv_group_event_2_members_connected">%s e %s conectados</string> + <string name="connect_via_member_address_alert_title">Conectar diretamente\?</string> + <string name="no_selected_chat">Sem chat selecionado</string> + <string name="privacy_message_draft">Rascunho de mensagem</string> + <string name="send_receipts_disabled">desativado</string> </resources> \ No newline at end of file diff --git a/apps/multiplatform/common/src/desktopMain/kotlin/chat/simplex/common/platform/Files.desktop.kt b/apps/multiplatform/common/src/desktopMain/kotlin/chat/simplex/common/platform/Files.desktop.kt index b26023951..9042a6283 100644 --- a/apps/multiplatform/common/src/desktopMain/kotlin/chat/simplex/common/platform/Files.desktop.kt +++ b/apps/multiplatform/common/src/desktopMain/kotlin/chat/simplex/common/platform/Files.desktop.kt @@ -2,8 +2,10 @@ package chat.simplex.common.platform import androidx.compose.runtime.* import chat.simplex.common.* +import chat.simplex.common.views.helpers.AlertManager import chat.simplex.common.views.helpers.generalGetString import chat.simplex.res.MR +import java.awt.Desktop import java.io.* import java.net.URI @@ -19,6 +21,20 @@ actual val agentDatabaseFileName: String = "simplex_v1_agent.db" actual val databaseExportDir: File = tmpDir +actual fun desktopOpenDatabaseDir() { + if (Desktop.isDesktopSupported()) { + try { + Desktop.getDesktop().open(dataDir); + } catch (e: IOException) { + Log.e(TAG, e.stackTraceToString()) + AlertManager.shared.showAlertMsg( + title = generalGetString(MR.strings.unknown_error), + text = e.stackTraceToString() + ) + } + } +} + @Composable actual fun rememberFileChooserLauncher(getContent: Boolean, rememberedValue: Any?, onResult: (URI?) -> Unit): FileChooserLauncher = remember(rememberedValue) { FileChooserLauncher(getContent, onResult) } diff --git a/apps/multiplatform/common/src/desktopMain/kotlin/chat/simplex/common/views/database/DatabaseEncryptionView.desktop.kt b/apps/multiplatform/common/src/desktopMain/kotlin/chat/simplex/common/views/database/DatabaseEncryptionView.desktop.kt new file mode 100644 index 000000000..af2b269b5 --- /dev/null +++ b/apps/multiplatform/common/src/desktopMain/kotlin/chat/simplex/common/views/database/DatabaseEncryptionView.desktop.kt @@ -0,0 +1,106 @@ +package chat.simplex.common.views.database + +import SectionItemView +import SectionTextFooter +import androidx.compose.foundation.layout.* +import androidx.compose.material.* +import androidx.compose.runtime.Composable +import androidx.compose.runtime.MutableState +import androidx.compose.ui.Alignment +import androidx.compose.ui.Modifier +import androidx.compose.ui.graphics.Color +import androidx.compose.ui.unit.Dp +import androidx.compose.ui.unit.dp +import chat.simplex.common.ui.theme.WarningOrange +import chat.simplex.common.views.helpers.* +import chat.simplex.res.MR +import dev.icerock.moko.resources.compose.painterResource +import dev.icerock.moko.resources.compose.stringResource + +@Composable +actual fun SavePassphraseSetting( + useKeychain: Boolean, + initialRandomDBPassphrase: Boolean, + storedKey: Boolean, + progressIndicator: Boolean, + minHeight: Dp, + onCheckedChange: (Boolean) -> Unit, +) { + SectionItemView(minHeight = minHeight) { + Row(verticalAlignment = Alignment.CenterVertically) { + Icon( + if (storedKey) painterResource(MR.images.ic_vpn_key_filled) else painterResource(MR.images.ic_vpn_key_off_filled), + stringResource(MR.strings.save_passphrase_in_settings), + tint = if (storedKey) WarningOrange else MaterialTheme.colors.secondary + ) + Spacer(Modifier.padding(horizontal = 4.dp)) + Text( + stringResource(MR.strings.save_passphrase_in_settings), + Modifier.padding(end = 24.dp), + color = Color.Unspecified + ) + Spacer(Modifier.fillMaxWidth().weight(1f)) + DefaultSwitch( + checked = useKeychain, + onCheckedChange = onCheckedChange, + enabled = !initialRandomDBPassphrase && !progressIndicator + ) + } + } +} + +@Composable +actual fun DatabaseEncryptionFooter( + useKeychain: MutableState<Boolean>, + chatDbEncrypted: Boolean?, + storedKey: MutableState<Boolean>, + initialRandomDBPassphrase: MutableState<Boolean>, +) { + if (chatDbEncrypted == false) { + SectionTextFooter(generalGetString(MR.strings.database_is_not_encrypted)) + } else if (useKeychain.value) { + if (storedKey.value) { + SectionTextFooter(generalGetString(MR.strings.settings_is_storing_in_clear_text)) + if (initialRandomDBPassphrase.value) { + SectionTextFooter(generalGetString(MR.strings.encrypted_with_random_passphrase)) + } else { + SectionTextFooter(annotatedStringResource(MR.strings.impossible_to_recover_passphrase)) + } + } else { + SectionTextFooter(generalGetString(MR.strings.passphrase_will_be_saved_in_settings)) + } + } else { + SectionTextFooter(generalGetString(MR.strings.you_have_to_enter_passphrase_every_time)) + SectionTextFooter(annotatedStringResource(MR.strings.impossible_to_recover_passphrase)) + } +} + +actual fun encryptDatabaseSavedAlert(onConfirm: () -> Unit) { + AlertManager.shared.showAlertDialog( + title = generalGetString(MR.strings.encrypt_database_question), + text = generalGetString(MR.strings.database_will_be_encrypted_and_passphrase_stored_in_settings) + "\n" + storeSecurelySaved(), + confirmText = generalGetString(MR.strings.encrypt_database), + onConfirm = onConfirm, + destructive = true, + ) +} + +actual fun changeDatabaseKeySavedAlert(onConfirm: () -> Unit) { + AlertManager.shared.showAlertDialog( + title = generalGetString(MR.strings.change_database_passphrase_question), + text = generalGetString(MR.strings.database_encryption_will_be_updated_in_settings) + "\n" + storeSecurelySaved(), + confirmText = generalGetString(MR.strings.update_database), + onConfirm = onConfirm, + destructive = false, + ) +} + +actual fun removePassphraseAlert(onConfirm: () -> Unit) { + AlertManager.shared.showAlertDialog( + title = generalGetString(MR.strings.remove_passphrase_from_settings), + text = storeSecurelyDanger(), + confirmText = generalGetString(MR.strings.remove_passphrase), + onConfirm = onConfirm, + destructive = true, + ) +} diff --git a/cabal.project b/cabal.project index 1be6c7365..b40b009b3 100644 --- a/cabal.project +++ b/cabal.project @@ -9,7 +9,7 @@ constraints: zip +disable-bzip2 +disable-zstd source-repository-package type: git location: https://github.com/simplex-chat/simplexmq.git - tag: 4c0b8a31d20870a23e120e243359901d8240f922 + tag: 980e5c4d1ec15f44290542fd2a5d1c08456f00d1 source-repository-package type: git diff --git a/docs/rfcs/2023-04-28-files-encryption.md b/docs/rfcs/2023-04-28-files-encryption.md new file mode 100644 index 000000000..30c6a4d2d --- /dev/null +++ b/docs/rfcs/2023-04-28-files-encryption.md @@ -0,0 +1,64 @@ +# Encrpting local app files + +## Problem + +Currently, the files are stored in the file storage unencrypted, unlike the database. + +There are multiple operations in the app that access files: + +1. Sending files via SMP - chat core reads the files chunk by chunk and sends them. The file can be encrypted once sent and the "encrypted" flag added. + +2. Sending files via XFTP - simplexmq encrypts the file first and then sends it. Currently, we are deleting the file from chat, once its uploaded, there is no reason to keep unencrypted file (from XFTP point of view) once its encrypted. + +3. Viewing images in the mobile apps. + +4. Playing voice files in the mobile apps. + +5. Playing videos and showing video previews in mobile apps. + +6. Saving files from the app storage to the device. + +## Possible solutions + +### System encryption + +A possible approach is to use platform-specific encryption mechanism. The problem with that approach is inconsistency between platforms, and that the files in chat archive will probably be unencrypted in this case. + +### App encryption + +Files will be encrypted once received, using storage key, and the core would expose C apis to mobile apps: + +1. Read the file with decryption - this can be used for image previews, for example, as a replacement for OS file reading. + +2. Copy the file with decryption to some permanent destination - this can be used for saving files to the device. + +3. Copy the file into a temporary location with decryption - this can be used for playing voice/video files. The app would remove the files once no longer used, and this temporary location can be cleaned on each app start, to clean up the files that the app failed to remove. Alternative to that would be to have both encrypted and decrypted copies available for the file, with paths stored in the database, and clean up process removed decrypted copies once no longer used - there should be some flags to indicate when decrypted copy can be deleted. + +For specific use cases: + +1. Viewing images in the mobile apps. + - iOS: we use `UIImage(contentsOfFile path: String)`. We could use `init?(data: Data)` instead, and decrypt the file in memory before passing it to the image view. Images are small enough for this approach to be ok, and in any case the image is read to memory as a whole. + - Android: we use `BitmapFactory.decodeFileDescriptor` (?). We could use ... + +2. Playing voice files in the mobile apps. + - iOS: we use `AVAudioPlayer.init(contentsOf: URL)` to play the file. We could either decrypt the file before playing it, or, given that voice files are small (even if we increase allowed duration, they are still likely to be under 1mb), we could use `init(data: Data)` to avoid creating decrypted file. + - Android: we use `MediaPlayer.setDataSource(filePath)`. We could use ... + +3. Showing video previews. + - iOS: ... + - Android: ... + + Possibly, we will need to store preview as a separate file, to avoid decrypting the whole video just to show preview. + +4. Playing video files. + - iOS: we use `AVPlayer(url: URL)`, the file will have to be decrypted for playback. + - Android: ... + +5. Saving files from the app storage to the device. + The file will have to be decrypted, passed to the system, and then decrypted copy deleted once no longer needed. + +### Which key to use for encryption + +1. Derive file encryption key from database storage key. The downside for this approach is managing key changes - they will be slow. Also, if file encryption is made optional, and in any case, for the existing users all files are not encrypted yet, we will need somehow to track which files are encrypted. + +2. Random per-file encryption key stored in the database. Given that the database is already encrypted, it can be a better approach, and it makes it easier to manage file encryption/decryption. File keys will not be sent to the client application, but they will be accessible via the database queries of course. diff --git a/docs/rfcs/2023-08-28-groups-improvements.md b/docs/rfcs/2023-08-28-groups-improvements.md new file mode 100644 index 000000000..7a653fcb2 --- /dev/null +++ b/docs/rfcs/2023-08-28-groups-improvements.md @@ -0,0 +1,112 @@ +# Groups improvements + +See also: +- [Group contacts management](./2022-10-19-group-contacts-management.md). +- [Create groups without establishing direct connections](./2023-08-10-groups-wt-contacts.md). + +## Problem + +Establishing connections in groups is unstable and uses a lot of traffic. There are several areas for improvement that that could help optimize it: + +- Joining group member prematurely creates direct and group connections for each member. + + Some members may never come online, and that traffic would be completely wasted. + + Instead of creating direct connections, we could allow to send direct messages inside group, and optionally have a separate protocol for automating establishing direct connection with member via them. + +- Host sends N introduction messages (XGrpMemIntro) to joining member. Instead they could be batched. + +## Possible solutions + +### Improved group handshake protocol + +Below are proposed changes to group handshake protocol to reduce traffic and improve stability. + +Each joining member creates a new temporary per group address for introduced members to connect via. Joining member sends it to host when accepting group invitation. + +``` haskell +XGrpAcptAddress :: MemberId -> ConnReqContact -> ChatMsgEvent 'Json +``` + +Host sends group introductions in batches, batching smaller messages first (introductions of members without profile picture). + +For each received batch of N introductions joining member creates N transient per member identifiers (MemberCodes) and replies to host with batched XGrpMemInv messages including these identifiers. Joining member would then use them to verify contact requests from introduced members. + +How is MemberCode different from MemberId? - MemberId is known to all group members and is constant per member per group. MemberCode would be known only to host and to introduced member (of existing members), so other members wouldn't be able to impersonate one another when requesting connection with joining member. An introduced member can still pass their identifier + joining member address to another member or outside of group, but it is no different to passing currently shared invitation links. + +```haskell +newtype MemberCode = MemberCode {unMemberCode :: ByteString} + +XGrpMemInvCode :: MemberId -> MemberCode -> ChatMsgEvent 'Json + +-- instead of / in addition to batching message could be + +type MemberCodes = Map MemberId MemberCode + +XGrpMemInvCodes :: MemberCodes -> ChatMsgEvent 'Json +``` + +Host includes joining member address and code (unique for each introduced member) into XGrpMemFwd messages instead of invitation links: + +```haskell +XGrpMemFwdCode :: MemberInfo -> ConnReqContact -> MemberCode -> ChatMsgEvent 'Json +``` + +Introduced members send contact requests with a new message XGroupMember / XIntroduced (similar to XInfo or XContact, see `processUserContactRequest`): + +```haskell +XIntroduced :: MemberInfo -> MemberCode -> ChatMsgEvent 'Json +``` + +Joinee verifies profile and code and automatically accepts contact request. They both assign resulting connection to respective group member record, without creating contact. + +After (if) all introduced members have connected, joining member deletes per group address. Possibly it can also be deleted after expiration interval. + +#### Group links + +We can reduce number of steps taken to join group via group link: +- Do not create direct connection and contact with group link host, instead use the connection resulting from contact request as a group connection, and assign it to a group member record. +- Host to not send XGrpInv message, joining member to not wait for it, instead joining member would initiate with XGrpAcptAddress after establishing connection via group link. + +In addition to their profile, host includes MemberId of joining member into confirmation when accepting group link join request, using new message: + +```haskell +XGroupLinkInfo :: Profile -> MemberId -> ChatMsgEvent 'Json +``` + +Joining member initially doesn't know group profile, they create a placeholder group with a new dummy profile (alternatively, we could include at least group display name into group link). After connection is established, host sends XGrpInfo containing group profile to joining member. This can happen in parallel with group handshake started by XGrpAcptAddress. + +Group profile could also be included into XGroupLinkInfo if not for the limitation on size if both host's profile and group profile contain pictures. + +![Adding member to the group](./diagrams/2023-08-28-groups-improvements.svg) + +#### Clients compatibility + +We have a [proposed mechanism](https://github.com/simplex-chat/simplex-chat/pull/2886) for communicating "chat protocol version" between clients. + +Sending and processing new protocol messages would only be supported by updated clients. + +Trying to support both protocols across different members in the same group would require complex logic: + +Host would have to send introduced members versions, joining member would provide both address or invitation links depending on each members' versions, host would forward accordingly. + +Instead we could assign "chat protocol version" per group and share it with members as part of group profile, and make a two-stage release when members would first be able to update and get new processing logic, but have it disabled until next release. + +After group switching to new processing logic old clients wouldn't be able to connect in groups. + +How should existing groups be switched? +- Owner user action? +- Owner client deciding automatically? +- In case group has multiple owners - which owner(s) can / should decide? +- Prohibited until all / part of existing members don't update? How to request members to update? +- Old clients will not be able to process and save group chat version from group profile update. + +### Sending direct messages inside group + +Group messages are sent by broadcasting them to all group member connections. As a replacement for creating additional direct connections in group we can allow to send message directly to members via group member connections. The UX would be to choose whether to send to group or to a specific member via compose view. + +Possible approach is to extend ExtMsgContent with `direct :: Maybe Bool` field, which would only be considered for group messages. + +Chat items should store information of receiving member database ID (for sending member) and of message being direct (for receiving member). Perhaps it could be a single field `direct_member_id`, which would be the same as `group_member_id` for received messages. + +TODO - consider whether `connection_id` or `group_id` or both should be assigned in `messages` table. diff --git a/docs/rfcs/diagrams/2023-08-28-groups-improvements.mmd b/docs/rfcs/diagrams/2023-08-28-groups-improvements.mmd new file mode 100644 index 000000000..591c30445 --- /dev/null +++ b/docs/rfcs/diagrams/2023-08-28-groups-improvements.mmd @@ -0,0 +1,40 @@ +sequenceDiagram + participant M as N existing<br>members + participant A as Alice + participant B as Bob + + note over A, B: 1. send and accept group invitation /<br>join via group link + alt host invites contact + A ->> B: x.grp.inv<br>invite Bob to group<br>(via contact connection) + else user joins via group link + B ->> A: request to join group via link + A ->> B: auto-accept<br>x.group.link.info with host's profile<br>and joining member MemberId<br>establish group member connection + A ->> B: x.grp.info<br>group profile + end + + note right of B: when joining via group link<br>Bob doesn't wait for x.grp.info<br>and initiates group handshake<br>with x.grp.acpt.address<br>after establishing connection + + note over B: create per group address + B ->> A: x.grp.acpt.address<br>accept invitation<br>and send address to connect<br>(via member connection) + B ->> A: establish group member connection + + note over M, B: 2. introduce new member Bob to all existing members + A ->> M: x.grp.mem.new<br>"announce" Bob<br>to existing members<br>(via member connections) + + loop batched + A ->> B: x.grp.mem.intro * N<br>"introduce" members<br>(via member connection) + note over B: create N MemberCodes + B ->> A: x.grp.mem.inv.code<br>unique MemberCodes<br>for all members<br>(via member connection) + end + + A ->> M: x.grp.mem.fwd.code<br>forward address<br>and unique MemberCodes<br>to all members<br>(via member connections) + + note over M, B: 3. establish group member connection + M ->> B: request group member connection<br>x.introduced with MemberCode + B ->> M: verify MemberCode, auto-accept + + note over M, B: no contact deduplication + + opt all introduced members connected / expiration + note over B: delete per group address + end diff --git a/docs/rfcs/diagrams/2023-08-28-groups-improvements.svg b/docs/rfcs/diagrams/2023-08-28-groups-improvements.svg new file mode 100644 index 000000000..34529d532 --- /dev/null +++ b/docs/rfcs/diagrams/2023-08-28-groups-improvements.svg @@ -0,0 +1 @@ +<svg aria-roledescription="sequence" role="graphics-document document" viewBox="-50 -10 1060 1928" style="max-width: 100%;" xmlns="http://www.w3.org/2000/svg" width="100%" id="graph-div" height="100%" xmlns:xlink="http://www.w3.org/1999/xlink"><style>@import url("https://cdnjs.cloudflare.com/ajax/libs/font-awesome/6.4.0/css/all.min.css");'</style><g><rect class="actor" ry="3" rx="3" height="65" width="150" stroke="#666" fill="#eaeaea" y="1842" x="611"></rect><text class="actor" alignment-baseline="central" dominant-baseline="central" style="text-anchor: middle; font-size: 16px; font-weight: 400;" y="1874.5" x="686"><tspan dy="0" x="686">Bob</tspan></text></g><g><rect class="actor" ry="3" rx="3" height="65" width="150" stroke="#666" fill="#eaeaea" y="1842" x="269"></rect><text class="actor" alignment-baseline="central" dominant-baseline="central" style="text-anchor: middle; font-size: 16px; font-weight: 400;" y="1874.5" x="344"><tspan dy="0" x="344">Alice</tspan></text></g><g><rect class="actor" ry="3" rx="3" height="65" width="150" stroke="#666" fill="#eaeaea" y="1842" x="0"></rect><text class="actor" alignment-baseline="central" dominant-baseline="central" style="text-anchor: middle; font-size: 16px; font-weight: 400;" y="1874.5" x="75"><tspan dy="-8" x="75">N existing</tspan></text><text class="actor" alignment-baseline="central" dominant-baseline="central" style="text-anchor: middle; font-size: 16px; font-weight: 400;" y="1874.5" x="75"><tspan dy="8" x="75">members</tspan></text></g><g><line stroke="#999" stroke-width="0.5px" class="200" y2="1842" x2="686" y1="5" x1="686" id="actor998"></line><g id="root-998"><rect class="actor" ry="3" rx="3" height="65" width="150" stroke="#666" fill="#eaeaea" y="0" x="611"></rect><text class="actor" alignment-baseline="central" dominant-baseline="central" style="text-anchor: middle; font-size: 16px; font-weight: 400;" y="32.5" x="686"><tspan dy="0" x="686">Bob</tspan></text></g></g><g><line stroke="#999" stroke-width="0.5px" class="200" y2="1842" x2="344" y1="5" x1="344" id="actor997"></line><g id="root-997"><rect class="actor" ry="3" rx="3" height="65" width="150" stroke="#666" fill="#eaeaea" y="0" x="269"></rect><text class="actor" alignment-baseline="central" dominant-baseline="central" style="text-anchor: middle; font-size: 16px; font-weight: 400;" y="32.5" x="344"><tspan dy="0" x="344">Alice</tspan></text></g></g><g><line stroke="#999" stroke-width="0.5px" class="200" y2="1842" x2="75" y1="5" x1="75" id="actor996"></line><g id="root-996"><rect class="actor" ry="3" rx="3" height="65" width="150" stroke="#666" fill="#eaeaea" y="0" x="0"></rect><text class="actor" alignment-baseline="central" dominant-baseline="central" style="text-anchor: middle; font-size: 16px; font-weight: 400;" y="32.5" x="75"><tspan dy="-8" x="75">N existing</tspan></text><text class="actor" alignment-baseline="central" dominant-baseline="central" style="text-anchor: middle; font-size: 16px; font-weight: 400;" y="32.5" x="75"><tspan dy="8" x="75">members</tspan></text></g></g><style>#graph-div{font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:16px;fill:#333;}#graph-div .error-icon{fill:#552222;}#graph-div .error-text{fill:#552222;stroke:#552222;}#graph-div .edge-thickness-normal{stroke-width:2px;}#graph-div .edge-thickness-thick{stroke-width:3.5px;}#graph-div .edge-pattern-solid{stroke-dasharray:0;}#graph-div .edge-pattern-dashed{stroke-dasharray:3;}#graph-div .edge-pattern-dotted{stroke-dasharray:2;}#graph-div .marker{fill:#333333;stroke:#333333;}#graph-div .marker.cross{stroke:#333333;}#graph-div svg{font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:16px;}#graph-div .actor{stroke:hsl(259.6261682243, 59.7765363128%, 87.9019607843%);fill:#ECECFF;}#graph-div text.actor>tspan{fill:black;stroke:none;}#graph-div .actor-line{stroke:grey;}#graph-div .messageLine0{stroke-width:1.5;stroke-dasharray:none;stroke:#333;}#graph-div .messageLine1{stroke-width:1.5;stroke-dasharray:2,2;stroke:#333;}#graph-div #arrowhead path{fill:#333;stroke:#333;}#graph-div .sequenceNumber{fill:white;}#graph-div #sequencenumber{fill:#333;}#graph-div #crosshead path{fill:#333;stroke:#333;}#graph-div .messageText{fill:#333;stroke:none;}#graph-div .labelBox{stroke:hsl(259.6261682243, 59.7765363128%, 87.9019607843%);fill:#ECECFF;}#graph-div .labelText,#graph-div .labelText>tspan{fill:black;stroke:none;}#graph-div .loopText,#graph-div .loopText>tspan{fill:black;stroke:none;}#graph-div .loopLine{stroke-width:2px;stroke-dasharray:2,2;stroke:hsl(259.6261682243, 59.7765363128%, 87.9019607843%);fill:hsl(259.6261682243, 59.7765363128%, 87.9019607843%);}#graph-div .note{stroke:#aaaa33;fill:#fff5ad;}#graph-div .noteText,#graph-div .noteText>tspan{fill:black;stroke:none;}#graph-div .activation0{fill:#f4f4f4;stroke:#666;}#graph-div .activation1{fill:#f4f4f4;stroke:#666;}#graph-div .activation2{fill:#f4f4f4;stroke:#666;}#graph-div .actorPopupMenu{position:absolute;}#graph-div .actorPopupMenuPanel{position:absolute;fill:#ECECFF;box-shadow:0px 8px 16px 0px rgba(0,0,0,0.2);filter:drop-shadow(3px 5px 2px rgb(0 0 0 / 0.4));}#graph-div .actor-man line{stroke:hsl(259.6261682243, 59.7765363128%, 87.9019607843%);fill:#ECECFF;}#graph-div .actor-man circle,#graph-div line{stroke:hsl(259.6261682243, 59.7765363128%, 87.9019607843%);fill:#ECECFF;stroke-width:2px;}#graph-div :root{--mermaid-font-family:"trebuchet ms",verdana,arial,sans-serif;}</style><g></g><defs><symbol height="24" width="24" id="computer"><path d="M2 2v13h20v-13h-20zm18 11h-16v-9h16v9zm-10.228 6l.466-1h3.524l.467 1h-4.457zm14.228 3h-24l2-6h2.104l-1.33 4h18.45l-1.297-4h2.073l2 6zm-5-10h-14v-7h14v7z" transform="scale(.5)"></path></symbol></defs><defs><symbol clip-rule="evenodd" fill-rule="evenodd" id="database"><path d="M12.258.001l.256.004.255.005.253.008.251.01.249.012.247.015.246.016.242.019.241.02.239.023.236.024.233.027.231.028.229.031.225.032.223.034.22.036.217.038.214.04.211.041.208.043.205.045.201.046.198.048.194.05.191.051.187.053.183.054.18.056.175.057.172.059.168.06.163.061.16.063.155.064.15.066.074.033.073.033.071.034.07.034.069.035.068.035.067.035.066.035.064.036.064.036.062.036.06.036.06.037.058.037.058.037.055.038.055.038.053.038.052.038.051.039.05.039.048.039.047.039.045.04.044.04.043.04.041.04.04.041.039.041.037.041.036.041.034.041.033.042.032.042.03.042.029.042.027.042.026.043.024.043.023.043.021.043.02.043.018.044.017.043.015.044.013.044.012.044.011.045.009.044.007.045.006.045.004.045.002.045.001.045v17l-.001.045-.002.045-.004.045-.006.045-.007.045-.009.044-.011.045-.012.044-.013.044-.015.044-.017.043-.018.044-.02.043-.021.043-.023.043-.024.043-.026.043-.027.042-.029.042-.03.042-.032.042-.033.042-.034.041-.036.041-.037.041-.039.041-.04.041-.041.04-.043.04-.044.04-.045.04-.047.039-.048.039-.05.039-.051.039-.052.038-.053.038-.055.038-.055.038-.058.037-.058.037-.06.037-.06.036-.062.036-.064.036-.064.036-.066.035-.067.035-.068.035-.069.035-.07.034-.071.034-.073.033-.074.033-.15.066-.155.064-.16.063-.163.061-.168.06-.172.059-.175.057-.18.056-.183.054-.187.053-.191.051-.194.05-.198.048-.201.046-.205.045-.208.043-.211.041-.214.04-.217.038-.22.036-.223.034-.225.032-.229.031-.231.028-.233.027-.236.024-.239.023-.241.02-.242.019-.246.016-.247.015-.249.012-.251.01-.253.008-.255.005-.256.004-.258.001-.258-.001-.256-.004-.255-.005-.253-.008-.251-.01-.249-.012-.247-.015-.245-.016-.243-.019-.241-.02-.238-.023-.236-.024-.234-.027-.231-.028-.228-.031-.226-.032-.223-.034-.22-.036-.217-.038-.214-.04-.211-.041-.208-.043-.204-.045-.201-.046-.198-.048-.195-.05-.19-.051-.187-.053-.184-.054-.179-.056-.176-.057-.172-.059-.167-.06-.164-.061-.159-.063-.155-.064-.151-.066-.074-.033-.072-.033-.072-.034-.07-.034-.069-.035-.068-.035-.067-.035-.066-.035-.064-.036-.063-.036-.062-.036-.061-.036-.06-.037-.058-.037-.057-.037-.056-.038-.055-.038-.053-.038-.052-.038-.051-.039-.049-.039-.049-.039-.046-.039-.046-.04-.044-.04-.043-.04-.041-.04-.04-.041-.039-.041-.037-.041-.036-.041-.034-.041-.033-.042-.032-.042-.03-.042-.029-.042-.027-.042-.026-.043-.024-.043-.023-.043-.021-.043-.02-.043-.018-.044-.017-.043-.015-.044-.013-.044-.012-.044-.011-.045-.009-.044-.007-.045-.006-.045-.004-.045-.002-.045-.001-.045v-17l.001-.045.002-.045.004-.045.006-.045.007-.045.009-.044.011-.045.012-.044.013-.044.015-.044.017-.043.018-.044.02-.043.021-.043.023-.043.024-.043.026-.043.027-.042.029-.042.03-.042.032-.042.033-.042.034-.041.036-.041.037-.041.039-.041.04-.041.041-.04.043-.04.044-.04.046-.04.046-.039.049-.039.049-.039.051-.039.052-.038.053-.038.055-.038.056-.038.057-.037.058-.037.06-.037.061-.036.062-.036.063-.036.064-.036.066-.035.067-.035.068-.035.069-.035.07-.034.072-.034.072-.033.074-.033.151-.066.155-.064.159-.063.164-.061.167-.06.172-.059.176-.057.179-.056.184-.054.187-.053.19-.051.195-.05.198-.048.201-.046.204-.045.208-.043.211-.041.214-.04.217-.038.22-.036.223-.034.226-.032.228-.031.231-.028.234-.027.236-.024.238-.023.241-.02.243-.019.245-.016.247-.015.249-.012.251-.01.253-.008.255-.005.256-.004.258-.001.258.001zm-9.258 20.499v.01l.001.021.003.021.004.022.005.021.006.022.007.022.009.023.01.022.011.023.012.023.013.023.015.023.016.024.017.023.018.024.019.024.021.024.022.025.023.024.024.025.052.049.056.05.061.051.066.051.07.051.075.051.079.052.084.052.088.052.092.052.097.052.102.051.105.052.11.052.114.051.119.051.123.051.127.05.131.05.135.05.139.048.144.049.147.047.152.047.155.047.16.045.163.045.167.043.171.043.176.041.178.041.183.039.187.039.19.037.194.035.197.035.202.033.204.031.209.03.212.029.216.027.219.025.222.024.226.021.23.02.233.018.236.016.24.015.243.012.246.01.249.008.253.005.256.004.259.001.26-.001.257-.004.254-.005.25-.008.247-.011.244-.012.241-.014.237-.016.233-.018.231-.021.226-.021.224-.024.22-.026.216-.027.212-.028.21-.031.205-.031.202-.034.198-.034.194-.036.191-.037.187-.039.183-.04.179-.04.175-.042.172-.043.168-.044.163-.045.16-.046.155-.046.152-.047.148-.048.143-.049.139-.049.136-.05.131-.05.126-.05.123-.051.118-.052.114-.051.11-.052.106-.052.101-.052.096-.052.092-.052.088-.053.083-.051.079-.052.074-.052.07-.051.065-.051.06-.051.056-.05.051-.05.023-.024.023-.025.021-.024.02-.024.019-.024.018-.024.017-.024.015-.023.014-.024.013-.023.012-.023.01-.023.01-.022.008-.022.006-.022.006-.022.004-.022.004-.021.001-.021.001-.021v-4.127l-.077.055-.08.053-.083.054-.085.053-.087.052-.09.052-.093.051-.095.05-.097.05-.1.049-.102.049-.105.048-.106.047-.109.047-.111.046-.114.045-.115.045-.118.044-.12.043-.122.042-.124.042-.126.041-.128.04-.13.04-.132.038-.134.038-.135.037-.138.037-.139.035-.142.035-.143.034-.144.033-.147.032-.148.031-.15.03-.151.03-.153.029-.154.027-.156.027-.158.026-.159.025-.161.024-.162.023-.163.022-.165.021-.166.02-.167.019-.169.018-.169.017-.171.016-.173.015-.173.014-.175.013-.175.012-.177.011-.178.01-.179.008-.179.008-.181.006-.182.005-.182.004-.184.003-.184.002h-.37l-.184-.002-.184-.003-.182-.004-.182-.005-.181-.006-.179-.008-.179-.008-.178-.01-.176-.011-.176-.012-.175-.013-.173-.014-.172-.015-.171-.016-.17-.017-.169-.018-.167-.019-.166-.02-.165-.021-.163-.022-.162-.023-.161-.024-.159-.025-.157-.026-.156-.027-.155-.027-.153-.029-.151-.03-.15-.03-.148-.031-.146-.032-.145-.033-.143-.034-.141-.035-.14-.035-.137-.037-.136-.037-.134-.038-.132-.038-.13-.04-.128-.04-.126-.041-.124-.042-.122-.042-.12-.044-.117-.043-.116-.045-.113-.045-.112-.046-.109-.047-.106-.047-.105-.048-.102-.049-.1-.049-.097-.05-.095-.05-.093-.052-.09-.051-.087-.052-.085-.053-.083-.054-.08-.054-.077-.054v4.127zm0-5.654v.011l.001.021.003.021.004.021.005.022.006.022.007.022.009.022.01.022.011.023.012.023.013.023.015.024.016.023.017.024.018.024.019.024.021.024.022.024.023.025.024.024.052.05.056.05.061.05.066.051.07.051.075.052.079.051.084.052.088.052.092.052.097.052.102.052.105.052.11.051.114.051.119.052.123.05.127.051.131.05.135.049.139.049.144.048.147.048.152.047.155.046.16.045.163.045.167.044.171.042.176.042.178.04.183.04.187.038.19.037.194.036.197.034.202.033.204.032.209.03.212.028.216.027.219.025.222.024.226.022.23.02.233.018.236.016.24.014.243.012.246.01.249.008.253.006.256.003.259.001.26-.001.257-.003.254-.006.25-.008.247-.01.244-.012.241-.015.237-.016.233-.018.231-.02.226-.022.224-.024.22-.025.216-.027.212-.029.21-.03.205-.032.202-.033.198-.035.194-.036.191-.037.187-.039.183-.039.179-.041.175-.042.172-.043.168-.044.163-.045.16-.045.155-.047.152-.047.148-.048.143-.048.139-.05.136-.049.131-.05.126-.051.123-.051.118-.051.114-.052.11-.052.106-.052.101-.052.096-.052.092-.052.088-.052.083-.052.079-.052.074-.051.07-.052.065-.051.06-.05.056-.051.051-.049.023-.025.023-.024.021-.025.02-.024.019-.024.018-.024.017-.024.015-.023.014-.023.013-.024.012-.022.01-.023.01-.023.008-.022.006-.022.006-.022.004-.021.004-.022.001-.021.001-.021v-4.139l-.077.054-.08.054-.083.054-.085.052-.087.053-.09.051-.093.051-.095.051-.097.05-.1.049-.102.049-.105.048-.106.047-.109.047-.111.046-.114.045-.115.044-.118.044-.12.044-.122.042-.124.042-.126.041-.128.04-.13.039-.132.039-.134.038-.135.037-.138.036-.139.036-.142.035-.143.033-.144.033-.147.033-.148.031-.15.03-.151.03-.153.028-.154.028-.156.027-.158.026-.159.025-.161.024-.162.023-.163.022-.165.021-.166.02-.167.019-.169.018-.169.017-.171.016-.173.015-.173.014-.175.013-.175.012-.177.011-.178.009-.179.009-.179.007-.181.007-.182.005-.182.004-.184.003-.184.002h-.37l-.184-.002-.184-.003-.182-.004-.182-.005-.181-.007-.179-.007-.179-.009-.178-.009-.176-.011-.176-.012-.175-.013-.173-.014-.172-.015-.171-.016-.17-.017-.169-.018-.167-.019-.166-.02-.165-.021-.163-.022-.162-.023-.161-.024-.159-.025-.157-.026-.156-.027-.155-.028-.153-.028-.151-.03-.15-.03-.148-.031-.146-.033-.145-.033-.143-.033-.141-.035-.14-.036-.137-.036-.136-.037-.134-.038-.132-.039-.13-.039-.128-.04-.126-.041-.124-.042-.122-.043-.12-.043-.117-.044-.116-.044-.113-.046-.112-.046-.109-.046-.106-.047-.105-.048-.102-.049-.1-.049-.097-.05-.095-.051-.093-.051-.09-.051-.087-.053-.085-.052-.083-.054-.08-.054-.077-.054v4.139zm0-5.666v.011l.001.02.003.022.004.021.005.022.006.021.007.022.009.023.01.022.011.023.012.023.013.023.015.023.016.024.017.024.018.023.019.024.021.025.022.024.023.024.024.025.052.05.056.05.061.05.066.051.07.051.075.052.079.051.084.052.088.052.092.052.097.052.102.052.105.051.11.052.114.051.119.051.123.051.127.05.131.05.135.05.139.049.144.048.147.048.152.047.155.046.16.045.163.045.167.043.171.043.176.042.178.04.183.04.187.038.19.037.194.036.197.034.202.033.204.032.209.03.212.028.216.027.219.025.222.024.226.021.23.02.233.018.236.017.24.014.243.012.246.01.249.008.253.006.256.003.259.001.26-.001.257-.003.254-.006.25-.008.247-.01.244-.013.241-.014.237-.016.233-.018.231-.02.226-.022.224-.024.22-.025.216-.027.212-.029.21-.03.205-.032.202-.033.198-.035.194-.036.191-.037.187-.039.183-.039.179-.041.175-.042.172-.043.168-.044.163-.045.16-.045.155-.047.152-.047.148-.048.143-.049.139-.049.136-.049.131-.051.126-.05.123-.051.118-.052.114-.051.11-.052.106-.052.101-.052.096-.052.092-.052.088-.052.083-.052.079-.052.074-.052.07-.051.065-.051.06-.051.056-.05.051-.049.023-.025.023-.025.021-.024.02-.024.019-.024.018-.024.017-.024.015-.023.014-.024.013-.023.012-.023.01-.022.01-.023.008-.022.006-.022.006-.022.004-.022.004-.021.001-.021.001-.021v-4.153l-.077.054-.08.054-.083.053-.085.053-.087.053-.09.051-.093.051-.095.051-.097.05-.1.049-.102.048-.105.048-.106.048-.109.046-.111.046-.114.046-.115.044-.118.044-.12.043-.122.043-.124.042-.126.041-.128.04-.13.039-.132.039-.134.038-.135.037-.138.036-.139.036-.142.034-.143.034-.144.033-.147.032-.148.032-.15.03-.151.03-.153.028-.154.028-.156.027-.158.026-.159.024-.161.024-.162.023-.163.023-.165.021-.166.02-.167.019-.169.018-.169.017-.171.016-.173.015-.173.014-.175.013-.175.012-.177.01-.178.01-.179.009-.179.007-.181.006-.182.006-.182.004-.184.003-.184.001-.185.001-.185-.001-.184-.001-.184-.003-.182-.004-.182-.006-.181-.006-.179-.007-.179-.009-.178-.01-.176-.01-.176-.012-.175-.013-.173-.014-.172-.015-.171-.016-.17-.017-.169-.018-.167-.019-.166-.02-.165-.021-.163-.023-.162-.023-.161-.024-.159-.024-.157-.026-.156-.027-.155-.028-.153-.028-.151-.03-.15-.03-.148-.032-.146-.032-.145-.033-.143-.034-.141-.034-.14-.036-.137-.036-.136-.037-.134-.038-.132-.039-.13-.039-.128-.041-.126-.041-.124-.041-.122-.043-.12-.043-.117-.044-.116-.044-.113-.046-.112-.046-.109-.046-.106-.048-.105-.048-.102-.048-.1-.05-.097-.049-.095-.051-.093-.051-.09-.052-.087-.052-.085-.053-.083-.053-.08-.054-.077-.054v4.153zm8.74-8.179l-.257.004-.254.005-.25.008-.247.011-.244.012-.241.014-.237.016-.233.018-.231.021-.226.022-.224.023-.22.026-.216.027-.212.028-.21.031-.205.032-.202.033-.198.034-.194.036-.191.038-.187.038-.183.04-.179.041-.175.042-.172.043-.168.043-.163.045-.16.046-.155.046-.152.048-.148.048-.143.048-.139.049-.136.05-.131.05-.126.051-.123.051-.118.051-.114.052-.11.052-.106.052-.101.052-.096.052-.092.052-.088.052-.083.052-.079.052-.074.051-.07.052-.065.051-.06.05-.056.05-.051.05-.023.025-.023.024-.021.024-.02.025-.019.024-.018.024-.017.023-.015.024-.014.023-.013.023-.012.023-.01.023-.01.022-.008.022-.006.023-.006.021-.004.022-.004.021-.001.021-.001.021.001.021.001.021.004.021.004.022.006.021.006.023.008.022.01.022.01.023.012.023.013.023.014.023.015.024.017.023.018.024.019.024.02.025.021.024.023.024.023.025.051.05.056.05.06.05.065.051.07.052.074.051.079.052.083.052.088.052.092.052.096.052.101.052.106.052.11.052.114.052.118.051.123.051.126.051.131.05.136.05.139.049.143.048.148.048.152.048.155.046.16.046.163.045.168.043.172.043.175.042.179.041.183.04.187.038.191.038.194.036.198.034.202.033.205.032.21.031.212.028.216.027.22.026.224.023.226.022.231.021.233.018.237.016.241.014.244.012.247.011.25.008.254.005.257.004.26.001.26-.001.257-.004.254-.005.25-.008.247-.011.244-.012.241-.014.237-.016.233-.018.231-.021.226-.022.224-.023.22-.026.216-.027.212-.028.21-.031.205-.032.202-.033.198-.034.194-.036.191-.038.187-.038.183-.04.179-.041.175-.042.172-.043.168-.043.163-.045.16-.046.155-.046.152-.048.148-.048.143-.048.139-.049.136-.05.131-.05.126-.051.123-.051.118-.051.114-.052.11-.052.106-.052.101-.052.096-.052.092-.052.088-.052.083-.052.079-.052.074-.051.07-.052.065-.051.06-.05.056-.05.051-.05.023-.025.023-.024.021-.024.02-.025.019-.024.018-.024.017-.023.015-.024.014-.023.013-.023.012-.023.01-.023.01-.022.008-.022.006-.023.006-.021.004-.022.004-.021.001-.021.001-.021-.001-.021-.001-.021-.004-.021-.004-.022-.006-.021-.006-.023-.008-.022-.01-.022-.01-.023-.012-.023-.013-.023-.014-.023-.015-.024-.017-.023-.018-.024-.019-.024-.02-.025-.021-.024-.023-.024-.023-.025-.051-.05-.056-.05-.06-.05-.065-.051-.07-.052-.074-.051-.079-.052-.083-.052-.088-.052-.092-.052-.096-.052-.101-.052-.106-.052-.11-.052-.114-.052-.118-.051-.123-.051-.126-.051-.131-.05-.136-.05-.139-.049-.143-.048-.148-.048-.152-.048-.155-.046-.16-.046-.163-.045-.168-.043-.172-.043-.175-.042-.179-.041-.183-.04-.187-.038-.191-.038-.194-.036-.198-.034-.202-.033-.205-.032-.21-.031-.212-.028-.216-.027-.22-.026-.224-.023-.226-.022-.231-.021-.233-.018-.237-.016-.241-.014-.244-.012-.247-.011-.25-.008-.254-.005-.257-.004-.26-.001-.26.001z" transform="scale(.5)"></path></symbol></defs><defs><symbol height="24" width="24" id="clock"><path d="M12 2c5.514 0 10 4.486 10 10s-4.486 10-10 10-10-4.486-10-10 4.486-10 10-10zm0-2c-6.627 0-12 5.373-12 12s5.373 12 12 12 12-5.373 12-12-5.373-12-12-12zm5.848 12.459c.202.038.202.333.001.372-1.907.361-6.045 1.111-6.547 1.111-.719 0-1.301-.582-1.301-1.301 0-.512.77-5.447 1.125-7.445.034-.192.312-.181.343.014l.985 6.238 5.394 1.011z" transform="scale(.5)"></path></symbol></defs><defs><marker orient="auto" markerHeight="12" markerWidth="12" markerUnits="userSpaceOnUse" refY="5" refX="9" id="arrowhead"><path d="M 0 0 L 10 5 L 0 10 z"></path></marker></defs><defs><marker refY="5" refX="4" orient="auto" markerHeight="8" markerWidth="15" id="crosshead"><path d="M 1,2 L 6,7 M 6,2 L 1,7" stroke-width="1pt" style="stroke-dasharray: 0px, 0px;" stroke="#000000" fill="none"></path></marker></defs><defs><marker orient="auto" markerHeight="28" markerWidth="20" refY="7" refX="18" id="filled-head"><path d="M 18,7 L9,13 L14,7 L9,1 Z"></path></marker></defs><defs><marker orient="auto" markerHeight="40" markerWidth="60" refY="15" refX="15" id="sequencenumber"><circle r="6" cy="15" cx="15"></circle></marker></defs><g><rect class="note" ry="0" rx="0" height="59" width="392" stroke="#666" fill="#EDF2AE" y="75" x="319"></rect><text dy="1em" class="noteText" style="font-size: 16px; font-weight: 400;" alignment-baseline="middle" dominant-baseline="middle" text-anchor="middle" y="80" x="515"><tspan x="515">1. send and accept group invitation /</tspan></text><text dy="1em" class="noteText" style="font-size: 16px; font-weight: 400;" alignment-baseline="middle" dominant-baseline="middle" text-anchor="middle" y="100" x="515"><tspan x="515">join via group link</tspan></text></g><g><line class="loopLine" y2="144" x2="696" y1="144" x1="334"></line><line class="loopLine" y2="540" x2="696" y1="144" x1="696"></line><line class="loopLine" y2="540" x2="696" y1="540" x1="334"></line><line class="loopLine" y2="540" x2="334" y1="144" x1="334"></line><line style="stroke-dasharray: 3px, 3px;" class="loopLine" y2="280" x2="696" y1="280" x1="334"></line><polygon class="labelBox" points="334,144 384,144 384,157 375.6,164 334,164"></polygon><text class="labelText" style="font-size: 16px; font-weight: 400;" alignment-baseline="middle" dominant-baseline="middle" text-anchor="middle" y="157" x="359">alt</text><text class="loopText" style="font-size: 16px; font-weight: 400;" text-anchor="middle" y="162" x="540"><tspan x="540">[host invites contact]</tspan></text><text class="loopText" style="font-size: 16px; font-weight: 400;" text-anchor="middle" y="298" x="515">[user joins via group link]</text></g><g><rect class="note" ry="0" rx="0" height="118" width="249" stroke="#666" fill="#EDF2AE" y="550" x="711"></rect><text dy="1em" class="noteText" style="font-size: 16px; font-weight: 400;" alignment-baseline="middle" dominant-baseline="middle" text-anchor="middle" y="555" x="836"><tspan x="836">when joining via group link</tspan></text><text dy="1em" class="noteText" style="font-size: 16px; font-weight: 400;" alignment-baseline="middle" dominant-baseline="middle" text-anchor="middle" y="575" x="836"><tspan x="836">Bob doesn't wait for x.grp.info</tspan></text><text dy="1em" class="noteText" style="font-size: 16px; font-weight: 400;" alignment-baseline="middle" dominant-baseline="middle" text-anchor="middle" y="594" x="836"><tspan x="836">and initiates group handshake</tspan></text><text dy="1em" class="noteText" style="font-size: 16px; font-weight: 400;" alignment-baseline="middle" dominant-baseline="middle" text-anchor="middle" y="614" x="836"><tspan x="836">with x.grp.acpt.address</tspan></text><text dy="1em" class="noteText" style="font-size: 16px; font-weight: 400;" alignment-baseline="middle" dominant-baseline="middle" text-anchor="middle" y="633" x="836"><tspan x="836">after establishing connection</tspan></text></g><g><rect class="note" ry="0" rx="0" height="40" width="211" stroke="#666" fill="#EDF2AE" y="678" x="580.5"></rect><text dy="1em" class="noteText" style="font-size: 16px; font-weight: 400;" alignment-baseline="middle" dominant-baseline="middle" text-anchor="middle" y="683" x="686"><tspan x="686">create per group address</tspan></text></g><g><rect class="note" ry="0" rx="0" height="40" width="661" stroke="#666" fill="#EDF2AE" y="881" x="50"></rect><text dy="1em" class="noteText" style="font-size: 16px; font-weight: 400;" alignment-baseline="middle" dominant-baseline="middle" text-anchor="middle" y="886" x="381"><tspan x="381">2. introduce new member Bob to all existing members</tspan></text></g><g><rect class="note" ry="0" rx="0" height="40" width="200" stroke="#666" fill="#EDF2AE" y="1167" x="586"></rect><text dy="1em" class="noteText" style="font-size: 16px; font-weight: 400;" alignment-baseline="middle" dominant-baseline="middle" text-anchor="middle" y="1172" x="686"><tspan x="686">create N MemberCodes</tspan></text></g><g><line class="loopLine" y2="1036" x2="796" y1="1036" x1="334"></line><line class="loopLine" y2="1322" x2="796" y1="1036" x1="796"></line><line class="loopLine" y2="1322" x2="796" y1="1322" x1="334"></line><line class="loopLine" y2="1322" x2="334" y1="1036" x1="334"></line><polygon class="labelBox" points="334,1036 384,1036 384,1049 375.6,1056 334,1056"></polygon><text class="labelText" style="font-size: 16px; font-weight: 400;" alignment-baseline="middle" dominant-baseline="middle" text-anchor="middle" y="1049" x="359">loop</text><text class="loopText" style="font-size: 16px; font-weight: 400;" text-anchor="middle" y="1054" x="590"><tspan x="590">[batched]</tspan></text></g><g><rect class="note" ry="0" rx="0" height="40" width="661" stroke="#666" fill="#EDF2AE" y="1456" x="50"></rect><text dy="1em" class="noteText" style="font-size: 16px; font-weight: 400;" alignment-baseline="middle" dominant-baseline="middle" text-anchor="middle" y="1461" x="381"><tspan x="381">3. establish group member connection</tspan></text></g><g><rect class="note" ry="0" rx="0" height="40" width="661" stroke="#666" fill="#EDF2AE" y="1621" x="50"></rect><text dy="1em" class="noteText" style="font-size: 16px; font-weight: 400;" alignment-baseline="middle" dominant-baseline="middle" text-anchor="middle" y="1626" x="381"><tspan x="381">no contact deduplication</tspan></text></g><g><rect class="note" ry="0" rx="0" height="40" width="211" stroke="#666" fill="#EDF2AE" y="1772" x="580.5"></rect><text dy="1em" class="noteText" style="font-size: 16px; font-weight: 400;" alignment-baseline="middle" dominant-baseline="middle" text-anchor="middle" y="1777" x="686"><tspan x="686">delete per group address</tspan></text></g><g><line class="loopLine" y2="1671" x2="801.5" y1="1671" x1="570.5"></line><line class="loopLine" y2="1822" x2="801.5" y1="1671" x1="801.5"></line><line class="loopLine" y2="1822" x2="801.5" y1="1822" x1="570.5"></line><line class="loopLine" y2="1822" x2="570.5" y1="1671" x1="570.5"></line><polygon class="labelBox" points="570.5,1671 620.5,1671 620.5,1684 612.1,1691 570.5,1691"></polygon><text class="labelText" style="font-size: 16px; font-weight: 400;" alignment-baseline="middle" dominant-baseline="middle" text-anchor="middle" y="1684" x="596">opt</text><text class="loopText" style="font-size: 16px; font-weight: 400;" text-anchor="middle" y="1689" x="711"><tspan x="711">[all introduced</tspan></text><text class="loopText" style="font-size: 16px; font-weight: 400;" text-anchor="middle" y="1708" x="711"><tspan x="711">members</tspan></text><text class="loopText" style="font-size: 16px; font-weight: 400;" text-anchor="middle" y="1728" x="711"><tspan x="711">connected /</tspan></text><text class="loopText" style="font-size: 16px; font-weight: 400;" text-anchor="middle" y="1747" x="711"><tspan x="711">expiration]</tspan></text></g><text dy="1em" class="messageText" style="font-size: 16px; font-weight: 400;" alignment-baseline="middle" dominant-baseline="middle" text-anchor="middle" y="194" x="515">x.grp.inv</text><text dy="1em" class="messageText" style="font-size: 16px; font-weight: 400;" alignment-baseline="middle" dominant-baseline="middle" text-anchor="middle" y="214" x="515">invite Bob to group</text><text dy="1em" class="messageText" style="font-size: 16px; font-weight: 400;" alignment-baseline="middle" dominant-baseline="middle" text-anchor="middle" y="233" x="515">(via contact connection)</text><line marker-end="url(#arrowhead)" style="fill: none;" stroke="none" stroke-width="2" class="messageLine0" y2="265" x2="686" y1="265" x1="344"></line><text dy="1em" class="messageText" style="font-size: 16px; font-weight: 400;" alignment-baseline="middle" dominant-baseline="middle" text-anchor="middle" y="325" x="515">request to join group via link</text><line marker-end="url(#arrowhead)" style="fill: none;" stroke="none" stroke-width="2" class="messageLine0" y2="358" x2="344" y1="358" x1="686"></line><text dy="1em" class="messageText" style="font-size: 16px; font-weight: 400;" alignment-baseline="middle" dominant-baseline="middle" text-anchor="middle" y="373" x="515">auto-accept</text><text dy="1em" class="messageText" style="font-size: 16px; font-weight: 400;" alignment-baseline="middle" dominant-baseline="middle" text-anchor="middle" y="393" x="515">x.group.link.info with host's profile</text><text dy="1em" class="messageText" style="font-size: 16px; font-weight: 400;" alignment-baseline="middle" dominant-baseline="middle" text-anchor="middle" y="412" x="515">and joining member MemberId</text><text dy="1em" class="messageText" style="font-size: 16px; font-weight: 400;" alignment-baseline="middle" dominant-baseline="middle" text-anchor="middle" y="432" x="515">establish group member connection</text><line marker-end="url(#arrowhead)" style="fill: none;" stroke="none" stroke-width="2" class="messageLine0" y2="463" x2="686" y1="463" x1="344"></line><text dy="1em" class="messageText" style="font-size: 16px; font-weight: 400;" alignment-baseline="middle" dominant-baseline="middle" text-anchor="middle" y="478" x="515">x.grp.info</text><text dy="1em" class="messageText" style="font-size: 16px; font-weight: 400;" alignment-baseline="middle" dominant-baseline="middle" text-anchor="middle" y="498" x="515">group profile</text><line marker-end="url(#arrowhead)" style="fill: none;" stroke="none" stroke-width="2" class="messageLine0" y2="530" x2="686" y1="530" x1="344"></line><text dy="1em" class="messageText" style="font-size: 16px; font-weight: 400;" alignment-baseline="middle" dominant-baseline="middle" text-anchor="middle" y="733" x="515">x.grp.acpt.address</text><text dy="1em" class="messageText" style="font-size: 16px; font-weight: 400;" alignment-baseline="middle" dominant-baseline="middle" text-anchor="middle" y="753" x="515">accept invitation</text><text dy="1em" class="messageText" style="font-size: 16px; font-weight: 400;" alignment-baseline="middle" dominant-baseline="middle" text-anchor="middle" y="772" x="515">and send address to connect</text><text dy="1em" class="messageText" style="font-size: 16px; font-weight: 400;" alignment-baseline="middle" dominant-baseline="middle" text-anchor="middle" y="792" x="515">(via member connection)</text><line marker-end="url(#arrowhead)" style="fill: none;" stroke="none" stroke-width="2" class="messageLine0" y2="823" x2="344" y1="823" x1="686"></line><text dy="1em" class="messageText" style="font-size: 16px; font-weight: 400;" alignment-baseline="middle" dominant-baseline="middle" text-anchor="middle" y="838" x="515">establish group member connection</text><line marker-end="url(#arrowhead)" style="fill: none;" stroke="none" stroke-width="2" class="messageLine0" y2="871" x2="344" y1="871" x1="686"></line><text dy="1em" class="messageText" style="font-size: 16px; font-weight: 400;" alignment-baseline="middle" dominant-baseline="middle" text-anchor="middle" y="936" x="210">x.grp.mem.new</text><text dy="1em" class="messageText" style="font-size: 16px; font-weight: 400;" alignment-baseline="middle" dominant-baseline="middle" text-anchor="middle" y="956" x="210">"announce" Bob</text><text dy="1em" class="messageText" style="font-size: 16px; font-weight: 400;" alignment-baseline="middle" dominant-baseline="middle" text-anchor="middle" y="975" x="210">to existing members</text><text dy="1em" class="messageText" style="font-size: 16px; font-weight: 400;" alignment-baseline="middle" dominant-baseline="middle" text-anchor="middle" y="995" x="210">(via member connections)</text><line marker-end="url(#arrowhead)" style="fill: none;" stroke="none" stroke-width="2" class="messageLine0" y2="1026" x2="75" y1="1026" x1="344"></line><text dy="1em" class="messageText" style="font-size: 16px; font-weight: 400;" alignment-baseline="middle" dominant-baseline="middle" text-anchor="middle" y="1086" x="515">x.grp.mem.intro * N</text><text dy="1em" class="messageText" style="font-size: 16px; font-weight: 400;" alignment-baseline="middle" dominant-baseline="middle" text-anchor="middle" y="1106" x="515">"introduce" members</text><text dy="1em" class="messageText" style="font-size: 16px; font-weight: 400;" alignment-baseline="middle" dominant-baseline="middle" text-anchor="middle" y="1125" x="515">(via member connection)</text><line marker-end="url(#arrowhead)" style="fill: none;" stroke="none" stroke-width="2" class="messageLine0" y2="1157" x2="686" y1="1157" x1="344"></line><text dy="1em" class="messageText" style="font-size: 16px; font-weight: 400;" alignment-baseline="middle" dominant-baseline="middle" text-anchor="middle" y="1222" x="515">x.grp.mem.inv.code</text><text dy="1em" class="messageText" style="font-size: 16px; font-weight: 400;" alignment-baseline="middle" dominant-baseline="middle" text-anchor="middle" y="1242" x="515">unique MemberCodes</text><text dy="1em" class="messageText" style="font-size: 16px; font-weight: 400;" alignment-baseline="middle" dominant-baseline="middle" text-anchor="middle" y="1261" x="515">for all members</text><text dy="1em" class="messageText" style="font-size: 16px; font-weight: 400;" alignment-baseline="middle" dominant-baseline="middle" text-anchor="middle" y="1281" x="515">(via member connection)</text><line marker-end="url(#arrowhead)" style="fill: none;" stroke="none" stroke-width="2" class="messageLine0" y2="1312" x2="344" y1="1312" x1="686"></line><text dy="1em" class="messageText" style="font-size: 16px; font-weight: 400;" alignment-baseline="middle" dominant-baseline="middle" text-anchor="middle" y="1337" x="210">x.grp.mem.fwd.code</text><text dy="1em" class="messageText" style="font-size: 16px; font-weight: 400;" alignment-baseline="middle" dominant-baseline="middle" text-anchor="middle" y="1357" x="210">forward address</text><text dy="1em" class="messageText" style="font-size: 16px; font-weight: 400;" alignment-baseline="middle" dominant-baseline="middle" text-anchor="middle" y="1376" x="210">and unique MemberCodes</text><text dy="1em" class="messageText" style="font-size: 16px; font-weight: 400;" alignment-baseline="middle" dominant-baseline="middle" text-anchor="middle" y="1396" x="210">to all members</text><text dy="1em" class="messageText" style="font-size: 16px; font-weight: 400;" alignment-baseline="middle" dominant-baseline="middle" text-anchor="middle" y="1415" x="210">(via member connections)</text><line marker-end="url(#arrowhead)" style="fill: none;" stroke="none" stroke-width="2" class="messageLine0" y2="1446" x2="75" y1="1446" x1="344"></line><text dy="1em" class="messageText" style="font-size: 16px; font-weight: 400;" alignment-baseline="middle" dominant-baseline="middle" text-anchor="middle" y="1511" x="381">request group member connection</text><text dy="1em" class="messageText" style="font-size: 16px; font-weight: 400;" alignment-baseline="middle" dominant-baseline="middle" text-anchor="middle" y="1531" x="381">x.introduced with MemberCode</text><line marker-end="url(#arrowhead)" style="fill: none;" stroke="none" stroke-width="2" class="messageLine0" y2="1563" x2="686" y1="1563" x1="75"></line><text dy="1em" class="messageText" style="font-size: 16px; font-weight: 400;" alignment-baseline="middle" dominant-baseline="middle" text-anchor="middle" y="1578" x="381">verify MemberCode, auto-accept</text><line marker-end="url(#arrowhead)" style="fill: none;" stroke="none" stroke-width="2" class="messageLine0" y2="1611" x2="75" y1="1611" x1="686"></line></svg> \ No newline at end of file diff --git a/scripts/nix/sha256map.nix b/scripts/nix/sha256map.nix index f3ad3061a..09ac41e49 100644 --- a/scripts/nix/sha256map.nix +++ b/scripts/nix/sha256map.nix @@ -1,5 +1,5 @@ { - "https://github.com/simplex-chat/simplexmq.git"."4c0b8a31d20870a23e120e243359901d8240f922" = "0lrgfm8di0x4rmidqp7k2fw29yaal6467nmb85lwk95yz602906z"; + "https://github.com/simplex-chat/simplexmq.git"."980e5c4d1ec15f44290542fd2a5d1c08456f00d1" = "1lqciyy215dvmbhykyp80bwipqmxybv39p6jff6vjgd5r34958nh"; "https://github.com/simplex-chat/hs-socks.git"."a30cc7a79a08d8108316094f8f2f82a0c5e1ac51" = "0yasvnr7g91k76mjkamvzab2kvlb1g5pspjyjn2fr6v83swjhj38"; "https://github.com/kazu-yamamoto/http2.git"."b5a1b7200cf5bc7044af34ba325284271f6dff25" = "0dqb50j57an64nf4qcf5vcz4xkd1vzvghvf8bk529c1k30r9nfzb"; "https://github.com/simplex-chat/direct-sqlcipher.git"."34309410eb2069b029b8fc1872deb1e0db123294" = "0kwkmhyfsn2lixdlgl15smgr1h5gjk7fky6abzh8rng2h5ymnffd"; diff --git a/simplex-chat.cabal b/simplex-chat.cabal index c510e7333..335e0ee10 100644 --- a/simplex-chat.cabal +++ b/simplex-chat.cabal @@ -1,6 +1,6 @@ cabal-version: 1.12 --- This file has been generated from package.yaml by hpack version 0.35.0. +-- This file has been generated from package.yaml by hpack version 0.35.2. -- -- see: https://github.com/sol/hpack @@ -10,7 +10,7 @@ category: Web, System, Services, Cryptography homepage: https://github.com/simplex-chat/simplex-chat#readme author: simplex.chat maintainer: chat@simplex.chat -copyright: 2020-23 simplex.chat +copyright: 2020-22 simplex.chat license: AGPL-3 license-file: LICENSE build-type: Simple @@ -108,8 +108,11 @@ library Simplex.Chat.Migrations.M20230705_delivery_receipts Simplex.Chat.Migrations.M20230721_group_snd_item_statuses Simplex.Chat.Migrations.M20230814_indexes + Simplex.Chat.Migrations.M20230827_file_encryption Simplex.Chat.Migrations.M20230829_connections_chat_vrange Simplex.Chat.Mobile + Simplex.Chat.Mobile.File + Simplex.Chat.Mobile.Shared Simplex.Chat.Mobile.WebRTC Simplex.Chat.Options Simplex.Chat.ProfileGenerator diff --git a/src/Simplex/Chat.hs b/src/Simplex/Chat.hs index 1165a2947..6fc2f855e 100644 --- a/src/Simplex/Chat.hs +++ b/src/Simplex/Chat.hs @@ -1,4 +1,3 @@ -{-# LANGUAGE BangPatterns #-} {-# LANGUAGE DataKinds #-} {-# LANGUAGE DuplicateRecordFields #-} {-# LANGUAGE FlexibleContexts #-} @@ -26,7 +25,7 @@ import Crypto.Random (drgNew) import qualified Data.Aeson as J import Data.Attoparsec.ByteString.Char8 (Parser) import qualified Data.Attoparsec.ByteString.Char8 as A -import Data.Bifunctor (bimap, first, second) +import Data.Bifunctor (bimap, first) import qualified Data.ByteString.Base64 as B64 import Data.ByteString.Char8 (ByteString) import qualified Data.ByteString.Char8 as B @@ -42,7 +41,6 @@ import qualified Data.List.NonEmpty as L import Data.Map.Strict (Map) import qualified Data.Map.Strict as M import Data.Maybe (catMaybes, fromMaybe, isJust, isNothing, listToMaybe, mapMaybe, maybeToList) -import qualified Data.Set as S import Data.Text (Text) import qualified Data.Text as T import Data.Text.Encoding (encodeUtf8) @@ -86,6 +84,8 @@ import qualified Simplex.Messaging.Agent.Store.SQLite.DB as DB import qualified Simplex.Messaging.Agent.Store.SQLite.Migrations as Migrations import Simplex.Messaging.Client (defaultNetworkConfig) import qualified Simplex.Messaging.Crypto as C +import Simplex.Messaging.Crypto.File (CryptoFile (..), CryptoFileArgs (..)) +import qualified Simplex.Messaging.Crypto.File as CF import Simplex.Messaging.Encoding import Simplex.Messaging.Encoding.String import Simplex.Messaging.Parsers (base64P) @@ -564,8 +564,9 @@ processChatCommand = \case SendFileSMP fileInline -> smpSndFileTransfer file fileSize fileInline SendFileXFTP -> xftpSndFileTransfer user file fileSize 1 $ CGContact ct where - smpSndFileTransfer :: FilePath -> Integer -> Maybe InlineFileMode -> m (FileInvitation, CIFile 'MDSnd, FileTransferMeta) - smpSndFileTransfer file fileSize fileInline = do + smpSndFileTransfer :: CryptoFile -> Integer -> Maybe InlineFileMode -> m (FileInvitation, CIFile 'MDSnd, FileTransferMeta) + smpSndFileTransfer (CryptoFile _ (Just _)) _ _ = throwChatError $ CEFileInternal "locally encrypted files can't be sent via SMP" -- can only happen if XFTP is disabled + smpSndFileTransfer (CryptoFile file Nothing) fileSize fileInline = do (agentConnId_, fileConnReq) <- if isJust fileInline then pure (Nothing, Nothing) @@ -578,7 +579,8 @@ processChatCommand = \case fileStatus <- case fileInline of Just IFMSent -> createSndDirectInlineFT db ct ft $> CIFSSndTransfer 0 1 _ -> pure CIFSSndStored - let ciFile = CIFile {fileId, fileName, fileSize, filePath = Just file, fileStatus, fileProtocol = FPSMP} + let fileSource = Just $ CF.plain file + ciFile = CIFile {fileId, fileName, fileSize, fileSource, fileStatus, fileProtocol = FPSMP} pure (fileInvitation, ciFile, ft) prepareMsg :: Maybe FileInvitation -> Maybe CITimed -> m (MsgContainer, Maybe (CIQuote 'CTDirect)) prepareMsg fInv_ timed_ = case quotedItemId_ of @@ -627,15 +629,17 @@ processChatCommand = \case SendFileSMP fileInline -> smpSndFileTransfer file fileSize fileInline SendFileXFTP -> xftpSndFileTransfer user file fileSize n $ CGGroup g where - smpSndFileTransfer :: FilePath -> Integer -> Maybe InlineFileMode -> m (FileInvitation, CIFile 'MDSnd, FileTransferMeta) - smpSndFileTransfer file fileSize fileInline = do + smpSndFileTransfer :: CryptoFile -> Integer -> Maybe InlineFileMode -> m (FileInvitation, CIFile 'MDSnd, FileTransferMeta) + smpSndFileTransfer (CryptoFile _ (Just _)) _ _ = throwChatError $ CEFileInternal "locally encrypted files can't be sent via SMP" -- can only happen if XFTP is disabled + smpSndFileTransfer (CryptoFile file Nothing) fileSize fileInline = do let fileName = takeFileName file fileInvitation = FileInvitation {fileName, fileSize, fileDigest = Nothing, fileConnReq = Nothing, fileInline, fileDescr = Nothing} fileStatus = if fileInline == Just IFMSent then CIFSSndTransfer 0 1 else CIFSSndStored chSize <- asks $ fileChunkSize . config withStore' $ \db -> do ft@FileTransferMeta {fileId} <- createSndGroupFileTransfer db userId gInfo file fileInvitation chSize - let ciFile = CIFile {fileId, fileName, fileSize, filePath = Just file, fileStatus, fileProtocol = FPSMP} + let fileSource = Just $ CF.plain file + ciFile = CIFile {fileId, fileName, fileSize, fileSource, fileStatus, fileProtocol = FPSMP} pure (fileInvitation, ciFile, ft) sendGroupFileInline :: [GroupMember] -> SharedMsgId -> FileTransferMeta -> m () sendGroupFileInline ms sharedMsgId ft@FileTransferMeta {fileInline} = @@ -690,17 +694,19 @@ processChatCommand = \case qText = msgContentText qmc qFileName = maybe qText (T.pack . (fileName :: CIFile d -> String)) ciFile_ qTextOrFile = if T.null qText then qFileName else qText - xftpSndFileTransfer :: User -> FilePath -> Integer -> Int -> ContactOrGroup -> m (FileInvitation, CIFile 'MDSnd, FileTransferMeta) - xftpSndFileTransfer user file fileSize n contactOrGroup = do - let fileName = takeFileName file + xftpSndFileTransfer :: User -> CryptoFile -> Integer -> Int -> ContactOrGroup -> m (FileInvitation, CIFile 'MDSnd, FileTransferMeta) + xftpSndFileTransfer user file@(CryptoFile filePath cfArgs) fileSize n contactOrGroup = do + let fileName = takeFileName filePath fileDescr = FileDescr {fileDescrText = "", fileDescrPartNo = 0, fileDescrComplete = False} fInv = xftpFileInvitation fileName fileSize fileDescr - fsFilePath <- toFSFilePath file - aFileId <- withAgent $ \a -> xftpSendFile a (aUserId user) fsFilePath (roundedFDCount n) + fsFilePath <- toFSFilePath filePath + let srcFile = CryptoFile fsFilePath cfArgs + aFileId <- withAgent $ \a -> xftpSendFile a (aUserId user) srcFile (roundedFDCount n) -- TODO CRSndFileStart event for XFTP chSize <- asks $ fileChunkSize . config ft@FileTransferMeta {fileId} <- withStore' $ \db -> createSndFileTransferXFTP db user contactOrGroup file fInv (AgentSndFileId aFileId) chSize - let ciFile = CIFile {fileId, fileName, fileSize, filePath = Just file, fileStatus = CIFSSndStored, fileProtocol = FPXFTP} + let fileSource = Just $ CryptoFile filePath cfArgs + ciFile = CIFile {fileId, fileName, fileSize, fileSource, fileStatus = CIFSSndStored, fileProtocol = FPXFTP} case contactOrGroup of CGContact Contact {activeConn} -> withStore' $ \db -> createSndFTDescrXFTP db user Nothing activeConn ft fileDescr CGGroup (Group _ ms) -> forM_ ms $ \m -> saveMemberFD m `catchChatError` (toView . CRChatError (Just user)) @@ -1621,26 +1627,40 @@ processChatCommand = \case asks showLiveItems >>= atomically . (`writeTVar` on) >> ok_ SendFile chatName f -> withUser $ \user -> do chatRef <- getChatRef user chatName - processChatCommand . APISendMessage chatRef False Nothing $ ComposedMessage (Just f) Nothing (MCFile "") + processChatCommand . APISendMessage chatRef False Nothing $ ComposedMessage (Just $ CF.plain f) Nothing (MCFile "") SendImage chatName f -> withUser $ \user -> do chatRef <- getChatRef user chatName filePath <- toFSFilePath f - unless (any ((`isSuffixOf` map toLower f)) imageExtensions) $ throwChatError CEFileImageType {filePath} + unless (any (`isSuffixOf` map toLower f) imageExtensions) $ throwChatError CEFileImageType {filePath} fileSize <- getFileSize filePath unless (fileSize <= maxImageSize) $ throwChatError CEFileImageSize {filePath} -- TODO include file description for preview - processChatCommand . APISendMessage chatRef False Nothing $ ComposedMessage (Just f) Nothing (MCImage "" fixedImagePreview) + processChatCommand . APISendMessage chatRef False Nothing $ ComposedMessage (Just $ CF.plain f) Nothing (MCImage "" fixedImagePreview) ForwardFile chatName fileId -> forwardFile chatName fileId SendFile ForwardImage chatName fileId -> forwardFile chatName fileId SendImage SendFileDescription _chatName _f -> pure $ chatCmdError Nothing "TODO" - ReceiveFile fileId rcvInline_ filePath_ -> withUser $ \_ -> + ReceiveFile fileId encrypted rcvInline_ filePath_ -> withUser $ \_ -> withChatLock "receiveFile" . procCmd $ do - (user, ft) <- withStore $ \db -> getRcvFileTransferById db fileId - receiveFile' user ft rcvInline_ filePath_ - SetFileToReceive fileId -> withUser $ \_ -> do + (user, ft) <- withStore (`getRcvFileTransferById` fileId) + ft' <- if encrypted then encryptLocalFile ft else pure ft + receiveFile' user ft' rcvInline_ filePath_ + where + encryptLocalFile ft@RcvFileTransfer {xftpRcvFile} = case xftpRcvFile of + Nothing -> throwChatError $ CEFileInternal "locally encrypted files can't be received via SMP" + Just f -> do + cfArgs <- liftIO $ CF.randomArgs + withStore' $ \db -> setFileCryptoArgs db fileId cfArgs + pure ft {xftpRcvFile = Just ((f :: XFTPRcvFile) {cryptoArgs = Just cfArgs})} + SetFileToReceive fileId encrypted -> withUser $ \_ -> do withChatLock "setFileToReceive" . procCmd $ do - withStore' (`setRcvFileToReceive` fileId) + cfArgs <- if encrypted then fileCryptoArgs else pure Nothing + withStore' $ \db -> setRcvFileToReceive db fileId cfArgs ok_ + where + fileCryptoArgs = do + (_, RcvFileTransfer {xftpRcvFile = f}) <- withStore (`getRcvFileTransferById` fileId) + unless (isJust f) $ throwChatError $ CEFileInternal "locally encrypted files can't be received via SMP" + liftIO $ Just <$> CF.randomArgs CancelFile fileId -> withUser $ \user@User {userId} -> withChatLock "cancelFile" . procCmd $ withStore (\db -> getFileTransfer db user fileId) >>= \case @@ -1744,17 +1764,17 @@ processChatCommand = \case ResetAgentStats -> withAgent resetAgentStats >> ok_ GetAgentSubs -> summary <$> withAgent getAgentSubscriptions where - summary SubscriptionsInfo {activeSubscriptions, pendingSubscriptions} = - CRAgentSubs {activeSubs, distinctActiveSubs, pendingSubs, distinctPendingSubs} + summary SubscriptionsInfo {activeSubscriptions, pendingSubscriptions, removedSubscriptions} = + CRAgentSubs + { activeSubs = foldl' countSubs M.empty activeSubscriptions, + pendingSubs = foldl' countSubs M.empty pendingSubscriptions, + removedSubs = foldl' accSubErrors M.empty removedSubscriptions + } where - (activeSubs, distinctActiveSubs) = foldSubs activeSubscriptions - (pendingSubs, distinctPendingSubs) = foldSubs pendingSubscriptions - foldSubs :: [SubInfo] -> (Map Text Int, Map Text Int) - foldSubs = second (M.map S.size) . foldl' acc (M.empty, M.empty) - acc (m, m') SubInfo {server, rcvId} = - ( M.alter (Just . maybe 1 (+ 1)) server m, - M.alter (Just . maybe (S.singleton rcvId) (S.insert rcvId)) server m' - ) + countSubs m SubInfo {server} = M.alter (Just . maybe 1 (+ 1)) server m + accSubErrors m = \case + SubInfo {server, subError = Just e} -> M.alter (Just . maybe [e] (e :)) server m + _ -> m GetAgentSubsDetails -> CRAgentSubsDetails <$> withAgent getAgentSubscriptions where withChatLock name action = asks chatLock >>= \l -> withLock l name action @@ -1838,18 +1858,19 @@ processChatCommand = \case contactMember Contact {contactId} = find $ \GroupMember {memberContactId = cId, memberStatus = s} -> cId == Just contactId && s /= GSMemRemoved && s /= GSMemLeft - checkSndFile :: MsgContent -> FilePath -> Integer -> m (Integer, SendFileMode) - checkSndFile mc f n = do + checkSndFile :: MsgContent -> CryptoFile -> Integer -> m (Integer, SendFileMode) + checkSndFile mc (CryptoFile f cfArgs) n = do fsFilePath <- toFSFilePath f unlessM (doesFileExist fsFilePath) . throwChatError $ CEFileNotFound f ChatConfig {fileChunkSize, inlineFiles} <- asks config xftpCfg <- readTVarIO =<< asks userXFTPFileConfig - fileSize <- getFileSize fsFilePath + fileSize <- liftIO $ CF.getFileContentsSize $ CryptoFile fsFilePath cfArgs when (fromInteger fileSize > maxFileSize) $ throwChatError $ CEFileSize f - let chunks = - ((- fileSize) `div` fileChunkSize) + let chunks = -((-fileSize) `div` fileChunkSize) fileInline = inlineFileMode mc inlineFiles chunks n fileMode = case xftpCfg of Just cfg + | isJust cfArgs -> SendFileXFTP | fileInline == Just IFMSent || fileSize < minFileSize cfg || n <= 0 -> SendFileSMP fileInline | otherwise -> SendFileXFTP _ -> SendFileSMP fileInline @@ -1876,7 +1897,7 @@ processChatCommand = \case summary <- foldM (processAndCount user' logLevel) (UserProfileUpdateSummary 0 0 0 []) contacts pure $ CRUserProfileUpdated user' (fromLocalProfile p) p' summary where - processAndCount user' ll (!s@UserProfileUpdateSummary {notChanged, updateSuccesses, updateFailures, changedContacts = cts}) ct = do + processAndCount user' ll s@UserProfileUpdateSummary {notChanged, updateSuccesses, updateFailures, changedContacts = cts} ct = do let mergedProfile = userProfileToSend user Nothing $ Just ct ct' = updateMergedPreferences user' ct mergedProfile' = userProfileToSend user' Nothing $ Just ct' @@ -2223,7 +2244,7 @@ acceptFileReceive user@User {userId} RcvFileTransfer {fileId, xftpRcvFile, fileI filePath <- getRcvFilePath fileId filePath_ fName True withStoreCtx (Just "acceptFileReceive, acceptRcvFileTransfer") $ \db -> acceptRcvFileTransfer db user fileId connIds ConnJoined filePath -- XFTP - (Just _xftpRcvFile, _) -> do + (Just XFTPRcvFile {cryptoArgs}, _) -> do filePath <- getRcvFilePath fileId filePath_ fName False (ci, rfd) <- withStoreCtx (Just "acceptFileReceive, xftpAcceptRcvFT ...") $ \db -> do -- marking file as accepted and reading description in the same transaction @@ -2231,7 +2252,7 @@ acceptFileReceive user@User {userId} RcvFileTransfer {fileId, xftpRcvFile, fileI ci <- xftpAcceptRcvFT db user fileId filePath rfd <- getRcvFileDescrByFileId db fileId pure (ci, rfd) - receiveViaCompleteFD user fileId rfd + receiveViaCompleteFD user fileId rfd cryptoArgs pure ci -- group & direct file protocol _ -> do @@ -2274,11 +2295,11 @@ acceptFileReceive user@User {userId} RcvFileTransfer {fileId, xftpRcvFile, fileI || (rcvInline_ == Just True && fileSize <= fileChunkSize * offerChunks) ) -receiveViaCompleteFD :: ChatMonad m => User -> FileTransferId -> RcvFileDescr -> m () -receiveViaCompleteFD user fileId RcvFileDescr {fileDescrText, fileDescrComplete} = +receiveViaCompleteFD :: ChatMonad m => User -> FileTransferId -> RcvFileDescr -> Maybe CryptoFileArgs -> m () +receiveViaCompleteFD user fileId RcvFileDescr {fileDescrText, fileDescrComplete} cfArgs = when fileDescrComplete $ do rd <- parseFileDescription fileDescrText - aFileId <- withAgent $ \a -> xftpReceiveFile a (aUserId user) rd + aFileId <- withAgent $ \a -> xftpReceiveFile a (aUserId user) rd cfArgs startReceivingFile user fileId withStoreCtx' (Just "receiveViaCompleteFD, updateRcvFileAgentId") $ \db -> updateRcvFileAgentId db fileId (Just $ AgentRcvFileId aFileId) @@ -2545,7 +2566,7 @@ cleanupManager = do `catchChatError` (toView . CRChatError (Just user)) cleanupMessages = do ts <- liftIO getCurrentTime - let cutoffTs = addUTCTime (- (30 * nominalDay)) ts + let cutoffTs = addUTCTime (-(30 * nominalDay)) ts withStoreCtx' (Just "cleanupManager, deleteOldMessages") (`deleteOldMessages` cutoffTs) startProximateTimedItemThread :: ChatMonad m => User -> (ChatRef, ChatItemId) -> UTCTime -> m () @@ -3597,14 +3618,14 @@ processAgentMessageConn user@User {userId} corrId agentConnId agentMessage = do processFDMessage fileId fileDescr = do ft <- withStore $ \db -> getRcvFileTransfer db user fileId unless (rcvFileCompleteOrCancelled ft) $ do - (rfd, RcvFileTransfer {fileStatus}) <- withStore $ \db -> do + (rfd, RcvFileTransfer {fileStatus, xftpRcvFile}) <- withStore $ \db -> do rfd <- appendRcvFD db userId fileId fileDescr -- reading second time in the same transaction as appending description -- to prevent race condition with accept ft' <- getRcvFileTransfer db user fileId pure (rfd, ft') - case fileStatus of - RFSAccepted _ -> receiveViaCompleteFD user fileId rfd + case (fileStatus, xftpRcvFile) of + (RFSAccepted _, Just XFTPRcvFile {cryptoArgs}) -> receiveViaCompleteFD user fileId rfd cryptoArgs _ -> pure () cancelMessageFile :: Contact -> SharedMsgId -> MsgMeta -> m () @@ -3630,7 +3651,8 @@ processAgentMessageConn user@User {userId} corrId agentConnId agentMessage = do withStore' $ \db -> startRcvInlineFT db user ft fPath inline pure (Just fPath, CIFSRcvAccepted) _ -> pure (Nothing, CIFSRcvInvitation) - pure (ft, CIFile {fileId, fileName, fileSize, filePath, fileStatus, fileProtocol}) + let fileSource = CF.plain <$> filePath + pure (ft, CIFile {fileId, fileName, fileSize, fileSource, fileStatus, fileProtocol}) messageUpdate :: Contact -> SharedMsgId -> MsgContent -> RcvMessage -> MsgMeta -> Maybe Int -> Maybe Bool -> m () messageUpdate ct@Contact {contactId, localDisplayName = c} sharedMsgId mc msg@RcvMessage {msgId} msgMeta ttl live_ = do @@ -3847,7 +3869,7 @@ processAgentMessageConn user@User {userId} corrId agentConnId agentMessage = do inline <- receiveInlineMode fInv Nothing fileChunkSize 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, filePath = Nothing, fileStatus = CIFSRcvInvitation, fileProtocol} + ciFile = Just $ CIFile {fileId, fileName, fileSize, fileSource = Nothing, fileStatus = CIFSRcvInvitation, fileProtocol} ci <- saveRcvChatItem' user (CDDirectRcv ct) msg sharedMsgId_ msgMeta (CIRcvMsgContent $ MCFile "") ciFile Nothing False toView $ CRNewChatItem user (AChatItem SCTDirect SMDRcv (DirectChat ct) ci) whenContactNtfs user ct $ do @@ -3861,7 +3883,7 @@ processAgentMessageConn user@User {userId} corrId agentConnId agentMessage = do 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, filePath = Nothing, fileStatus = CIFSRcvInvitation, fileProtocol} + 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 groupMsgToView gInfo m ci msgMeta let g = groupName' gInfo @@ -4796,10 +4818,9 @@ deleteGroupCI user gInfo ci@(CChatItem msgDir deletedItem@ChatItem {file}) byUse pure $ CRChatItemDeleted user (AChatItem SCTGroup msgDir (GroupChat gInfo) deletedItem) toCi byUser timed deleteCIFile :: (ChatMonad m, MsgDirectionI d) => User -> Maybe (CIFile d) -> m () -deleteCIFile user file = - forM_ file $ \CIFile {fileId, filePath, fileStatus} -> do - let fileInfo = CIFileInfo {fileId, fileStatus = Just $ AFS msgDirection fileStatus, filePath} - fileAgentConnIds <- deleteFile' user fileInfo True +deleteCIFile user file_ = + forM_ file_ $ \file -> do + fileAgentConnIds <- deleteFile' user (mkCIFileInfo file) True deleteAgentConnectionsAsync user fileAgentConnIds markDirectCIDeleted :: ChatMonad m => User -> Contact -> CChatItem 'CTDirect -> MessageId -> Bool -> UTCTime -> m ChatResponse @@ -4823,10 +4844,9 @@ markGroupCIDeleted user gInfo@GroupInfo {groupId} ci@(CChatItem _ ChatItem {file gItem (CChatItem msgDir ci') = AChatItem SCTGroup msgDir (GroupChat gInfo) ci' cancelCIFile :: (ChatMonad m, MsgDirectionI d) => User -> Maybe (CIFile d) -> m () -cancelCIFile user file = - forM_ file $ \CIFile {fileId, filePath, fileStatus} -> do - let fileInfo = CIFileInfo {fileId, fileStatus = Just $ AFS msgDirection fileStatus, filePath} - fileAgentConnIds <- cancelFile' user fileInfo True +cancelCIFile user file_ = + forM_ file_ $ \file -> do + fileAgentConnIds <- cancelFile' user (mkCIFileInfo file) True deleteAgentConnectionsAsync user fileAgentConnIds createAgentConnectionAsync :: forall m c. (ChatMonad m, ConnectionModeI c) => User -> CommandFunction -> Bool -> SConnectionMode c -> m (CommandId, ConnId) @@ -5061,7 +5081,7 @@ withAgent :: ChatMonad m => (AgentClient -> ExceptT AgentErrorType m a) -> m a withAgent action = asks smpAgent >>= runExceptT . action - >>= liftEither . first (\e -> ChatErrorAgent e Nothing) + >>= liftEither . first (`ChatErrorAgent` Nothing) withStore' :: ChatMonad m => (DB.Connection -> IO a) -> m a withStore' action = withStore $ liftIO . action @@ -5296,8 +5316,8 @@ chatCommandP = ("/fforward " <|> "/ff ") *> (ForwardFile <$> chatNameP' <* A.space <*> A.decimal), ("/image_forward " <|> "/imgf ") *> (ForwardImage <$> chatNameP' <* A.space <*> A.decimal), ("/fdescription " <|> "/fd") *> (SendFileDescription <$> chatNameP' <* A.space <*> filePath), - ("/freceive " <|> "/fr ") *> (ReceiveFile <$> A.decimal <*> optional (" inline=" *> onOffP) <*> optional (A.space *> filePath)), - "/_set_file_to_receive " *> (SetFileToReceive <$> A.decimal), + ("/freceive " <|> "/fr ") *> (ReceiveFile <$> A.decimal <*> (" encrypt=" *> onOffP <|> pure False) <*> optional (" inline=" *> onOffP) <*> optional (A.space *> filePath)), + "/_set_file_to_receive " *> (SetFileToReceive <$> A.decimal <*> (" encrypt=" *> onOffP <|> pure False)), ("/fcancel " <|> "/fc ") *> (CancelFile <$> A.decimal), ("/fstatus " <|> "/fs ") *> (FileStatus <$> A.decimal), "/simplex" *> (ConnectSimplex <$> incognitoP), diff --git a/src/Simplex/Chat/Bot.hs b/src/Simplex/Chat/Bot.hs index 234963b44..df9c66cee 100644 --- a/src/Simplex/Chat/Bot.hs +++ b/src/Simplex/Chat/Bot.hs @@ -66,7 +66,7 @@ sendComposedMessage cc = sendComposedMessage' cc . contactId' sendComposedMessage' :: ChatController -> ContactId -> Maybe ChatItemId -> MsgContent -> IO () sendComposedMessage' cc ctId quotedItemId msgContent = do - let cm = ComposedMessage {filePath = Nothing, quotedItemId, msgContent} + let cm = ComposedMessage {fileSource = Nothing, quotedItemId, msgContent} sendChatCmd cc (APISendMessage (ChatRef CTDirect ctId) False Nothing cm) >>= \case CRNewChatItem {} -> printLog cc CLLInfo $ "sent message to contact ID " <> show ctId r -> putStrLn $ "unexpected send message response: " <> show r diff --git a/src/Simplex/Chat/Controller.hs b/src/Simplex/Chat/Controller.hs index b942256c1..6380da647 100644 --- a/src/Simplex/Chat/Controller.hs +++ b/src/Simplex/Chat/Controller.hs @@ -22,8 +22,9 @@ import Control.Monad.Except import Control.Monad.IO.Unlift import Control.Monad.Reader import Crypto.Random (ChaChaDRG) -import Data.Aeson (FromJSON (..), ToJSON (..)) +import Data.Aeson (FromJSON (..), ToJSON (..), (.:), (.:?)) import qualified Data.Aeson as J +import qualified Data.Aeson.Types as JT import qualified Data.Attoparsec.ByteString.Char8 as A import Data.ByteString.Char8 (ByteString) import qualified Data.ByteString.Char8 as B @@ -54,16 +55,18 @@ import Simplex.Messaging.Agent.Env.SQLite (AgentConfig, NetworkConfig) import Simplex.Messaging.Agent.Lock import Simplex.Messaging.Agent.Protocol import Simplex.Messaging.Agent.Store.SQLite (MigrationConfirmation, SQLiteStore, UpMigration) +import Simplex.Messaging.Agent.Store.SQLite.DB (SlowQueryStats (..)) import qualified Simplex.Messaging.Crypto as C +import Simplex.Messaging.Crypto.File (CryptoFile (..)) +import qualified Simplex.Messaging.Crypto.File as CF import Simplex.Messaging.Encoding.String import Simplex.Messaging.Notifications.Protocol (DeviceToken (..), NtfTknStatus) import Simplex.Messaging.Parsers (dropPrefix, enumJSON, parseAll, parseString, sumTypeJSON) import Simplex.Messaging.Protocol (AProtoServerWithAuth, AProtocolType, CorrId, MsgFlags, NtfServer, ProtoServerWithAuth, ProtocolTypeI, QueueId, SProtocolType, UserProtocol, XFTPServerWithAuth) import Simplex.Messaging.TMap (TMap) -import Simplex.Messaging.Agent.Store.SQLite.DB (SlowQueryStats (..)) import Simplex.Messaging.Transport (simplexMQVersion) import Simplex.Messaging.Transport.Client (TransportHost) -import Simplex.Messaging.Util (allFinally, catchAllErrors, tryAllErrors) +import Simplex.Messaging.Util (allFinally, catchAllErrors, tryAllErrors, (<$$>)) import Simplex.Messaging.Version import System.IO (Handle) import System.Mem.Weak (Weak) @@ -389,8 +392,8 @@ data ChatCommand | ForwardFile ChatName FileTransferId | ForwardImage ChatName FileTransferId | SendFileDescription ChatName FilePath - | ReceiveFile {fileId :: FileTransferId, fileInline :: Maybe Bool, filePath :: Maybe FilePath} - | SetFileToReceive FileTransferId + | ReceiveFile {fileId :: FileTransferId, storeEncrypted :: Bool, fileInline :: Maybe Bool, filePath :: Maybe FilePath} + | SetFileToReceive {fileId :: FileTransferId, storeEncrypted :: Bool} | CancelFile FileTransferId | FileStatus FileTransferId | ShowProfile -- UserId (not used in UI) @@ -572,7 +575,7 @@ data ChatResponse | CRSlowSQLQueries {chatQueries :: [SlowSQLQuery], agentQueries :: [SlowSQLQuery]} | CRDebugLocks {chatLockName :: Maybe String, agentLocks :: AgentLocks} | CRAgentStats {agentStats :: [[String]]} - | CRAgentSubs {activeSubs :: Map Text Int, distinctActiveSubs :: Map Text Int, pendingSubs :: Map Text Int, distinctPendingSubs :: Map Text Int} + | CRAgentSubs {activeSubs :: Map Text Int, pendingSubs :: Map Text Int, removedSubs :: Map Text [String]} | CRAgentSubsDetails {agentSubs :: SubscriptionsInfo} | CRConnectionDisabled {connectionEntity :: ConnectionEntity} | CRAgentRcvQueueDeleted {agentConnId :: AgentConnId, server :: SMPServer, agentQueueId :: AgentQueueId, agentError_ :: Maybe AgentErrorType} @@ -725,11 +728,24 @@ data UserProfileUpdateSummary = UserProfileUpdateSummary instance ToJSON UserProfileUpdateSummary where toEncoding = J.genericToEncoding J.defaultOptions data ComposedMessage = ComposedMessage - { filePath :: Maybe FilePath, + { fileSource :: Maybe CryptoFile, quotedItemId :: Maybe ChatItemId, msgContent :: MsgContent } - deriving (Show, Generic, FromJSON) + deriving (Show, Generic) + +-- This instance is needed for backward compatibility, can be removed in v6.0 +instance FromJSON ComposedMessage where + parseJSON (J.Object v) = do + fileSource <- + (v .:? "fileSource") >>= \case + Nothing -> CF.plain <$$> (v .:? "filePath") + f -> pure f + quotedItemId <- v .:? "quotedItemId" + msgContent <- v .: "msgContent" + pure ComposedMessage {fileSource, quotedItemId, msgContent} + parseJSON invalid = + JT.prependFailure "bad ComposedMessage, " (JT.typeMismatch "Object" invalid) instance ToJSON ComposedMessage where toJSON = J.genericToJSON J.defaultOptions {J.omitNothingFields = True} diff --git a/src/Simplex/Chat/Messages.hs b/src/Simplex/Chat/Messages.hs index 33b604184..45e5f9ff7 100644 --- a/src/Simplex/Chat/Messages.hs +++ b/src/Simplex/Chat/Messages.hs @@ -37,6 +37,8 @@ import Simplex.Chat.Protocol import Simplex.Chat.Types import Simplex.Chat.Types.Preferences import Simplex.Messaging.Agent.Protocol (AgentMsgId, MsgMeta (..), MsgReceiptStatus (..)) +import Simplex.Messaging.Crypto.File (CryptoFile (..)) +import qualified Simplex.Messaging.Crypto.File as CF import Simplex.Messaging.Encoding.String import Simplex.Messaging.Parsers (dropPrefix, enumJSON, fromTextField_, parseAll, sumTypeJSON) import Simplex.Messaging.Protocol (MsgBody) @@ -459,7 +461,7 @@ data CIFile (d :: MsgDirection) = CIFile { fileId :: Int64, fileName :: String, fileSize :: Integer, - filePath :: Maybe FilePath, -- local file path + fileSource :: Maybe CryptoFile, -- local file path with optional key and nonce fileStatus :: CIFileStatus d, fileProtocol :: FileProtocol } @@ -631,6 +633,14 @@ data CIFileInfo = CIFileInfo } deriving (Show) +mkCIFileInfo :: MsgDirectionI d => CIFile d -> CIFileInfo +mkCIFileInfo CIFile {fileId, fileStatus, fileSource} = + CIFileInfo + { fileId, + fileStatus = Just $ AFS msgDirection fileStatus, + filePath = CF.filePath <$> fileSource + } + data CIStatus (d :: MsgDirection) where CISSndNew :: CIStatus 'MDSnd CISSndSent :: SndCIStatusProgress -> CIStatus 'MDSnd diff --git a/src/Simplex/Chat/Messages/CIContent.hs b/src/Simplex/Chat/Messages/CIContent.hs index 725cf74cf..95c490a90 100644 --- a/src/Simplex/Chat/Messages/CIContent.hs +++ b/src/Simplex/Chat/Messages/CIContent.hs @@ -50,7 +50,7 @@ instance FromField AMsgDirection where fromField = fromIntField_ $ fmap fromMsgD instance ToField MsgDirection where toField = toField . msgDirectionInt -fromIntField_ :: (Typeable a) => (Int64 -> Maybe a) -> Field -> Ok a +fromIntField_ :: Typeable a => (Int64 -> Maybe a) -> Field -> Ok a fromIntField_ fromInt = \case f@(Field (SQLInteger i) _) -> case fromInt i of diff --git a/src/Simplex/Chat/Migrations/M20230827_file_encryption.hs b/src/Simplex/Chat/Migrations/M20230827_file_encryption.hs new file mode 100644 index 000000000..2e659cac8 --- /dev/null +++ b/src/Simplex/Chat/Migrations/M20230827_file_encryption.hs @@ -0,0 +1,20 @@ +{-# LANGUAGE QuasiQuotes #-} + +module Simplex.Chat.Migrations.M20230827_file_encryption where + +import Database.SQLite.Simple (Query) +import Database.SQLite.Simple.QQ (sql) + +m20230827_file_encryption :: Query +m20230827_file_encryption = + [sql| +ALTER TABLE files ADD COLUMN file_crypto_key BLOB; +ALTER TABLE files ADD COLUMN file_crypto_nonce BLOB; +|] + +down_m20230827_file_encryption :: Query +down_m20230827_file_encryption = + [sql| +ALTER TABLE files DROP COLUMN file_crypto_key; +ALTER TABLE files DROP COLUMN file_crypto_nonce; +|] diff --git a/src/Simplex/Chat/Migrations/chat_schema.sql b/src/Simplex/Chat/Migrations/chat_schema.sql index b41d7efe6..eafdc85d1 100644 --- a/src/Simplex/Chat/Migrations/chat_schema.sql +++ b/src/Simplex/Chat/Migrations/chat_schema.sql @@ -204,7 +204,9 @@ CREATE TABLE files( agent_snd_file_id BLOB NULL, private_snd_file_descr TEXT NULL, agent_snd_file_deleted INTEGER DEFAULT 0 CHECK(agent_snd_file_deleted NOT NULL), - protocol TEXT NOT NULL DEFAULT 'smp' + protocol TEXT NOT NULL DEFAULT 'smp', + file_crypto_key BLOB, + file_crypto_nonce BLOB ); CREATE TABLE snd_files( file_id INTEGER NOT NULL REFERENCES files ON DELETE CASCADE, diff --git a/src/Simplex/Chat/Mobile.hs b/src/Simplex/Chat/Mobile.hs index 6e62fbce0..0f4b262b7 100644 --- a/src/Simplex/Chat/Mobile.hs +++ b/src/Simplex/Chat/Mobile.hs @@ -35,6 +35,8 @@ import GHC.Generics (Generic) import Simplex.Chat import Simplex.Chat.Controller import Simplex.Chat.Markdown (ParsedMarkdown (..), parseMaybeMarkdownList) +import Simplex.Chat.Mobile.File +import Simplex.Chat.Mobile.Shared import Simplex.Chat.Mobile.WebRTC import Simplex.Chat.Options import Simplex.Chat.Store @@ -69,6 +71,10 @@ foreign export ccall "chat_encrypt_media" cChatEncryptMedia :: CString -> Ptr Wo foreign export ccall "chat_decrypt_media" cChatDecryptMedia :: CString -> Ptr Word8 -> CInt -> IO CString +foreign export ccall "chat_write_file" cChatWriteFile :: CString -> Ptr Word8 -> CInt -> IO CJSONString + +foreign export ccall "chat_read_file" cChatReadFile :: CString -> CString -> CString -> IO (Ptr Word8) + -- | check / migrate database and initialize chat controller on success cChatMigrateInit :: CString -> CString -> CString -> Ptr (StablePtr ChatController) -> IO CJSONString cChatMigrateInit fp key conf ctrl = do @@ -151,8 +157,6 @@ defaultMobileConfig = logLevel = CLLError } -type CJSONString = CString - getActiveUser_ :: SQLiteStore -> IO (Maybe User) getActiveUser_ st = find activeUser <$> withTransaction st getUsers diff --git a/src/Simplex/Chat/Mobile/File.hs b/src/Simplex/Chat/Mobile/File.hs new file mode 100644 index 000000000..25e694365 --- /dev/null +++ b/src/Simplex/Chat/Mobile/File.hs @@ -0,0 +1,83 @@ +{-# LANGUAGE DeriveGeneric #-} +{-# LANGUAGE OverloadedStrings #-} +{-# LANGUAGE TupleSections #-} + +module Simplex.Chat.Mobile.File + ( cChatWriteFile, + cChatReadFile, + WriteFileResult (..), + ReadFileResult (..), + chatWriteFile, + chatReadFile, + ) +where + +import Control.Monad.Except +import Data.Aeson (ToJSON) +import qualified Data.Aeson as J +import Data.ByteString (ByteString) +import qualified Data.ByteString as B +import qualified Data.ByteString.Lazy as LB +import qualified Data.ByteString.Lazy.Char8 as LB' +import Data.Word (Word8) +import Foreign.C +import Foreign.Marshal.Alloc (mallocBytes) +import Foreign.Ptr +import GHC.Generics (Generic) +import Simplex.Chat.Mobile.Shared +import Simplex.Messaging.Crypto.File (CryptoFile (..), CryptoFileArgs (..)) +import qualified Simplex.Messaging.Crypto.File as CF +import Simplex.Messaging.Encoding.String +import Simplex.Messaging.Parsers (dropPrefix, sumTypeJSON) + +data WriteFileResult + = WFResult {cryptoArgs :: CryptoFileArgs} + | WFError {writeError :: String} + deriving (Generic) + +instance ToJSON WriteFileResult where toEncoding = J.genericToEncoding . sumTypeJSON $ dropPrefix "WF" + +cChatWriteFile :: CString -> Ptr Word8 -> CInt -> IO CJSONString +cChatWriteFile cPath ptr len = do + path <- peekCAString cPath + s <- getByteString ptr len + r <- chatWriteFile path s + newCAString $ LB'.unpack $ J.encode r + +chatWriteFile :: FilePath -> ByteString -> IO WriteFileResult +chatWriteFile path s = do + cfArgs <- CF.randomArgs + let file = CryptoFile path $ Just cfArgs + either (WFError . show) (\_ -> WFResult cfArgs) + <$> runExceptT (CF.writeFile file $ LB.fromStrict s) + +data ReadFileResult + = RFResult {fileSize :: Int} + | RFError {readError :: String} + deriving (Generic) + +instance ToJSON ReadFileResult where toEncoding = J.genericToEncoding . sumTypeJSON $ dropPrefix "RF" + +cChatReadFile :: CString -> CString -> CString -> IO (Ptr Word8) +cChatReadFile cPath cKey cNonce = do + path <- peekCAString cPath + key <- B.packCString cKey + nonce <- B.packCString cNonce + (r, s) <- chatReadFile path key nonce + let r' = LB.toStrict $ J.encode r <> "\NUL" + ptr <- mallocBytes $ B.length r' + B.length s + putByteString ptr r' + unless (B.null s) $ putByteString (ptr `plusPtr` B.length r') s + pure ptr + +chatReadFile :: FilePath -> ByteString -> ByteString -> IO (ReadFileResult, ByteString) +chatReadFile path keyStr nonceStr = do + either ((,"") . RFError) result <$> runExceptT readFile_ + where + result s = let s' = LB.toStrict s in (RFResult $ B.length s', s') + readFile_ :: ExceptT String IO LB.ByteString + readFile_ = do + key <- liftEither $ strDecode keyStr + nonce <- liftEither $ strDecode nonceStr + let file = CryptoFile path $ Just $ CFArgs key nonce + withExceptT show $ CF.readFile file diff --git a/src/Simplex/Chat/Mobile/Shared.hs b/src/Simplex/Chat/Mobile/Shared.hs new file mode 100644 index 000000000..a73a25fb6 --- /dev/null +++ b/src/Simplex/Chat/Mobile/Shared.hs @@ -0,0 +1,19 @@ +module Simplex.Chat.Mobile.Shared where + +import qualified Data.ByteString as B +import Data.ByteString.Internal (ByteString (PS), memcpy) +import Foreign.C (CInt, CString) +import Foreign (Ptr, Word8, newForeignPtr_, plusPtr) +import Foreign.ForeignPtr.Unsafe + +type CJSONString = CString + +getByteString :: Ptr Word8 -> CInt -> IO ByteString +getByteString ptr len = do + fp <- newForeignPtr_ ptr + pure $ PS fp 0 $ fromIntegral len + +putByteString :: Ptr Word8 -> ByteString -> IO () +putByteString ptr bs@(PS fp offset _) = do + let p = unsafeForeignPtrToPtr fp `plusPtr` offset + memcpy ptr p $ B.length bs diff --git a/src/Simplex/Chat/Mobile/WebRTC.hs b/src/Simplex/Chat/Mobile/WebRTC.hs index e05c9d609..19ba2b751 100644 --- a/src/Simplex/Chat/Mobile/WebRTC.hs +++ b/src/Simplex/Chat/Mobile/WebRTC.hs @@ -12,16 +12,15 @@ import Control.Monad.Except import qualified Crypto.Cipher.Types as AES import Data.Bifunctor (bimap) import qualified Data.ByteArray as BA +import Data.ByteString (ByteString) import qualified Data.ByteString as B import qualified Data.ByteString.Base64.URL as U -import Data.ByteString.Internal (ByteString (PS), memcpy) import Data.Either (fromLeft) import Data.Word (Word8) import Foreign.C (CInt, CString, newCAString) -import Foreign.ForeignPtr (newForeignPtr_) -import Foreign.ForeignPtr.Unsafe (unsafeForeignPtrToPtr) -import Foreign.Ptr (Ptr, plusPtr) +import Foreign.Ptr (Ptr) import qualified Simplex.Messaging.Crypto as C +import Simplex.Chat.Mobile.Shared cChatEncryptMedia :: CString -> Ptr Word8 -> CInt -> IO CString cChatEncryptMedia = cTransformMedia chatEncryptMedia @@ -32,16 +31,10 @@ cChatDecryptMedia = cTransformMedia chatDecryptMedia cTransformMedia :: (ByteString -> ByteString -> ExceptT String IO ByteString) -> CString -> Ptr Word8 -> CInt -> IO CString cTransformMedia f cKey cFrame cFrameLen = do key <- B.packCString cKey - frame <- getFrame + frame <- getByteString cFrame cFrameLen runExceptT (f key frame >>= liftIO . putFrame) >>= newCAString . fromLeft "" where - getFrame = do - fp <- newForeignPtr_ cFrame - pure $ PS fp 0 $ fromIntegral cFrameLen - putFrame bs@(PS fp offset _) = do - let len = B.length bs - p = unsafeForeignPtrToPtr fp `plusPtr` offset - when (len <= fromIntegral cFrameLen) $ memcpy cFrame p len + putFrame s = when (B.length s <= fromIntegral cFrameLen) $ putByteString cFrame s {-# INLINE cTransformMedia #-} chatEncryptMedia :: ByteString -> ByteString -> ExceptT String IO ByteString diff --git a/src/Simplex/Chat/Store/Files.hs b/src/Simplex/Chat/Store/Files.hs index 4c370d15e..fa085908e 100644 --- a/src/Simplex/Chat/Store/Files.hs +++ b/src/Simplex/Chat/Store/Files.hs @@ -56,6 +56,7 @@ module Simplex.Chat.Store.Files startRcvInlineFT, xftpAcceptRcvFT, setRcvFileToReceive, + setFileCryptoArgs, getRcvFilesToReceive, setRcvFTAgentDeleted, updateRcvFileStatus, @@ -84,18 +85,21 @@ import Data.Time.Clock (UTCTime (..), getCurrentTime, nominalDay) import Data.Type.Equality import Database.SQLite.Simple (Only (..), (:.) (..)) import Database.SQLite.Simple.QQ (sql) +import Simplex.Chat.Messages +import Simplex.Chat.Messages.CIContent +import Simplex.Chat.Protocol import Simplex.Chat.Store.Direct import Simplex.Chat.Store.Messages import Simplex.Chat.Store.Profiles import Simplex.Chat.Store.Shared -import Simplex.Chat.Messages -import Simplex.Chat.Messages.CIContent -import Simplex.Chat.Protocol import Simplex.Chat.Types import Simplex.Chat.Util (week) import Simplex.Messaging.Agent.Protocol (AgentMsgId, ConnId, UserId) import Simplex.Messaging.Agent.Store.SQLite (firstRow, maybeFirstRow) import qualified Simplex.Messaging.Agent.Store.SQLite.DB as DB +import qualified Simplex.Messaging.Crypto as C +import Simplex.Messaging.Crypto.File (CryptoFile (..), CryptoFileArgs (..)) +import qualified Simplex.Messaging.Crypto.File as CF getLiveSndFileTransfers :: DB.Connection -> User -> IO [SndFileTransfer] getLiveSndFileTransfers db User {userId} = do @@ -257,14 +261,14 @@ getSndFTViaMsgDelivery db User {userId} Connection {connId, agentConnId} agentMs (\n -> SndFileTransfer {fileId, fileStatus, fileName, fileSize, chunkSize, filePath, fileDescrId, fileInline, groupMemberId, recipientDisplayName = n, connId, agentConnId}) <$> (contactName_ <|> memberName_) -createSndFileTransferXFTP :: DB.Connection -> User -> ContactOrGroup -> FilePath -> FileInvitation -> AgentSndFileId -> Integer -> IO FileTransferMeta -createSndFileTransferXFTP db User {userId} contactOrGroup filePath FileInvitation {fileName, fileSize} agentSndFileId chunkSize = do +createSndFileTransferXFTP :: DB.Connection -> User -> ContactOrGroup -> CryptoFile -> FileInvitation -> AgentSndFileId -> Integer -> IO FileTransferMeta +createSndFileTransferXFTP db User {userId} contactOrGroup (CryptoFile filePath cryptoArgs) FileInvitation {fileName, fileSize} agentSndFileId chunkSize = do currentTs <- getCurrentTime - let xftpSndFile = Just XFTPSndFile {agentSndFileId, privateSndFileDescr = Nothing, agentSndFileDeleted = False} + let xftpSndFile = Just XFTPSndFile {agentSndFileId, privateSndFileDescr = Nothing, agentSndFileDeleted = False, cryptoArgs} DB.execute db - "INSERT INTO files (contact_id, group_id, user_id, file_name, file_path, file_size, chunk_size, agent_snd_file_id, ci_file_status, protocol, created_at, updated_at) VALUES (?,?,?,?,?,?,?,?,?,?,?,?)" - (contactAndGroupIds contactOrGroup :. (userId, fileName, filePath, fileSize, chunkSize, agentSndFileId, CIFSSndStored, FPXFTP, currentTs, currentTs)) + "INSERT INTO files (contact_id, group_id, user_id, file_name, file_path, file_crypto_key, file_crypto_nonce, file_size, chunk_size, agent_snd_file_id, ci_file_status, protocol, created_at, updated_at) VALUES (?,?,?,?,?,?,?,?,?,?,?,?,?,?)" + (contactAndGroupIds contactOrGroup :. (userId, fileName, filePath, CF.fileKey <$> cryptoArgs, CF.fileNonce <$> cryptoArgs, fileSize, chunkSize, agentSndFileId, CIFSSndStored, FPXFTP, currentTs, currentTs)) fileId <- insertedRowId db pure FileTransferMeta {fileId, xftpSndFile, fileName, filePath, fileSize, fileInline = Nothing, chunkSize, cancelled = False} @@ -479,7 +483,8 @@ createRcvFileTransfer db userId Contact {contactId, localDisplayName = c} f@File currentTs <- liftIO getCurrentTime rfd_ <- mapM (createRcvFD_ db userId currentTs) fileDescr let rfdId = (fileDescrId :: RcvFileDescr -> Int64) <$> rfd_ - xftpRcvFile = (\rfd -> XFTPRcvFile {rcvFileDescription = rfd, agentRcvFileId = Nothing, agentRcvFileDeleted = False}) <$> rfd_ + -- cryptoArgs = Nothing here, the decision to encrypt is made when receiving it + xftpRcvFile = (\rfd -> XFTPRcvFile {rcvFileDescription = rfd, agentRcvFileId = Nothing, agentRcvFileDeleted = False, cryptoArgs = Nothing}) <$> rfd_ fileProtocol = if isJust rfd_ then FPXFTP else FPSMP fileId <- liftIO $ do DB.execute @@ -499,7 +504,8 @@ createRcvGroupFileTransfer db userId GroupMember {groupId, groupMemberId, localD currentTs <- liftIO getCurrentTime rfd_ <- mapM (createRcvFD_ db userId currentTs) fileDescr let rfdId = (fileDescrId :: RcvFileDescr -> Int64) <$> rfd_ - xftpRcvFile = (\rfd -> XFTPRcvFile {rcvFileDescription = rfd, agentRcvFileId = Nothing, agentRcvFileDeleted = False}) <$> rfd_ + -- cryptoArgs = Nothing here, the decision to encrypt is made when receiving it + xftpRcvFile = (\rfd -> XFTPRcvFile {rcvFileDescription = rfd, agentRcvFileId = Nothing, agentRcvFileDeleted = False, cryptoArgs = Nothing}) <$> rfd_ fileProtocol = if isJust rfd_ then FPXFTP else FPSMP fileId <- liftIO $ do DB.execute @@ -600,7 +606,7 @@ getRcvFileTransfer db User {userId} fileId = do [sql| SELECT r.file_status, r.file_queue_info, r.group_member_id, f.file_name, f.file_size, f.chunk_size, f.cancelled, cs.local_display_name, m.local_display_name, - f.file_path, r.file_inline, r.rcv_file_inline, r.agent_rcv_file_id, r.agent_rcv_file_deleted, c.connection_id, c.agent_conn_id + f.file_path, f.file_crypto_key, f.file_crypto_nonce, r.file_inline, r.rcv_file_inline, r.agent_rcv_file_id, r.agent_rcv_file_deleted, c.connection_id, c.agent_conn_id FROM rcv_files r JOIN files f USING (file_id) LEFT JOIN connections c ON r.file_id = c.rcv_file_id @@ -614,9 +620,9 @@ getRcvFileTransfer db User {userId} fileId = do where rcvFileTransfer :: Maybe RcvFileDescr -> - (FileStatus, Maybe ConnReqInvitation, Maybe Int64, String, Integer, Integer, Maybe Bool) :. (Maybe ContactName, Maybe ContactName, Maybe FilePath, Maybe InlineFileMode, Maybe InlineFileMode, Maybe AgentRcvFileId, Bool) :. (Maybe Int64, Maybe AgentConnId) -> + (FileStatus, Maybe ConnReqInvitation, Maybe Int64, String, Integer, Integer, Maybe Bool) :. (Maybe ContactName, Maybe ContactName, Maybe FilePath, Maybe C.SbKey, Maybe C.CbNonce, Maybe InlineFileMode, Maybe InlineFileMode, Maybe AgentRcvFileId, Bool) :. (Maybe Int64, Maybe AgentConnId) -> ExceptT StoreError IO RcvFileTransfer - rcvFileTransfer rfd_ ((fileStatus', fileConnReq, grpMemberId, fileName, fileSize, chunkSize, cancelled_) :. (contactName_, memberName_, filePath_, fileInline, rcvFileInline, agentRcvFileId, agentRcvFileDeleted) :. (connId_, agentConnId_)) = + rcvFileTransfer rfd_ ((fileStatus', fileConnReq, grpMemberId, fileName, fileSize, chunkSize, cancelled_) :. (contactName_, memberName_, filePath_, fileKey, fileNonce, fileInline, rcvFileInline, agentRcvFileId, agentRcvFileDeleted) :. (connId_, agentConnId_)) = case contactName_ <|> memberName_ of Nothing -> throwError $ SERcvFileInvalid fileId Just name -> do @@ -629,7 +635,8 @@ getRcvFileTransfer db User {userId} fileId = do where ft senderDisplayName fileStatus = let fileInvitation = FileInvitation {fileName, fileSize, fileDigest = Nothing, fileConnReq, fileInline, fileDescr = Nothing} - xftpRcvFile = (\rfd -> XFTPRcvFile {rcvFileDescription = rfd, agentRcvFileId, agentRcvFileDeleted}) <$> rfd_ + cryptoArgs = CFArgs <$> fileKey <*> fileNonce + xftpRcvFile = (\rfd -> XFTPRcvFile {rcvFileDescription = rfd, agentRcvFileId, agentRcvFileDeleted, cryptoArgs}) <$> rfd_ in RcvFileTransfer {fileId, xftpRcvFile, fileInvitation, fileStatus, rcvFileInline, senderDisplayName, chunkSize, cancelled, grpMemberId} rfi = maybe (throwError $ SERcvFileInvalid fileId) pure =<< rfi_ rfi_ = case (filePath_, connId_, agentConnId_) of @@ -683,13 +690,21 @@ acceptRcvFT_ db User {userId} fileId filePath rcvFileInline currentTs = do "UPDATE rcv_files SET rcv_file_inline = ?, file_status = ?, updated_at = ? WHERE file_id = ?" (rcvFileInline, FSAccepted, currentTs, fileId) -setRcvFileToReceive :: DB.Connection -> FileTransferId -> IO () -setRcvFileToReceive db fileId = do +setRcvFileToReceive :: DB.Connection -> FileTransferId -> Maybe CryptoFileArgs -> IO () +setRcvFileToReceive db fileId cfArgs_ = do currentTs <- getCurrentTime + DB.execute db "UPDATE rcv_files SET to_receive = 1, updated_at = ? WHERE file_id = ?" (currentTs, fileId) + forM_ cfArgs_ $ \cfArgs -> setFileCryptoArgs_ db fileId cfArgs currentTs + +setFileCryptoArgs :: DB.Connection -> FileTransferId -> CryptoFileArgs -> IO () +setFileCryptoArgs db fileId cfArgs = setFileCryptoArgs_ db fileId cfArgs =<< getCurrentTime + +setFileCryptoArgs_ :: DB.Connection -> FileTransferId -> CryptoFileArgs -> UTCTime -> IO () +setFileCryptoArgs_ db fileId (CFArgs key nonce) currentTs = DB.execute db - "UPDATE rcv_files SET to_receive = 1, updated_at = ? WHERE file_id = ?" - (currentTs, fileId) + "UPDATE files SET file_crypto_key = ?, file_crypto_nonce = ?, updated_at = ? WHERE file_id = ?" + (key, nonce, currentTs, fileId) getRcvFilesToReceive :: DB.Connection -> User -> IO [RcvFileTransfer] getRcvFilesToReceive db user@User {userId} = do @@ -842,15 +857,16 @@ getFileTransferMeta db User {userId} fileId = DB.query db [sql| - SELECT file_name, file_size, chunk_size, file_path, file_inline, agent_snd_file_id, agent_snd_file_deleted, private_snd_file_descr, cancelled + SELECT file_name, file_size, chunk_size, file_path, file_crypto_key, file_crypto_nonce, file_inline, agent_snd_file_id, agent_snd_file_deleted, private_snd_file_descr, cancelled FROM files WHERE user_id = ? AND file_id = ? |] (userId, fileId) where - fileTransferMeta :: (String, Integer, Integer, FilePath, Maybe InlineFileMode, Maybe AgentSndFileId, Bool, Maybe Text, Maybe Bool) -> FileTransferMeta - fileTransferMeta (fileName, fileSize, chunkSize, filePath, fileInline, aSndFileId_, agentSndFileDeleted, privateSndFileDescr, cancelled_) = - let xftpSndFile = (\fId -> XFTPSndFile {agentSndFileId = fId, privateSndFileDescr, agentSndFileDeleted}) <$> aSndFileId_ + fileTransferMeta :: (String, Integer, Integer, FilePath, Maybe C.SbKey, Maybe C.CbNonce, Maybe InlineFileMode, Maybe AgentSndFileId, Bool, Maybe Text, Maybe Bool) -> FileTransferMeta + fileTransferMeta (fileName, fileSize, chunkSize, filePath, fileKey, fileNonce, fileInline, aSndFileId_, agentSndFileDeleted, privateSndFileDescr, cancelled_) = + let cryptoArgs = CFArgs <$> fileKey <*> fileNonce + xftpSndFile = (\fId -> XFTPSndFile {agentSndFileId = fId, privateSndFileDescr, agentSndFileDeleted, cryptoArgs}) <$> aSndFileId_ in FileTransferMeta {fileId, xftpSndFile, fileName, fileSize, chunkSize, filePath, fileInline, cancelled = fromMaybe False cancelled_} getContactFileInfo :: DB.Connection -> User -> Contact -> IO [CIFileInfo] diff --git a/src/Simplex/Chat/Store/Messages.hs b/src/Simplex/Chat/Store/Messages.hs index 5f760add1..12d1b6525 100644 --- a/src/Simplex/Chat/Store/Messages.hs +++ b/src/Simplex/Chat/Store/Messages.hs @@ -13,7 +13,6 @@ module Simplex.Chat.Store.Messages ( getContactConnIds_, getDirectChatReactions_, - toDirectChatItem, -- * Message and chat item functions deleteContactCIs, @@ -122,6 +121,8 @@ import Simplex.Chat.Types import Simplex.Messaging.Agent.Protocol (AgentMsgId, ConnId, MsgMeta (..), UserId) import Simplex.Messaging.Agent.Store.SQLite (firstRow, firstRow', maybeFirstRow) import qualified Simplex.Messaging.Agent.Store.SQLite.DB as DB +import qualified Simplex.Messaging.Crypto as C +import Simplex.Messaging.Crypto.File (CryptoFile (..), CryptoFileArgs (..)) import Simplex.Messaging.Util (eitherToMaybe) import UnliftIO.STM @@ -484,7 +485,7 @@ getDirectChatPreviews_ db user@User {userId} = do -- ChatItem 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.ci_file_status, f.protocol, + 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, -- DirectQuote ri.chat_item_id, i.quoted_shared_msg_id, i.quoted_sent_at, i.quoted_content, i.quoted_sent FROM contacts ct @@ -549,7 +550,7 @@ getGroupChatPreviews_ db User {userId, userContactId} = do -- ChatItem 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.ci_file_status, f.protocol, + 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, -- Maybe GroupMember - sender m.group_member_id, m.group_id, m.member_id, m.member_role, m.member_category, m.member_status, m.invited_by, m.local_display_name, m.contact_id, m.contact_profile_id, p.contact_profile_id, @@ -671,7 +672,7 @@ getDirectChatItemsLast db User {userId} contactId count search = ExceptT $ do -- ChatItem 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.ci_file_status, f.protocol, + 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, -- DirectQuote ri.chat_item_id, i.quoted_shared_msg_id, i.quoted_sent_at, i.quoted_content, i.quoted_sent FROM chat_items i @@ -700,7 +701,7 @@ getDirectChatAfter_ db User {userId} ct@Contact {contactId} afterChatItemId coun -- ChatItem 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.ci_file_status, f.protocol, + 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, -- DirectQuote ri.chat_item_id, i.quoted_shared_msg_id, i.quoted_sent_at, i.quoted_content, i.quoted_sent FROM chat_items i @@ -730,7 +731,7 @@ getDirectChatBefore_ db User {userId} ct@Contact {contactId} beforeChatItemId co -- ChatItem 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.ci_file_status, f.protocol, + 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, -- DirectQuote ri.chat_item_id, i.quoted_shared_msg_id, i.quoted_sent_at, i.quoted_content, i.quoted_sent FROM chat_items i @@ -952,7 +953,7 @@ type ChatStatsRow = (Int, ChatItemId, Bool) toChatStats :: ChatStatsRow -> ChatStats toChatStats (unreadCount, minUnreadItemId, unreadChat) = ChatStats {unreadCount, minUnreadItemId, unreadChat} -type MaybeCIFIleRow = (Maybe Int64, Maybe String, Maybe Integer, Maybe FilePath, Maybe ACIFileStatus, Maybe FileProtocol) +type MaybeCIFIleRow = (Maybe Int64, Maybe String, Maybe Integer, Maybe FilePath, Maybe C.SbKey, Maybe C.CbNonce, Maybe ACIFileStatus, Maybe FileProtocol) type ChatItemModeRow = (Maybe Int, Maybe UTCTime, Maybe Bool) @@ -973,7 +974,7 @@ toQuote (quotedItemId, quotedSharedMsgId, quotedSentAt, quotedMsgContent, _) dir -- this function can be changed so it never fails, not only avoid failure on invalid json toDirectChatItem :: UTCTime -> ChatItemRow :. QuoteRow -> Either StoreError (CChatItem 'CTDirect) -toDirectChatItem currentTs (((itemId, itemTs, AMsgDirection msgDir, itemContentText, itemText, itemStatus, sharedMsgId) :. (itemDeleted, deletedTs, itemEdited, createdAt, updatedAt) :. (timedTTL, timedDeleteAt, itemLive) :. (fileId_, fileName_, fileSize_, filePath, fileStatus_, fileProtocol_)) :. quoteRow) = +toDirectChatItem currentTs (((itemId, itemTs, AMsgDirection msgDir, itemContentText, itemText, itemStatus, sharedMsgId) :. (itemDeleted, deletedTs, itemEdited, createdAt, updatedAt) :. (timedTTL, timedDeleteAt, itemLive) :. (fileId_, fileName_, fileSize_, filePath, fileKey, fileNonce, fileStatus_, fileProtocol_)) :. quoteRow) = chatItem $ fromRight invalid $ dbParseACIContent itemContentText where invalid = ACIContent msgDir $ CIInvalidJSON itemContentText @@ -990,7 +991,10 @@ toDirectChatItem currentTs (((itemId, itemTs, AMsgDirection msgDir, itemContentT maybeCIFile :: CIFileStatus d -> Maybe (CIFile d) maybeCIFile fileStatus = case (fileId_, fileName_, fileSize_, fileProtocol_) of - (Just fileId, Just fileName, Just fileSize, Just fileProtocol) -> Just CIFile {fileId, fileName, fileSize, filePath, fileStatus, fileProtocol} + (Just fileId, Just fileName, Just fileSize, Just fileProtocol) -> + let cfArgs = CFArgs <$> fileKey <*> fileNonce + fileSource = (`CryptoFile` cfArgs) <$> filePath + in Just CIFile {fileId, fileName, fileSize, fileSource, fileStatus, fileProtocol} _ -> Nothing cItem :: MsgDirectionI d => SMsgDirection d -> CIDirection 'CTDirect d -> CIStatus d -> CIContent d -> Maybe (CIFile d) -> CChatItem 'CTDirect cItem d chatDir ciStatus content file = @@ -1023,7 +1027,7 @@ toGroupQuote qr@(_, _, _, _, quotedSent) quotedMember_ = toQuote qr $ direction -- 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, fileStatus_, fileProtocol_)) :. memberRow_ :. (quoteRow :. quotedMemberRow_) :. deletedByGroupMemberRow_) = do +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 chatItem $ fromRight invalid $ dbParseACIContent itemContentText where member_ = toMaybeGroupMember userContactId memberRow_ @@ -1043,7 +1047,10 @@ toGroupChatItem currentTs userContactId (((itemId, itemTs, AMsgDirection msgDir, maybeCIFile :: CIFileStatus d -> Maybe (CIFile d) maybeCIFile fileStatus = case (fileId_, fileName_, fileSize_, fileProtocol_) of - (Just fileId, Just fileName, Just fileSize, Just fileProtocol) -> Just CIFile {fileId, fileName, fileSize, filePath, fileStatus, fileProtocol} + (Just fileId, Just fileName, Just fileSize, Just fileProtocol) -> + let cfArgs = CFArgs <$> fileKey <*> fileNonce + fileSource = (`CryptoFile` cfArgs) <$> filePath + in Just CIFile {fileId, fileName, fileSize, fileSource, fileStatus, fileProtocol} _ -> Nothing cItem :: MsgDirectionI d => SMsgDirection d -> CIDirection 'CTGroup d -> CIStatus d -> CIContent d -> Maybe (CIFile d) -> CChatItem 'CTGroup cItem d chatDir ciStatus content file = @@ -1143,7 +1150,7 @@ updateDirectChatItemStatus db user@User {userId} contactId itemId itemStatus = d correctDir :: CChatItem c -> Either StoreError (ChatItem c d) correctDir (CChatItem _ ci) = first SEInternalError $ checkDirection ci -updateDirectChatItem :: forall d. (MsgDirectionI d) => DB.Connection -> User -> Int64 -> ChatItemId -> CIContent d -> Bool -> Maybe MessageId -> ExceptT StoreError IO (ChatItem 'CTDirect d) +updateDirectChatItem :: forall d. MsgDirectionI d => DB.Connection -> User -> Int64 -> ChatItemId -> CIContent d -> Bool -> Maybe MessageId -> ExceptT StoreError IO (ChatItem 'CTDirect d) updateDirectChatItem db user contactId itemId newContent live msgId_ = do ci <- liftEither . correctDir =<< getDirectChatItem db user contactId itemId liftIO $ updateDirectChatItem' db user contactId ci newContent live msgId_ @@ -1151,7 +1158,7 @@ updateDirectChatItem db user contactId itemId newContent live msgId_ = do correctDir :: CChatItem c -> Either StoreError (ChatItem c d) correctDir (CChatItem _ ci) = first SEInternalError $ checkDirection ci -updateDirectChatItem' :: forall d. (MsgDirectionI d) => DB.Connection -> User -> Int64 -> ChatItem 'CTDirect d -> CIContent d -> Bool -> Maybe MessageId -> IO (ChatItem 'CTDirect d) +updateDirectChatItem' :: forall d. MsgDirectionI d => DB.Connection -> User -> Int64 -> ChatItem 'CTDirect d -> CIContent d -> Bool -> Maybe MessageId -> IO (ChatItem 'CTDirect d) updateDirectChatItem' db User {userId} contactId ci newContent live msgId_ = do currentTs <- liftIO getCurrentTime let ci' = updatedChatItem ci newContent live currentTs @@ -1296,7 +1303,7 @@ getDirectChatItem db User {userId} contactId itemId = ExceptT $ do -- ChatItem 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.ci_file_status, f.protocol, + 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, -- DirectQuote ri.chat_item_id, i.quoted_shared_msg_id, i.quoted_sent_at, i.quoted_content, i.quoted_sent FROM chat_items i @@ -1471,7 +1478,7 @@ getGroupChatItem db User {userId, userContactId} groupId itemId = ExceptT $ do -- ChatItem 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.ci_file_status, f.protocol, + 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, -- GroupMember m.group_member_id, m.group_id, m.member_id, m.member_role, m.member_category, m.member_status, m.invited_by, m.local_display_name, m.contact_id, m.contact_profile_id, p.contact_profile_id, diff --git a/src/Simplex/Chat/Store/Migrations.hs b/src/Simplex/Chat/Store/Migrations.hs index 3bd9bf4f7..b763f9a54 100644 --- a/src/Simplex/Chat/Store/Migrations.hs +++ b/src/Simplex/Chat/Store/Migrations.hs @@ -76,6 +76,7 @@ import Simplex.Chat.Migrations.M20230621_chat_item_moderations import Simplex.Chat.Migrations.M20230705_delivery_receipts import Simplex.Chat.Migrations.M20230721_group_snd_item_statuses import Simplex.Chat.Migrations.M20230814_indexes +import Simplex.Chat.Migrations.M20230827_file_encryption import Simplex.Chat.Migrations.M20230829_connections_chat_vrange import Simplex.Messaging.Agent.Store.SQLite.Migrations (Migration (..)) @@ -153,6 +154,7 @@ schemaMigrations = ("20230705_delivery_receipts", m20230705_delivery_receipts, Just down_m20230705_delivery_receipts), ("20230721_group_snd_item_statuses", m20230721_group_snd_item_statuses, Just down_m20230721_group_snd_item_statuses), ("20230814_indexes", m20230814_indexes, Just down_m20230814_indexes), + ("20230827_file_encryption", m20230827_file_encryption, Just down_m20230827_file_encryption), ("20230829_connections_chat_vrange", m20230829_connections_chat_vrange, Just down_m20230829_connections_chat_vrange) ] diff --git a/src/Simplex/Chat/Types.hs b/src/Simplex/Chat/Types.hs index 4981b225b..ee5899db2 100644 --- a/src/Simplex/Chat/Types.hs +++ b/src/Simplex/Chat/Types.hs @@ -42,6 +42,7 @@ import Simplex.Chat.Types.Preferences import Simplex.Chat.Types.Util import Simplex.FileTransfer.Description (FileDigest) import Simplex.Messaging.Agent.Protocol (ACommandTag (..), ACorrId, AParty (..), APartyCmdTag (..), ConnId, ConnectionMode (..), ConnectionRequestUri, InvitationId, SAEntity (..), UserId) +import Simplex.Messaging.Crypto.File (CryptoFileArgs (..)) import Simplex.Messaging.Encoding.String import Simplex.Messaging.Parsers (dropPrefix, fromTextField_, sumTypeJSON, taggedObjectJSON) import Simplex.Messaging.Protocol (ProtoServerWithAuth, ProtocolTypeI) @@ -963,7 +964,8 @@ instance ToJSON RcvFileTransfer where toEncoding = J.genericToEncoding J.default data XFTPRcvFile = XFTPRcvFile { rcvFileDescription :: RcvFileDescr, agentRcvFileId :: Maybe AgentRcvFileId, - agentRcvFileDeleted :: Bool + agentRcvFileDeleted :: Bool, + cryptoArgs :: Maybe CryptoFileArgs } deriving (Eq, Show, Generic) @@ -1118,7 +1120,8 @@ instance ToJSON FileTransferMeta where toEncoding = J.genericToEncoding J.defaul data XFTPSndFile = XFTPSndFile { agentSndFileId :: AgentSndFileId, privateSndFileDescr :: Maybe Text, - agentSndFileDeleted :: Bool + agentSndFileDeleted :: Bool, + cryptoArgs :: Maybe CryptoFileArgs } deriving (Eq, Show, Generic) diff --git a/src/Simplex/Chat/View.hs b/src/Simplex/Chat/View.hs index 2f512a9b7..57f508c71 100644 --- a/src/Simplex/Chat/View.hs +++ b/src/Simplex/Chat/View.hs @@ -20,6 +20,7 @@ import Data.Int (Int64) import Data.List (groupBy, intercalate, intersperse, partition, sortOn) import Data.List.NonEmpty (NonEmpty) import qualified Data.List.NonEmpty as L +import Data.Map.Strict (Map) import qualified Data.Map.Strict as M import Data.Maybe (fromMaybe, isJust, isNothing, mapMaybe) import Data.Text (Text) @@ -50,6 +51,7 @@ import Simplex.Messaging.Agent.Env.SQLite (NetworkConfig (..)) import Simplex.Messaging.Agent.Protocol import Simplex.Messaging.Agent.Store.SQLite.DB (SlowQueryStats (..)) import qualified Simplex.Messaging.Crypto as C +import Simplex.Messaging.Crypto.File (CryptoFile (..), CryptoFileArgs (..)) import Simplex.Messaging.Encoding import Simplex.Messaging.Encoding.String import Simplex.Messaging.Parsers (dropPrefix, taggedObjectJSON) @@ -161,7 +163,7 @@ responseToView user_ ChatConfig {logLevel, showReactions, showReceipts, testView CRRcvFileDescrReady _ _ -> [] CRRcvFileDescrNotReady _ _ -> [] CRRcvFileProgressXFTP {} -> [] - CRRcvFileAccepted u ci -> ttyUser u $ savingFile' ci + CRRcvFileAccepted u ci -> ttyUser u $ savingFile' testView ci CRRcvFileAcceptedSndCancelled u ft -> ttyUser u $ viewRcvFileSndCancelled ft CRSndFileCancelled u _ ftm fts -> ttyUser u $ viewSndFileCancelled ftm fts CRRcvFileCancelled u _ ft -> ttyUser u $ receivingFile_ "cancelled" ft @@ -252,7 +254,7 @@ responseToView user_ ChatConfig {logLevel, showReactions, showReceipts, testView CRSQLResult rows -> map plain rows CRSlowSQLQueries {chatQueries, agentQueries} -> let viewQuery SlowSQLQuery {query, queryStats = SlowQueryStats {count, timeMax, timeAvg}} = - "count: " <> sShow count + ("count: " <> sShow count) <> (" :: max: " <> sShow timeMax <> " ms") <> (" :: avg: " <> sShow timeAvg <> " ms") <> (" :: " <> plain (T.unwords $ T.lines query)) @@ -262,20 +264,21 @@ responseToView user_ ChatConfig {logLevel, showReactions, showReceipts, testView plain $ "agent locks: " <> LB.unpack (J.encode agentLocks) ] CRAgentStats stats -> map (plain . intercalate ",") stats - CRAgentSubs {activeSubs, distinctActiveSubs, pendingSubs, distinctPendingSubs} -> - [plain $ "Subscriptions: active = " <> show (sum activeSubs) <> ", distinct active = " <> show (sum distinctActiveSubs) <> ", pending = " <> show (sum pendingSubs) <> ", distinct pending = " <> show (sum distinctPendingSubs)] + CRAgentSubs {activeSubs, pendingSubs, removedSubs} -> + [plain $ "Subscriptions: active = " <> show (sum activeSubs) <> ", pending = " <> show (sum pendingSubs) <> ", removed = " <> show (sum $ M.map length removedSubs)] <> ("active subscriptions:" : listSubs activeSubs) - <> ("distinct active subscriptions:" : listSubs distinctActiveSubs) <> ("pending subscriptions:" : listSubs pendingSubs) - <> ("distinct pending subscriptions:" : listSubs distinctPendingSubs) + <> ("removed subscriptions:" : listSubs removedSubs) where - listSubs = map (\(srv, count) -> plain $ srv <> ": " <> tshow count) . M.assocs - CRAgentSubsDetails SubscriptionsInfo {activeSubscriptions, pendingSubscriptions} -> + listSubs :: Show a => Map Text a -> [StyledString] + listSubs = map (\(srv, info) -> plain $ srv <> ": " <> tshow info) . M.assocs + CRAgentSubsDetails SubscriptionsInfo {activeSubscriptions, pendingSubscriptions, removedSubscriptions} -> ("active subscriptions:" : map sShow activeSubscriptions) <> ("pending subscriptions: " : map sShow pendingSubscriptions) + <> ("removed subscriptions: " : map sShow removedSubscriptions) CRConnectionDisabled entity -> viewConnectionEntityDisabled entity CRAgentRcvQueueDeleted acId srv aqId err_ -> - [ "completed deleting rcv queue, agent connection id: " <> sShow acId + [ ("completed deleting rcv queue, agent connection id: " <> sShow acId) <> (", server: " <> sShow srv) <> (", agent queue id: " <> sShow aqId) <> maybe "" (\e -> ", error: " <> sShow e) err_ @@ -328,7 +331,7 @@ responseToView user_ ChatConfig {logLevel, showReactions, showReceipts, testView Just CIQuote {chatDir = quoteDir, content} -> Just (msgDirectionInt $ quoteMsgDirection quoteDir, msgContentText content) fPath = case file of - Just CIFile {filePath = Just fp} -> Just fp + Just CIFile {fileSource = Just (CryptoFile fp _)} -> Just fp _ -> Nothing testViewItem :: CChatItem c -> Maybe GroupMember -> Text testViewItem (CChatItem _ ci@ChatItem {meta = CIMeta {itemText}}) membership_ = @@ -951,7 +954,8 @@ viewNetworkConfig NetworkConfig {socksProxy, tcpTimeout} = viewContactInfo :: Contact -> ConnectionStats -> Maybe Profile -> [StyledString] viewContactInfo ct@Contact {contactId, profile = LocalProfile {localAlias, contactLink}, activeConn} stats incognitoProfile = - ["contact ID: " <> sShow contactId] <> viewConnectionStats stats + ["contact ID: " <> sShow contactId] + <> viewConnectionStats stats <> maybe [] (\l -> ["contact address: " <> (plain . strEncode) l]) contactLink <> maybe ["you've shared main profile with this contact"] @@ -1275,8 +1279,8 @@ viewSentBroadcast mc s f ts tz time = prependFirst (highlight' "/feed" <> " (" < | otherwise = "" viewSentFileInvitation :: StyledString -> CIFile d -> CurrentTime -> TimeZone -> CIMeta c d -> [StyledString] -viewSentFileInvitation to CIFile {fileId, filePath, fileStatus} ts tz = case filePath of - Just fPath -> sentWithTime_ ts tz $ ttySentFile fPath +viewSentFileInvitation to CIFile {fileId, fileSource, fileStatus} ts tz = case fileSource of + Just (CryptoFile fPath _) -> sentWithTime_ ts tz $ ttySentFile fPath _ -> const [] where ttySentFile fPath = ["/f " <> to <> ttyFilePath fPath] <> cancelSending @@ -1344,14 +1348,20 @@ humanReadableSize size mB = kB * 1024 gB = mB * 1024 -savingFile' :: AChatItem -> [StyledString] -savingFile' (AChatItem _ _ (DirectChat Contact {localDisplayName = c}) ChatItem {file = Just CIFile {fileId, filePath = Just filePath}, chatDir = CIDirectRcv}) = - ["saving file " <> sShow fileId <> " from " <> ttyContact c <> " to " <> plain filePath] -savingFile' (AChatItem _ _ _ ChatItem {file = Just CIFile {fileId, filePath = Just filePath}, chatDir = CIGroupRcv GroupMember {localDisplayName = m}}) = - ["saving file " <> sShow fileId <> " from " <> ttyContact m <> " to " <> plain filePath] -savingFile' (AChatItem _ _ _ ChatItem {file = Just CIFile {fileId, filePath = Just filePath}}) = - ["saving file " <> sShow fileId <> " to " <> plain filePath] -savingFile' _ = ["saving file"] -- shouldn't happen +savingFile' :: Bool -> AChatItem -> [StyledString] +savingFile' testView (AChatItem _ _ chat ChatItem {file = Just CIFile {fileId, fileSource = Just (CryptoFile filePath cfArgs_)}, chatDir}) = + let from = case (chat, chatDir) of + (DirectChat Contact {localDisplayName = c}, CIDirectRcv) -> " from " <> ttyContact c + (_, CIGroupRcv GroupMember {localDisplayName = m}) -> " from " <> ttyContact m + _ -> "" + in ["saving file " <> sShow fileId <> from <> " to " <> plain filePath] <> cfArgsStr + where + cfArgsStr = case cfArgs_ of + Just cfArgs@(CFArgs key nonce) + | testView -> [plain $ LB.unpack $ J.encode cfArgs] + | otherwise -> [plain $ "encryption key: " <> strEncode key <> ", nonce: " <> strEncode nonce] + _ -> [] +savingFile' _ _ = ["saving file"] -- shouldn't happen receivingFile_' :: StyledString -> AChatItem -> [StyledString] receivingFile_' status (AChatItem _ _ (DirectChat Contact {localDisplayName = c}) ChatItem {file = Just CIFile {fileId, fileName}, chatDir = CIDirectRcv}) = @@ -1403,7 +1413,7 @@ viewFileTransferStatus (FTRcv ft@RcvFileTransfer {fileId, fileInvitation = FileI RFSCancelled Nothing -> "cancelled" viewFileTransferStatusXFTP :: AChatItem -> [StyledString] -viewFileTransferStatusXFTP (AChatItem _ _ _ ChatItem {file = Just CIFile {fileId, fileName, fileSize, fileStatus, filePath}}) = +viewFileTransferStatusXFTP (AChatItem _ _ _ ChatItem {file = Just CIFile {fileId, fileName, fileSize, fileStatus, fileSource}}) = case fileStatus of CIFSSndStored -> ["sending " <> fstr <> " just started"] CIFSSndTransfer progress total -> ["sending " <> fstr <> " in progress " <> fileProgressXFTP progress total fileSize] @@ -1413,7 +1423,7 @@ viewFileTransferStatusXFTP (AChatItem _ _ _ ChatItem {file = Just CIFile {fileId CIFSRcvInvitation -> ["receiving " <> fstr <> " not accepted yet, use " <> highlight ("/fr " <> show fileId) <> " to receive file"] CIFSRcvAccepted -> ["receiving " <> fstr <> " just started"] CIFSRcvTransfer progress total -> ["receiving " <> fstr <> " progress " <> fileProgressXFTP progress total fileSize] - CIFSRcvComplete -> ["receiving " <> fstr <> " complete" <> maybe "" (\fp -> ", path: " <> plain fp) filePath] + CIFSRcvComplete -> ["receiving " <> fstr <> " complete" <> maybe "" (\(CryptoFile fp _) -> ", path: " <> plain fp) fileSource] CIFSRcvCancelled -> ["receiving " <> fstr <> " cancelled"] CIFSRcvError -> ["receiving " <> fstr <> " error"] CIFSInvalid text -> [fstr <> " invalid status: " <> plain text] diff --git a/stack.yaml b/stack.yaml index d86d8fe57..c949cbb16 100644 --- a/stack.yaml +++ b/stack.yaml @@ -49,7 +49,7 @@ extra-deps: # - simplexmq-1.0.0@sha256:34b2004728ae396e3ae449cd090ba7410781e2b3cefc59259915f4ca5daa9ea8,8561 # - ../simplexmq - github: simplex-chat/simplexmq - commit: 4c0b8a31d20870a23e120e243359901d8240f922 + commit: 980e5c4d1ec15f44290542fd2a5d1c08456f00d1 - github: kazu-yamamoto/http2 commit: b5a1b7200cf5bc7044af34ba325284271f6dff25 # - ../direct-sqlcipher diff --git a/tests/ChatTests/Files.hs b/tests/ChatTests/Files.hs index 9c277e00e..0adb234ce 100644 --- a/tests/ChatTests/Files.hs +++ b/tests/ChatTests/Files.hs @@ -8,14 +8,19 @@ import ChatClient import ChatTests.Utils import Control.Concurrent (threadDelay) import Control.Concurrent.Async (concurrently_) +import qualified Data.Aeson as J import qualified Data.ByteString.Char8 as B +import qualified Data.ByteString.Lazy.Char8 as LB import Simplex.Chat (roundedFDCount) import Simplex.Chat.Controller (ChatConfig (..), InlineFilesConfig (..), XFTPFileConfig (..), defaultInlineFilesConfig) +import Simplex.Chat.Mobile.File import Simplex.Chat.Options (ChatOpts (..)) import Simplex.FileTransfer.Client.Main (xftpClientCLI) import Simplex.FileTransfer.Server.Env (XFTPServerConfig (..)) +import Simplex.Messaging.Crypto.File (CryptoFile (..), CryptoFileArgs (..)) +import Simplex.Messaging.Encoding.String import Simplex.Messaging.Util (unlessM) -import System.Directory (copyFile, doesFileExist) +import System.Directory (copyFile, createDirectoryIfMissing, doesFileExist, getFileSize) import System.Environment (withArgs) import System.IO.Silently (capture_) import Test.Hspec @@ -59,6 +64,7 @@ chatFileTests = do describe "file transfer over XFTP" $ do it "round file description count" $ const testXFTPRoundFDCount it "send and receive file" testXFTPFileTransfer + it "send and receive locally encrypted files" testXFTPFileTransferEncrypted it "send and receive file, accepting after upload" testXFTPAcceptAfterUpload it "send and receive file in group" testXFTPGroupFileTransfer it "delete uploaded file" testXFTPDeleteUploadedFile @@ -1012,6 +1018,35 @@ testXFTPFileTransfer = where cfg = testCfg {xftpFileConfig = Just $ XFTPFileConfig {minFileSize = 0}, tempDir = Just "./tests/tmp"} +testXFTPFileTransferEncrypted :: HasCallStack => FilePath -> IO () +testXFTPFileTransferEncrypted = + testChatCfg2 cfg aliceProfile bobProfile $ \alice bob -> do + src <- B.readFile "./tests/fixtures/test.pdf" + srcLen <- getFileSize "./tests/fixtures/test.pdf" + let srcPath = "./tests/tmp/alice/test.pdf" + createDirectoryIfMissing True "./tests/tmp/alice/" + createDirectoryIfMissing True "./tests/tmp/bob/" + WFResult cfArgs <- chatWriteFile srcPath src + let fileJSON = LB.unpack $ J.encode $ CryptoFile srcPath $ Just cfArgs + withXFTPServer $ do + connectUsers alice bob + alice ##> ("/_send @2 json {\"msgContent\":{\"type\":\"file\", \"text\":\"\"}, \"fileSource\": " <> fileJSON <> "}") + alice <# "/f @bob ./tests/tmp/alice/test.pdf" + alice <## "use /fc 1 to cancel sending" + bob <# "alice> sends file test.pdf (266.0 KiB / 272376 bytes)" + bob <## "use /fr 1 [<dir>/ | <path>] to receive it" + bob ##> "/fr 1 encrypt=on ./tests/tmp/bob/" + bob <## "saving file 1 from alice to ./tests/tmp/bob/test.pdf" + Just (CFArgs key nonce) <- J.decode . LB.pack <$> getTermLine bob + alice <## "completed uploading file 1 (test.pdf) for bob" + bob <## "started receiving file 1 (test.pdf) from alice" + bob <## "completed receiving file 1 (test.pdf) from alice" + (RFResult destLen, dest) <- chatReadFile "./tests/tmp/bob/test.pdf" (strEncode key) (strEncode nonce) + fromIntegral destLen `shouldBe` srcLen + dest `shouldBe` src + where + cfg = testCfg {xftpFileConfig = Just $ XFTPFileConfig {minFileSize = 0}, tempDir = Just "./tests/tmp"} + testXFTPAcceptAfterUpload :: HasCallStack => FilePath -> IO () testXFTPAcceptAfterUpload = testChatCfg2 cfg aliceProfile bobProfile $ \alice bob -> do @@ -1446,7 +1481,7 @@ startFileTransfer alice bob = startFileTransfer' alice bob "test.jpg" "136.5 KiB / 139737 bytes" startFileTransfer' :: HasCallStack => TestCC -> TestCC -> String -> String -> IO () -startFileTransfer' cc1 cc2 fileName fileSize = startFileTransferWithDest' cc1 cc2 fileName fileSize $ Just "./tests/tmp" +startFileTransfer' cc1 cc2 fName fSize = startFileTransferWithDest' cc1 cc2 fName fSize $ Just "./tests/tmp" checkPartialTransfer :: HasCallStack => String -> IO () checkPartialTransfer fileName = do diff --git a/tests/MobileTests.hs b/tests/MobileTests.hs index 31c080354..604a1640e 100644 --- a/tests/MobileTests.hs +++ b/tests/MobileTests.hs @@ -1,22 +1,46 @@ {-# LANGUAGE CPP #-} +{-# LANGUAGE ScopedTypeVariables #-} + +{-# OPTIONS_GHC -fno-warn-orphans #-} module MobileTests where import ChatTests.Utils import Control.Monad.Except +import Crypto.Random (getRandomBytes) +import Data.Aeson (FromJSON (..)) +import qualified Data.Aeson as J +import Data.ByteString (ByteString) +import qualified Data.ByteString as B +import qualified Data.ByteString.Char8 as BS +import qualified Data.ByteString.Lazy.Char8 as LB +import Data.Word (Word8) +import Foreign.C +import Foreign.Marshal.Alloc (mallocBytes) +import Foreign.Ptr import Simplex.Chat.Mobile +import Simplex.Chat.Mobile.File +import Simplex.Chat.Mobile.Shared +import Simplex.Chat.Mobile.WebRTC import Simplex.Chat.Store import Simplex.Chat.Store.Profiles import Simplex.Chat.Types (AgentUserId (..), Profile (..)) import Simplex.Messaging.Agent.Store.SQLite (MigrationConfirmation (..)) +import qualified Simplex.Messaging.Crypto as C +import Simplex.Messaging.Crypto.File (CryptoFileArgs (..)) +import Simplex.Messaging.Encoding.String +import Simplex.Messaging.Parsers (dropPrefix, sumTypeJSON) import System.FilePath ((</>)) import Test.Hspec -mobileTests :: SpecWith FilePath +mobileTests :: HasCallStack => SpecWith FilePath mobileTests = do describe "mobile API" $ do it "start new chat without user" testChatApiNoUser it "start new chat with existing user" testChatApi + it "should encrypt/decrypt WebRTC frames" testMediaApi + it "should encrypt/decrypt WebRTC frames via C API" testMediaCApi + it "should read/write encrypted files via C API" testFileCApi noActiveUser :: String #if defined(darwin_HOST_OS) && defined(swiftJSON) @@ -113,3 +137,65 @@ testChatApi tmp = do chatRecvMsgWait cc 10000 `shouldReturn` "" chatParseMarkdown "hello" `shouldBe` "{}" chatParseMarkdown "*hello*" `shouldBe` parsedMarkdown + +testMediaApi :: HasCallStack => FilePath -> IO () +testMediaApi _ = do + key :: ByteString <- getRandomBytes 32 + frame <- getRandomBytes 100 + let keyStr = strEncode key + reserved = B.replicate (C.authTagSize + C.gcmIVSize) 0 + frame' = frame <> reserved + Right encrypted <- runExceptT $ chatEncryptMedia keyStr frame' + encrypted `shouldNotBe` frame' + B.length encrypted `shouldBe` B.length frame' + runExceptT (chatDecryptMedia keyStr encrypted) `shouldReturn` Right frame' + +testMediaCApi :: HasCallStack => FilePath -> IO () +testMediaCApi _ = do + key :: ByteString <- getRandomBytes 32 + frame <- getRandomBytes 100 + let keyStr = strEncode key + reserved = B.replicate (C.authTagSize + C.gcmIVSize) 0 + frame' = frame <> reserved + encrypted <- test cChatEncryptMedia keyStr frame' + encrypted `shouldNotBe` frame' + test cChatDecryptMedia keyStr encrypted `shouldReturn` frame' + where + test :: HasCallStack => (CString -> Ptr Word8 -> CInt -> IO CString) -> ByteString -> ByteString -> IO ByteString + test f keyStr frame = do + let len = B.length frame + cLen = fromIntegral len + ptr <- mallocBytes len + putByteString ptr frame + cKeyStr <- newCAString $ BS.unpack keyStr + (f cKeyStr ptr cLen >>= peekCAString) `shouldReturn` "" + getByteString ptr cLen + +instance FromJSON WriteFileResult where parseJSON = J.genericParseJSON . sumTypeJSON $ dropPrefix "WF" + +instance FromJSON ReadFileResult where parseJSON = J.genericParseJSON . sumTypeJSON $ dropPrefix "RF" + +testFileCApi :: FilePath -> IO () +testFileCApi tmp = do + src <- B.readFile "./tests/fixtures/test.pdf" + cPath <- newCAString $ tmp </> "test.pdf" + let len = B.length src + cLen = fromIntegral len + ptr <- mallocBytes $ B.length src + putByteString ptr src + r <- peekCAString =<< cChatWriteFile cPath ptr cLen + Just (WFResult (CFArgs key nonce)) <- jDecode r + cKey <- encodedCString key + cNonce <- encodedCString nonce + ptr' <- cChatReadFile cPath cKey cNonce + -- the returned pointer contains NUL-terminated JSON string of ReadFileResult followed by the file contents + r' <- peekCAString $ castPtr ptr' + Just (RFResult sz) <- jDecode r' + contents <- getByteString (ptr' `plusPtr` (length r' + 1)) $ fromIntegral sz + contents `shouldBe` src + sz `shouldBe` len + where + jDecode :: FromJSON a => String -> IO (Maybe a) + jDecode = pure . J.decode . LB.pack + encodedCString :: StrEncoding a => a -> IO CString + encodedCString = newCAString . BS.unpack . strEncode diff --git a/website/langs/ar.json b/website/langs/ar.json index afa1076a6..bf44575f2 100644 --- a/website/langs/ar.json +++ b/website/langs/ar.json @@ -157,10 +157,10 @@ "comparison-section-list-point-7": "شبكات P2P إما لديها سلطة مركزية أو أن الشبكة كلها يمكن عرضة للخطر", "see-here": "اقرأ هنا", "no-secure": "لا - آمن", - "comparison-section-list-point-5": "لا يحمي المعلومات الوصفية للمستخدمين", + "comparison-section-list-point-5": "لا يحمي خصوصية البيانات الوصفية للمستخدمين", "comparison-section-list-point-6": "على الرغم من أن الـP2P موزعة، إلا أنها ليست فدرالية - يعملون كشبكة واحدة", "comparison-section-list-point-1": "عادة ما يكون مكوناً من رقم الهاتف، أو اسم المستخدم في بعض الأحيان", - "comparison-section-list-point-4": "إذا خوادم المشغّل مُخترقة", + "comparison-section-list-point-4": "إذا خوادم المشغّل مُخترقة. تحقق من رمز الأمان في Signal وبعض التطبيقات الأخرى للتخفيف منه", "simplex-unique-card-3-p-1": "يخزن SimpleX جميع بيانات المستخدم على الأجهزة العميلة<strong> بتنسيق قاعدة بيانات محمولة مشفرة — </strong>يمكن نقله إلى جهاز آخر.", "simplex-unique-card-4-p-1": "شبكة SimpleX لا مركزية بالكامل ومستقلة عن أي عملة مشفرة أو أي منصة أخرى، بخلاف الإنترنت.", "simplex-unique-card-4-p-2": "يمكنك<strong> استخدام SimpleX مع خوادمك الخاصة </strong> أو مع الخوادم التي نوفرها — ولا يزال الاتصال ممكن بأي مستخدم.", @@ -240,5 +240,8 @@ "signing-key-fingerprint": "توقيع مفتاح البصمة (SHA-256)", "f-droid-org-repo": "مستودع F-Droid.org", "stable-versions-built-by-f-droid-org": "الإصدارات الثابتة التي تم إنشاؤها بواسطة F-Droid.org", - "releases-to-this-repo-are-done-1-2-days-later": "يتم إصدار الإصدارات إلى هذا المستودع بعد يوم أو يومين" -} \ No newline at end of file + "releases-to-this-repo-are-done-1-2-days-later": "يتم إصدار الإصدارات إلى هذا المستودع بعد يوم أو يومين", + "f-droid-page-simplex-chat-repo-section-text": "لإضافته إلى عميل F-Droid، <span class='hide-on-mobile'>امسح رمز QR أو</span> استخدم عنوان URL هذا:", + "f-droid-page-f-droid-org-repo-section-text": "مستودعات SimpleX Chat و F-Droid.org مبنية على مفاتيح مختلفة. للتبديل، يرجى <a href='/docs/guide/chat-profiles.html#move-your-chat-profiles-to-another-device'>تصدير</a> قاعدة بيانات الدردشة وإعادة تثبيت التطبيق.", + "comparison-section-list-point-4a": "مُرحلات SimpleX لا يمكنها أن تتنازل عن تشفير بين الطرفين. تحقق من رمز الأمان للتخفيف من الهجوم على القناة خارج النطاق" +} diff --git a/website/langs/de.json b/website/langs/de.json index ced2abffd..6cb3f1d3f 100644 --- a/website/langs/de.json +++ b/website/langs/de.json @@ -170,7 +170,7 @@ "no-federated": "Nein - föderiert", "comparison-section-list-point-2": "DNS-basierte Adressen", "comparison-section-list-point-3": "Öffentlicher Schlüssel oder eine andere weltweit eindeutige ID", - "comparison-section-list-point-4": "Wenn die Server des Betreibers kompromittiert werden", + "comparison-section-list-point-4": "Wenn die Server des Betreibers kompromittiert werden. In Signal und weiteren Apps kann der Securitycode überprüft werden, um dies zu entschärfen", "comparison-section-list-point-6": "P2P sind zwar verteilt, aber nicht föderiert - sie arbeiten als ein einziges Netzwerk", "comparison-section-list-point-7": "P2P-Netzwerke haben entweder eine zentrale Verwaltung oder das gesamte Netzwerk kann kompromittiert werden", "see-here": "Siehe hier", @@ -194,7 +194,7 @@ "comparison-section-list-point-1": "Normalerweise auf der Grundlage einer Telefonnummer, in einigen Fällen auf der Grundlage von Benutzernamen", "comparison-point-5-text": "Zentrale Komponente oder andere Netzwerk-weite Angriffe", "no-decentralized": "Nein - dezentralisiert", - "comparison-section-list-point-5": "Metadaten des Nutzers werden nicht geschützt", + "comparison-section-list-point-5": "Die Privatsphäre-Metadaten des Nutzers werden nicht geschützt", "simplex-network-overlay-card-1-li-1": "P2P-Netzwerke vertrauen auf Varianten von <a href='https://en.wikipedia.org/wiki/Distributed_hash_table'>DHT</a>, um Nachrichten zu routen. DHT-Designs müssen zwischen Zustellungsgarantie und Latenz ausgleichen. Verglichen mit P2P bietet SimpleX sowohl eine bessere Zustellungsgarantie, als auch eine niedrigere Latenz, weil eine Nachricht redundant und parallel über mehrere Server gesendet werden kann, wobei die durch den Empfänger ausgewählten Server genutzt werden. In P2P-Netzwerken werden Nachrichten sequentiell über <em>O(log N)</em> Knoten gesendet, wobei die Knoten durch einen Algorithmus ausgewählt werden.", "simplex-unique-overlay-card-3-p-4": "Zwischen dem gesendeten und empfangenen Serververkehr gibt es keine gemeinsamen Kennungen oder Chiffriertexte — sodass ein Beobachter nicht ohne weiteres feststellen kann, wer mit wem kommuniziert, selbst wenn TLS kompromittiert wurde.", "simplex-unique-overlay-card-4-p-3": "Wenn Sie darüber nachdenken, für die SimpleX-Plattform entwickeln zu wollen, z.B. einen Chatbot für SimpleX-App-Nutzer oder die Integration der SimpleX-Chat-Bibliothek in Ihre mobilen Apps, <a href='https://simplex.chat/contact#/?v=1&smp=smp%3A%2F%2FPQUV2eL0t7OStZOoAsPEV2QYWt4-xilbakvGUGOItUo%3D%40smp6.simplex.im%2FK1rslx-m5bpXVIdMZg9NLUZ_8JBm8xTt%23MCowBQYDK2VuAyEALDeVe-sG8mRY22LsXlPgiwTNs9dbiLrNuA7f3ZMAJ2w%3D' target='_blank'>kontaktieren Sie uns bitte</a> für eine weitere Beratung und Unterstützung.", @@ -242,5 +242,6 @@ "simplex-chat-via-f-droid": "SimpleX Chat per F-Droid", "simplex-chat-repo": "SimpleX Chat Repository", "stable-and-beta-versions-built-by-developers": "Von den Entwicklern erstellte stabile und Beta-Versionen", - "f-droid-page-simplex-chat-repo-section-text": "Um es Ihrem F-Droid-Client hinzuzufügen <span class='hide-on-mobile'>scannen Sie den QR-Code oder</span> nutzen Sie diese URL:" -} \ No newline at end of file + "f-droid-page-simplex-chat-repo-section-text": "Um es Ihrem F-Droid-Client hinzuzufügen <span class='hide-on-mobile'>scannen Sie den QR-Code oder</span> nutzen Sie diese URL:", + "comparison-section-list-point-4a": "SimpleX-Relais können die E2E-Verschlüsselung nicht kompromittieren. Überprüfen Sie den Sicherheitscode, um einen möglichen Angriff auf den Out-of-Band-Kanal zu entschärfen" +} diff --git a/website/langs/es.json b/website/langs/es.json index 4505d4b98..3f317ae4e 100644 --- a/website/langs/es.json +++ b/website/langs/es.json @@ -179,8 +179,8 @@ "no-federated": "No - federado", "comparison-section-list-point-1": "Generalmente basado en un número de teléfono, en algunos casos en nombres de usuario", "comparison-section-list-point-2": "Direcciones basadas en DNS", - "comparison-section-list-point-4": "Si los servidores del operador se ven comprometidos", - "comparison-section-list-point-5": "No protege los metadatos del usuario", + "comparison-section-list-point-4": "Si los servidores del operador se ven comprometidos. Verifique el código de seguridad en Signal y alguna otra aplicación para mitigarlo", + "comparison-section-list-point-5": "No protege la privacidad de los metadatos del usuario", "comparison-section-list-point-3": "Clave pública o algun otro ID único a nivel global", "comparison-section-list-point-6": "A pesar de que las redes P2P son distribuidas, no son federadas - funcionan como una única red", "comparison-section-list-point-7": "Las redes P2P o bien tienen una autoridad central o toda la red puede verse comprometida", @@ -242,5 +242,6 @@ "stable-versions-built-by-f-droid-org": "Versión estable compilada por F-Droid.org", "f-droid-page-f-droid-org-repo-section-text": "Los repositorios de SimpleX Chat y F-Droid.org firman con distinto certificado. Para cambiar, por favor <a href='/docs/guide/chat-profiles.html#move-your-chat-profiles-to-another-device'>exportar</a> la base de datos y reinstala la aplicación.", "signing-key-fingerprint": "Huella digital de la clave de firma (SHA-256)", - "releases-to-this-repo-are-done-1-2-days-later": "Las versiones aparecen 1-2 días más tarde en este repositorio" -} \ No newline at end of file + "releases-to-this-repo-are-done-1-2-days-later": "Las versiones aparecen 1-2 días más tarde en este repositorio", + "comparison-section-list-point-4a": "Los servidores de retransmisión no pueden comprometer la encriptación e2e. Para evitar posibles ataques, verifique el código de seguridad mediante un canal alternativo" +} diff --git a/website/langs/fi.json b/website/langs/fi.json new file mode 100644 index 000000000..eaa774aa9 --- /dev/null +++ b/website/langs/fi.json @@ -0,0 +1,247 @@ +{ + "home": "Koti", + "developers": "Kehittäjät", + "reference": "Lisätietoja", + "blog": "Blogi", + "features": "Ominaisuudet", + "why-simplex": "Miksi SimpleX", + "simplex-privacy": "SimpleX yksityisyys", + "simplex-network": "SimpleX verkko", + "simplex-explained": "SimpleX selitetynä", + "simplex-explained-tab-1-text": "1. Mitä käyttäjät kokevat", + "simplex-explained-tab-2-text": "2. Miten se toimii", + "simplex-explained-tab-3-text": "3. Mitä palvelimet näkevät", + "simplex-explained-tab-1-p-2": "Kuinka se voi toimia yksisuuntaisten jonotusten kanssa ilman käyttäjäprofiilin tunnisteita?", + "simplex-explained-tab-2-p-1": "Jokaista yhteyttä varten käytetään kahta erillistä viestintäjonon mahdollistavaa palvelinta viestien lähettämiseen ja vastaanottamiseen.", + "simplex-explained-tab-2-p-2": "Palvelimet välittävät viestejä vain yhteen suuntaan eivätkä saa kokonaiskuvaa käyttäjän keskustelusta tai yhteyksistä.", + "simplex-explained-tab-3-p-2": "Käyttäjät voivat lisäksi parantaa metadata-yksityisyyttään käyttämällä Tor-verkkoa palvelimiin yhdistämiseen, mikä estää IP-osoitteen perusteella tapahtuvan yhteyksien tekemisen.", + "chat-bot-example": "Keskustelu­botti-esimerkki", + "smp-protocol": "SMP-protokolla", + "chat-protocol": "Keskustelu­protokolla", + "simplex-chat-protocol": "SimpleX Keskustelu­protokolla", + "terminal-cli": "Pääte CLI", + "terms-and-privacy-policy": "Käyttöehdot ja yksityisyys­käytäntö", + "hero-header": "Yksityisyys uudelleen määritelty", + "hero-subheader": "Ensimmäinen viestisovellus<br>ilman käyttäjätunnuksia", + "hero-overlay-1-textlink": "Miksi käyttäjätunnukset ovat huonoja yksityisyydelle?", + "hero-overlay-1-title": "Kuinka SimpleX toimii?", + "hero-overlay-2-title": "Miksi käyttäjätunnukset ovat huonoja yksityisyydelle?", + "feature-1-title": "Päästä päähän salattuja viestejä markdownin ja muokkaamisen kera", + "feature-2-title": "Päästä päähän salattuja<br>kuvia ja tiedostoja", + "feature-3-title": "Hajautetut salaiset ryhmät —<br>vain käyttäjät tietävät niiden olemassaolosta", + "feature-4-title": "Päästä päähän salattuja ääniviestejä", + "feature-5-title": "Katoavia viestejä", + "feature-8-title": "Incognito-tila —<br>ainutlaatuinen SimpleX Chatille", + "simplex-network-overlay-1-title": "Verrattuna P2P-viestintäprotokolliin", + "simplex-private-7-title": "Viestin eheys<br>vahvistus", + "simplex-private-9-title": "Yksisuuntaisia<br>viestijonoja", + "simplex-private-10-title": "Väliaikaiset nimettömät parittaiset tunnisteet", + "simplex-private-card-3-point-1": "Vain TLS 1.2/1.3 vahvoilla algoritmeilla käytetään asiakas-palvelin-yhteyksiin.", + "simplex-private-card-4-point-2": "Käyttääksesi SimpleX:ää Torin kautta, asenna <a href=\"https://guardianproject.info/apps/org.torproject.android/\" target=\"_blank\">Orbot-sovellus</a> ja ota käyttöön SOCKS5-välityspalvelin (tai VPN <a href=\"https://apps.apple.com/us/app/orbot/id1609461599?platform=iphone\" target=\"_blank\">iOS:lla</a>).", + "simplex-private-card-7-point-1": "Viestien eheyden varmistamiseksi ne numeroituvat peräkkäin ja sisältävät edellisen viestin tiivisteen.", + "simplex-private-card-7-point-2": "Jos viestiä lisätään, poistetaan tai muutetaan, vastaanottaja saa ilmoituksen.", + "simplex-private-card-8-point-1": "SimpleX-palvelimet toimivat matalan viiveen sekoitussolmuina — saapuvilla ja lähtevillä viesteillä on erilainen järjestys.", + "simplex-private-card-10-point-1": "SimpleX käyttää väliaikaisia nimettömiä parittaisia osoitteita ja tunnistetietoja jokaiselle käyttäjäkontaktille tai ryhmän jäsenelle.", + "privacy-matters-2-title": "Vaalien manipulointi", + "privacy-matters-2-overlay-1-title": "Yksityisyys antaa sinulle valtaa", + "privacy-matters-2-overlay-1-linkText": "Yksityisyys antaa sinulle valtaa", + "privacy-matters-3-title": "Syyte viattomasta yhteydestä", + "privacy-matters-3-overlay-1-title": "Yksityisyys suojaa vapauttasi", + "simplex-unique-3-title": "Sinä hallitset tietojasi", + "hero-overlay-card-1-p-4": "Tämä ratkaisu estää kaikkien käyttäjien metadatan vuotamisen sovellustason tasolla. Lisätäksesi yksityisyyttä ja suojataksesi IP-osoitteesi, voit yhdistää viestintäpalvelimiin Tor-verkon kautta.", + "hero-overlay-card-1-p-5": "Vain asiakaslaitteet tallentavat käyttäjäprofiilit, yhteystiedot ja ryhmät; viestit lähetetään 2-kerroksisella päästä päähän salauksella.", + "hero-overlay-card-2-p-1": "Kun käyttäjillä on pysyvät tunnisteet, vaikka ne olisivat vain satunnaisia numeroita, kuten istunnon tunniste, on riski, että palveluntarjoaja tai hyökkääjä voi havaita miten käyttäjät ovat yhteydessä toisiinsa ja kuinka monta viestiä he lähettävät.", + "simplex-network-overlay-card-1-p-1": "<a href='https://fi.wikipedia.org/wiki/Vertaisverkko'>P2P</a> viestintäprotokollilla ja sovelluksilla on erilaisia ongelmia, jotka tekevät niistä vähemmän luotettavia kuin SimpleX, monimutkaisempia analysoida ja alttiita useille hyökkäystyypeille.", + "privacy-matters-overlay-card-1-p-1": "Monet suuret yritykset käyttävät tietoa siitä, keiden kanssa olet yhteydessä, arvioidakseen tulojasi, myydäkseen sinulle tarpeettomia tuotteita ja määrittääkseen hinnat.", + "privacy-matters-overlay-card-1-p-2": "Verkkokauppiaat tietävät, että alhaisempiin tuloluokkiin kuuluvat ihmiset todennäköisemmin tekevät kiireellisiä ostoksia, joten he voivat veloittaa korkeampia hintoja tai poistaa alennukset.", + "simplex-unique-overlay-card-3-p-1": "SimpleX Chat tallentaa kaikki käyttäjätiedot vain asiakaslaitteille käyttäen <strong>siirrettävää salattua tietokantamuotoa</strong>, joka voidaan viedä ja siirtää mihin tahansa tuettuun laitteeseen.", + "simplex-unique-overlay-card-3-p-2": "Päästä päähän salatut viestit säilytetään väliaikaisesti SimpleX-releay-palvelimilla, kunnes ne vastaanotetaan, minkä jälkeen ne poistetaan pysyvästi.", + "simplex-unique-card-1-p-1": "SimpleX suojaa profiilisi, yhteystietosi ja metatietosi yksityisyyden, piilottaen ne SimpleX-alustan palvelimilta ja kaikilta havainnoijilta.", + "simplex-unique-card-1-p-2": "Toisin kuin millään muulla olemassa olevalla viestintäalustalla, SimpleX:llä ei ole tunnisteita käyttäjille — <strong>ei edes satunnaisia numeroita</strong>.", + "simplex-unique-card-4-p-1": "SimpleX-verkko on täysin hajautettu ja riippumaton mistään kryptovaluutasta tai mistään muusta alustasta paitsi Internetistä.", + "simplex-unique-card-4-p-2": "Voit <strong>käyttää SimpleX:ää omien palvelimiesi kanssa</strong> tai meidän tarjoamillamme palvelimilla — ja silti yhdistyä mihin tahansa käyttäjään.", + "join": "Liity", + "hide-info": "Piilota tiedot", + "contact-hero-header": "Sait osoitteen yhdistämistä varten SimpleX Chatissa", + "invitation-hero-header": "Sait kertalinkini yhdistämistä varten SimpleX Chatissa", + "contact-hero-p-2": "Et ole vielä ladannut SimpleX Chat -sovellusta?", + "privacy-matters-section-subheader": "Metatietojesi yksityisyyden säilyttäminen — <span class='text-active-blue'>keneen puhut</span> — suojaa sinua seuraavilta:", + "privacy-matters-section-label": "Varmista, ettei viestintäsovelluksesi pääse käsiksi tietoihisi!", + "simplex-private-section-header": "Mikä tekee SimpleX:stä <span class='gradient-text'>yksityisen</span>", + "simplex-network-1-header": "Toisin kuin P2P-verkot", + "simplex-network-1-overlay-linktext": "P2P-verkkojen ongelmia", + "protocol-1-text": "Signal, suuret alustat", + "protocol-2-text": "XMPP, Matrix", + "protocol-3-text": "P2P-protokollat", + "comparison-point-1-text": "Vaati globaalin identiteetin", + "comparison-point-2-text": "Mahdollisuus MITM-hyökkäykseen", + "comparison-point-3-text": "Riippuvuus DNS:stä", + "comparison-point-4-text": "Yksittäinen tai keskitetty verkko", + "yes": "Kyllä", + "no": "Ei", + "no-private": "Ei - yksityinen", + "no-secure": "Ei - turvallinen", + "no-resilient": "Ei - joustava", + "comparison-section-list-point-7": "P2P-verkoilla on joko keskitetty auktoriteetti tai koko verkko voidaan vaarantaa", + "see-here": "katso täältä", + "guide-dropdown-1": "Nopea aloitus", + "guide-dropdown-2": "Viestien lähettäminen", + "guide-dropdown-3": "Salaiset ryhmät", + "guide-dropdown-4": "Keskusteluprofiilit", + "guide-dropdown-7": "Yksityisyys ja turvallisuus", + "guide-dropdown-8": "Sovellusasetukset", + "guide-dropdown-9": "Yhteyksien luominen", + "docs-dropdown-5": "XFTP-palvelimen isännöiminen", + "docs-dropdown-6": "WebRTC-palvelimet", + "docs-dropdown-7": "Käännä SimpleX Chat", + "docs-dropdown-8": "SimpleX Hakupalvelu", + "on-this-page": "Tällä sivulla", + "back-to-top": "Takaisin ylös", + "glossary": "Sanasto", + "simplex-chat-repo": "SimpleX Chat -varasto", + "signing-key-fingerprint": "Allekirjoitusavaimen sormenjälki (SHA-256)", + "f-droid-org-repo": "F-Droid.org -varasto", + "stable-versions-built-by-f-droid-org": "Vakioversiot luotu F-Droid.org -varastoon", + "releases-to-this-repo-are-done-1-2-days-later": "Julkaisut tälle varastolle tehdään 1-2 päivää myöhemmin", + "f-droid-page-f-droid-org-repo-section-text": "SimpleX Chat ja F-Droid.org -varastot allekirjoittavat buildit eri avaimilla. Vaihtaaksesi, <a href='/docs/guide/chat-profiles.html#move-your-chat-profiles-to-another-device'>vienti</a> keskustelutietokanta ja asenna sovellus uudelleen.", + "hero-overlay-2-textlink": "Kuinka SimpleX toimii?", + "hero-2-header": "Luo yksityinen yhteys", + "hero-2-header-desc": "Video näyttää, kuinka muodostat yhteyden ystävääsi heidän kertakäyttöiseen QR-koodiinsa, henkilökohtaisesti tai videolinkin kautta. Voit myös liittyä jakamalla kutsulinkin kautta.", + "feature-6-title": "Päästä päähän salattuja<br>puheluita ja videopuheluja", + "feature-7-title": "Siirrettävä salattu tietokanta — siirrä profiilisi toiselle laitteelle", + "simplex-explained-tab-1-p-1": "Voit luoda yhteyshenkilöitä ja ryhmiä sekä käydä kaksisuuntaisia keskusteluja kuten missä tahansa muussa viestisovelluksessa.", + "simplex-explained-tab-3-p-1": "Palvelimilla on erilliset anonyymit tunnistetiedot kullekin jonolle, eivätkä ne tiedä, mille käyttäjille ne kuuluvat.", + "donate": "Lahjoita", + "copyright-label": "© 2020-2023 SimpleX | Avoin projekti", + "hero-p-1": "Muissa sovelluksissa on käyttäjätunnuksia: Signal, Matrix, Session, Briar, Jami, Cwtch, jne.<br> SimpleX ei käytä niitä, <strong>ei edes satunnaisia numeroita</strong>.<br> Tämä parantaa yksityisyyttäsi radikaalisti.", + "simplex-private-1-title": "2 kerrosta päästä päähän salattua viestintää", + "simplex-private-2-title": "Lisäkerros palvelimen salaukselle", + "simplex-private-3-title": "Turvallinen tunnistettu<br>TLS-tiedonsiirto", + "simplex-private-4-title": "Vaihtoehtoinen pääsy Torin kautta", + "simplex-private-5-title": "Useita tasoja<br>sisältöpakkauksia", + "simplex-private-6-title": "Avainvaihto kanavan ulkopuolella<br> (out-of-band)", + "simplex-private-8-title": "Viestien sekoitus<br>korrelaation vähentämiseksi", + "simplex-private-card-1-point-1": "Kaksoisruuviprotokolla —<br>OTR-viestintä täydellisellä eteenpäinsalauksella ja murron palautuksella.", + "simplex-private-card-1-point-2": "NaCL-kryptolaatikko kussakin jonossa estämään liikenteen korrelaatiota viestijonojen välillä, jos TLS vaarantuu.", + "simplex-private-card-2-point-1": "Lisäkerros palvelimen salaukselle vastaanottajalle toimittaessa, jotta lähetetyn ja vastaanotetun palvelinliikenteen korrelaatio estyy, jos TLS vaarantuu.", + "simplex-private-card-3-point-2": "Palvelimen sormenjälki ja kanavan sitominen estävät välikäden hyökkäykset ja toistohyökkäykset.", + "simplex-private-card-3-point-3": "Yhteyden jatkaminen on estetty istuntohyökkäysten estämiseksi.", + "simplex-private-card-4-point-1": "Suojataksesi IP-osoitettasi, voit käyttää palvelimia Tor-verkon tai jonkin muun kuljetuskerroksen päällä.", + "simplex-private-card-5-point-1": "SimpleX käyttää sisältöpakkauksia jokaiselle salauskerrokselle estämään viestikoon hyökkäyksiä.", + "simplex-private-card-5-point-2": "Se saa erikokoiset viestit näyttämään samalta palvelimille ja verkon tarkkailijoille.", + "simplex-private-card-6-point-1": "Monet viestintäalustat ovat alttiita välikäden hyökkäyksille palvelimilta tai verkko-operaattoreilta.", + "simplex-private-card-6-point-2": "Estääkseen sen, SimpleX-sovellukset siirtävät yksittäiset avaimet kanavan ulkopuolella, kun jaat osoitteen linkkinä tai QR-koodina.", + "simplex-private-card-9-point-1": "Jokainen viestijono siirtää viestejä yhteen suuntaan, eri lähettävillä ja vastaanottavilla osoitteilla.", + "simplex-private-card-9-point-2": "Se vähentää hyökkäysvektoreita verrattuna perinteisiin viestivälittimiin ja saatavilla oleviin metatietoihin.", + "simplex-private-card-10-point-2": "Se mahdollistaa viestien toimittamisen ilman käyttäjäprofiilitunnisteita, tarjoten paremman metatietosuojan kuin vaihtoehdot.", + "privacy-matters-1-title": "Mainonnan ja hintasyrjinnän estäminen", + "privacy-matters-1-overlay-1-title": "Yksityisyys säästää rahaa", + "privacy-matters-1-overlay-1-linkText": "Yksityisyys säästää rahaa", + "privacy-matters-3-overlay-1-linkText": "Yksityisyys suojaa vapauttasi", + "simplex-unique-1-title": "Sinulla on täydellinen yksityisyys", + "simplex-unique-1-overlay-1-title": "Täysi yksityisyys identiteetistäsi, profiilistasi, kontakteistasi ja metatiedoista", + "simplex-unique-2-title": "Olet suojattu roskapostilta ja väärinkäytöksiltä", + "simplex-unique-2-overlay-1-title": "Paras suoja roskapostilta ja väärinkäytöksiltä", + "simplex-unique-3-overlay-1-title": "Omistusoikeus, hallinta ja tietojesi turvallisuus", + "simplex-unique-4-title": "Omistat SimpleX-verkon", + "simplex-unique-4-overlay-1-title": "Täysin hajautettu — käyttäjät omistavat SimpleX-verkon", + "hero-overlay-card-1-p-1": "Monet käyttäjät ovat kysyneet: <em>jos SimpleX ei käytä käyttäjätunnisteita, miten se tietää minne viestit toimitetaan?</em>", + "hero-overlay-card-1-p-3": "Määrität, minkä palvelimen(t) valitset viestien vastaanottamiseen, sekä kontaktisi — palvelimet, joita käytät viestien lähettämiseen heille. Jokainen keskustelu käyttää todennäköisesti kahta eri palvelinta.", + "hero-overlay-card-1-p-6": "Lue lisää <a href='https://github.com/simplex-chat/simplexmq/blob/stable/protocol/overview-tjr.md' target='_blank'>SimpleX whitepaperista</a>.", + "hero-overlay-card-1-p-2": "Viestien toimittamiseen SimpleX ei käytä muiden alustojen käyttäjätunnuksia, vaan sen sijaan se käyttää väliaikaisia nimettömiä parittaisia tunnisteita viestijonoille, jotka ovat erillisiä jokaiselle yhteydelle — pitkäaikaisia tunnisteita ei ole.", + "hero-overlay-card-2-p-3": "Jopa yksityisimmillä sovelluksilla, jotka käyttävät Tor v3 -palveluja, jos puhut kahdelle eri yhteyshenkilölle saman profiilin kautta, he voivat todistaa olevansa yhteydessä samaan henkilöön.", + "hero-overlay-card-2-p-4": "SimpleX suojaa näitä hyökkäyksiä vastaan, sillä siinä ei ole käyttäjätunnuksia toteutuksessaan. Ja jos käytät Incognito-tilaa, sinulla on eri näyttönimi jokaiselle yhteyshenkilölle, mikä estää yhteisten tietojen jakamisen heidän välillään.", + "hero-overlay-card-2-p-2": "Tämän tiedon avulla he voisivat yhdistää sen olemassa oleviin julkisiin sosiaalisiin verkostoihin ja määrittää joitakin todellisia identiteettejä.", + "simplex-network-overlay-card-1-li-1": "P2P-verkot luottavat jonkinlaiseen <a href='https://fi.wikipedia.org/wiki/Hajautettu_tietokanta'>DHT</a>-varianttiin viestien reitittämiseksi. DHT-suunnittelun on tasapainotettava toimituksen varmuutta ja latenssia. SimpleX:llä on parempi toimitusvarmuus ja pienempi latenssi kuin P2P-verkoilla, koska viesti voidaan toimittaa redundanssina useiden palvelimien kautta samanaikaisesti, käyttäen vastaanottajan valitsemia palvelimia. P2P-verkoissa viesti kulkee läpi <em>O(log N)</em> solmun sekvenssissä, käyttäen algoritmin valitsemia solmuja.", + "simplex-network-overlay-card-1-li-2": "SimpleX-toteutus, toisin kuin useimmat P2P-verkot, ei käytä globaaleja käyttäjätunnisteita millään tavalla, ei edes tilapäisiä, ja käyttää ainoastaan tilapäisiä parillisia tunnisteita, tarjoten paremman anonymiteetin ja metadatansuojan.", + "simplex-network-overlay-card-1-li-6": "P2P-verkot saattavat olla alttiita <a href='https://www.usenix.org/conference/woot15/workshop-program/presentation/p2p-file-sharing-hell-exploiting-bittorrent'>DRDoS-hyökkäyksille</a>, kun asiakkaat voivat lähettää uudelleen ja voimistaa liikennettä, mikä johtaa verkko-laajuiseen palvelunestohyökkäykseen. SimpleX-asiakkaat välittävät liikennettä vain tunnetuilta yhteyksiltä eivätkä voi olla käytettävissä hyökkääjänä liikenteen voimistamiseen koko verkossa.", + "simplex-network-overlay-card-1-li-3": "P2P ei ratkaise <a href='https://fi.wikipedia.org/wiki/V%C3%A4lik%C3%A4den_hy%C3%B6kk%C3%A4ys'>välikäden hyökkäys</a> -ongelmaa, ja useimmat olemassa olevat toteutukset eivät käytä kanavan ulkopuolisia viestejä alkuperäiseen avaimenvaihtoon. SimpleX käyttää kanavan ulkopuolisia viestejä tai, joissakin tapauksissa, jo valmiiksi turvallisia ja luotettuja yhteyksiä alkuperäiseen avaimenvaihtoon.", + "simplex-network-overlay-card-1-li-4": "P2P-toteutukset voivat estyä joidenkin Internet-palveluntarjoajien (kuten <a href='https://fi.wikipedia.org/wiki/BitTorrent'>BitTorrent</a>) toimesta. SimpleX on kuljetusprotokollasta riippumaton - se voi toimia yleisten verkkoprotokollien kautta, kuten esimerkiksi WebSockets.", + "simplex-network-overlay-card-1-li-5": "Kaikki tunnetut P2P-verkot saattavat olla alttiita <a href='https://fi.wikipedia.org/wiki/Sybil-hy%C3%B6kk%C3%A4ys'>Sybil-hyökkäykselle</a>, koska jokainen solmu on löydettävissä, ja verkko toimii kokonaisuutena. Tunnetut keinot sen lieventämiseksi vaativat joko keskitetyn komponentin tai kalliin <a href='https://fi.wikipedia.org/wiki/Proof_of_work'>työn todistuksen</a>. SimpleX-verkolla ei ole palvelimen löydettävyyttä, se on fragmentoitunut ja toimii useina eristettyinä aliverkkoina, mikä tekee verkko-laajuisista hyökkäyksistä mahdottomia.", + "privacy-matters-overlay-card-1-p-3": "Jotkut rahoitus- ja vakuutusyhtiöt käyttävät sosiaalisia verkostoja määrittääkseen korkoja ja vakuutusmaksuja. Se tekee usein alhaisempiin tuloihin kuuluvien ihmisten maksavan enemmän — sitä kutsutaan <a href='https://fairbydesign.com/povertypremium/' target='_blank'>'köyhyyslisäksi'</a>.", + "privacy-matters-overlay-card-1-p-4": "SimpleX-alusta suojaa yhteyksiesi yksityisyyttä paremmin kuin mikään vaihtoehto, estäen täysin yhteysverkkosi tulemisen saataville mille tahansa yrityksille tai organisaatioille. Vaikka ihmiset käyttävät SimpleX Chatin tarjoamia palvelimia, emme tiedä käyttäjien määrää tai heidän yhteyksiään.", + "privacy-matters-overlay-card-2-p-3": "SimpleX on ensimmäinen alusta, jolla ei ole mitään käyttäjätunnisteita suunnittelussaan, suojaten siten yhteyksesi verkkoa paremmin kuin mikään tunnettu vaihtoehto.", + "privacy-matters-overlay-card-2-p-1": "Ei niin kauan sitten huomasimme merkittävien vaalien olevan manipuloitavissa <a href='https://en.wikipedia.org/wiki/Facebook–Cambridge_Analytica_data_scandal' target='_blank'>kunnioitetun konsulttiyrityksen</a> toimesta, joka käytti sosiaalisia verkostoja vääristämään käsitystämme todellisesta maailmasta ja manipuloimaan ääniämme.", + "privacy-matters-overlay-card-3-p-1": "Kaikkien pitäisi välittää viestinnän yksityisyydestä ja turvallisuudesta — harmittomat keskustelut voivat asettaa sinut vaaraan, vaikka sinulla ei olisi mitään piilotettavaa.", + "privacy-matters-overlay-card-2-p-2": "Ollaksesi objektiivinen ja tehdäksesi itsenäisiä päätöksiä, sinun on hallittava tietotilaasi. Se on mahdollista vain, jos käytät yksityistä viestintäalustaa, jolla ei ole pääsyä sosiaaliseen verkostoosi.", + "privacy-matters-overlay-card-3-p-2": "Yksi järkyttävimmistä tarinoista on <a href='https://en.wikipedia.org/wiki/Mohamedou_Ould_Slahi' target='_blank'>Mohamedou Ould Salahi'n</a> kokemus, joka on kuvattu hänen muistelmissaan ja esitetty The Mauritanian -elokuvassa. Hänet laitettiin Guantanamo-leirille ilman oikeudenkäyntiä ja häntä kidutettiin siellä 15 vuotta puhelun jälkeen sukulaiselleen Afganistanissa, epäiltynä osallisuudesta 9/11-iskuihin, vaikka hän oli asunut Saksassa edelliset 10 vuotta.", + "privacy-matters-overlay-card-3-p-3": "Tavalliset ihmiset pidätetään siitä, mitä he jakavat verkossa, jopa 'anonyymien' tiliensä kautta, <a href='https://www.dailymail.co.uk/news/article-11282263/Moment-police-swoop-house-devout-catholic-mother-malicious-online-posts.html' target='_blank'>jopa demokraattisissa maissa</a>.", + "privacy-matters-overlay-card-3-p-4": "Ei riitä, että käytät päästä päähän salattua viestintäsovellusta, meidän kaikkien pitäisi käyttää viestintäsovelluksia, jotka suojelevat henkilökohtaisten verkostojemme yksityisyyttä — keiden kanssa olemme yhteydessä.", + "simplex-unique-overlay-card-1-p-1": "Toisin kuin muut viestintäalustat, SimpleX:llä ei ole <strong>mitään tunnisteita käyttäjille</strong>. Se ei luota puhelinnumeroihin, verkkotunnuksiin perustuviin osoitteisiin (kuten sähköposti tai XMPP), käyttäjänimiin, julkisiin avaimiin tai edes satunnaisiin numeroihin tunnistaakseen käyttäjänsä — emme tiedä kuinka monta ihmistä käyttää SimpleX-palvelimiamme.", + "simplex-unique-overlay-card-1-p-2": "Viestien toimittamiseksi SimpleX käyttää <a href='https://csrc.nist.gov/glossary/term/Pairwise_Pseudonymous_Identifier'>parittaisia nimettömiä osoitteita</a> kaksisuuntaisille viestijonoille, jotka ovat erilliset vastaanotetuille ja lähetetyille viesteille, yleensä eri palvelimien kautta. SimpleX:n käyttö on kuin <strong>eri “kertakäyttöinen” sähköposti tai puhelin jokaiselle yhteydelle</strong>, eikä sinun tarvitse vaivautua niiden hallitsemiseen.", + "simplex-unique-overlay-card-1-p-3": "Tämä suunnittelu suojaa sitä, kenen kanssa kommunikoit, piilottamalla sen SimpleX-alustan palvelimilta ja kaikilta havainnoijilta. Piilottaaksesi IP-osoitteesi palvelimilta, voit <strong>yhdistää SimpleX-palvelimiin Tor-verkon kautta</strong>.", + "simplex-unique-overlay-card-2-p-1": "Koska sinulla ei ole tunnistetta SimpleX-alustalla, kukaan ei voi ottaa sinuun yhteyttä, ellei jaa kertakäyttöistä tai väliaikaista käyttäjäosoitetta, kuten QR-koodia tai linkkiä.", + "simplex-unique-overlay-card-2-p-2": "Jopa valinnaisen käyttäjäosoitteen kanssa, vaikka sitä voitaisiin käyttää roskapostiyhteyspyyntöjen lähettämiseen, voit vaihtaa sen tai poistaa sen kokonaan menettämättä mitään yhteyksiäsi.", + "simplex-unique-overlay-card-3-p-3": "Toisin kuin liitettyjen verkkojen palvelimet (sähköposti, XMPP tai Matrix), SimpleX-palvelimet eivät tallenna käyttäjätilejä, ne vain välittävät viestejä, suojaten molempien osapuolien yksityisyyttä.", + "simplex-unique-overlay-card-3-p-4": "Lähetetyssä ja vastaanotetussa palvelimen liikenteessä ei ole yhteisiä tunnisteita tai salaustekstiä — jos joku havaitsee sen, he eivät voi helposti selvittää kuka kommunikoi kenen kanssa, vaikka TLS olisi vaarantunut.", + "simplex-unique-overlay-card-4-p-3": "Jos harkitset kehittämistä SimpleX-alustalle, esimerkiksi chat-botin luomista SimpleX-sovelluksen käyttäjille tai SimpleX Chat -kirjaston integrointia mobiilisovelluksiisi, ole hyvä ja <a href='https://simplex.chat/contact#/?v=1&smp=smp%3A%2F%2FPQUV2eL0t7OStZOoAsPEV2QYWt4-xilbakvGUGOItUo%3D%40smp6.simplex.im%2FK1rslx-m5bpXVIdMZg9NLUZ_8JBm8xTt%23MCowBQYDK2VuAyEALDeVe-sG8mRY22LsXlPgiwTNs9dbiLrNuA7f3ZMAJ2w%3D' target='_blank'>ota yhteyttä</a> saadaksesi neuvoja ja tukea.", + "simplex-unique-overlay-card-4-p-1": "Voit <strong>käyttää SimpleX:ää omien palvelimiesi kanssa</strong> ja silti kommunikoida ihmisten kanssa, jotka käyttävät meille tarjottuja valmiiksi määritettyjä palvelimia.", + "simplex-unique-overlay-card-4-p-2": "SimpleX-alusta käyttää <a href='https://github.com/simplex-chat/simplexmq/blob/stable/protocol/overview-tjr.md' target='_blank'>avoimeen protokollaan</a> ja tarjoaa <a href='https://github.com/simplex-chat/simplex-chat/tree/stable/packages/simplex-chat-client/typescript' target='_blank'>SDK:n chatbotien luomiseen</a>, mahdollistaen palvelujen toteuttamisen, joiden kanssa käyttäjät voivat olla vuorovaikutuksessa SimpleX Chat -sovellusten kautta — odotamme innolla nähdä, millaisia SimpleX-palveluja voit rakentaa.", + "simplex-unique-card-3-p-1": "SimpleX tallentaa kaikki käyttäjätiedot asiakaslaitteille <strong>siirrettävässä salatussa tietokannan muodossa</strong> — se voidaan siirtää toiseen laitteeseen.", + "simplex-unique-card-3-p-2": "Päästä päähän salatut viestit säilytetään väliaikaisesti SimpleX-releay-palvelimilla, kunnes ne vastaanotetaan, minkä jälkeen ne poistetaan pysyvästi.", + "we-invite-you-to-join-the-conversation": "Kutsumme sinut mukaan keskusteluun", + "join-the-REDDIT-community": "Liity REDDIT-yhteisöön", + "simplex-unique-card-2-p-1": "Koska sinulla ei ole tunnistetta tai kiinteää osoitetta SimpleX-alustalla, kukaan ei voi ottaa sinuun yhteyttä, ellet jaa kertakäyttöistä tai väliaikaista käyttäjäosoitetta, kuten QR-koodia tai linkkiä.", + "join-us-on-GitHub": "Liity GitHubissa", + "donate-here-to-help-us": "Tue meitä täällä lahjoituksilla", + "sign-up-to-receive-our-updates": "Tilaa päivityksemme", + "enter-your-email-address": "Syötä sähköpostiosoitteesi", + "get-simplex": "Hanki SimpleX", + "why-simplex-is": "Miksi SimpleX on", + "unique": "ainutlaatuinen", + "learn-more": "Lue lisää", + "more-info": "Lisätietoja", + "contact-hero-subheader": "Skannaa QR-koodi SimpleX Chat -sovelluksella puhelimessasi tai tabletissasi.", + "contact-hero-p-1": "Tämän linkin julkiset avaimet ja viestijonon osoite eivät lähetä verkkoa pitkin, kun katsot tätä sivua — ne sisältyvät linkin URL:n hash-osaan.", + "contact-hero-p-3": "Käytä alla olevia linkkejä ladataksesi sovelluksen.", + "scan-qr-code-from-mobile-app": "Skannaa QR-koodi mobiilisovelluksesta", + "to-make-a-connection": "Yhteyden muodostaminen:", + "install-simplex-app": "Asenna SimpleX-sovellus", + "connect-in-app": "Yhdisty sovelluksessa", + "open-simplex-app": "Avaa Simplex-sovellus", + "tap-the-connect-button-in-the-app": "Napauta sovelluksessa olevaa <span class='text-active-blue'>‘yhdistä’</span>-painiketta", + "scan-the-qr-code-with-the-simplex-chat-app": "Skannaa QR-koodi SimpleX Chat -sovelluksella", + "scan-the-qr-code-with-the-simplex-chat-app-description": "Tämän linkin julkiset avaimet ja viestijonon osoite eivät lähetä verkkoa pitkin, kun katsot tätä sivua —<br> ne sisältyvät linkin URL:n hash-osaan.", + "installing-simplex-chat-to-terminal": "Asentaminen SimpleX Chat terminaaliin", + "see-simplex-chat": "Katso SimpleX Chat", + "use-this-command": "Käytä tätä komentoa:", + "github-repository": "GitHub-varasto", + "the-instructions--source-code": "ohjeet, miten ladata tai kääntää se lähdekoodista.", + "if-you-already-installed-simplex-chat-for-the-terminal": "Jos olet jo asentanut SimpleX Chatin terminaaliin", + "if-you-already-installed": "Jos olet jo asentanut", + "simplex-chat-for-the-terminal": "SimpleX Chatin terminaaliin", + "copy-the-command-below-text": "kopioi alla oleva komento ja käytä sitä keskustelussa:", + "privacy-matters-section-header": "Miksi yksityisyys on <span class='gradient-text'>tärkeää</span>", + "tap-to-close": "Napauta sulkeaksesi", + "simplex-network-section-header": "SimpleX <span class='gradient-text'>Verkko</span>", + "simplex-network-section-desc": "Simplex Chat tarjoaa parhaan yksityisyyden yhdistämällä P2P-verkkojen ja liitettävien verkkojen edut.", + "simplex-network-1-desc": "Kaikki viestit lähetetään palvelimien kautta, mikä sekä parantaa metatietojen yksityisyyttä että mahdollistaa luotettavan asynkronisen viestien toimituksen, samalla välttäen monia", + "simplex-network-2-header": "Toisin kuin liitettävät verkot", + "simplex-network-3-desc": "palvelimet tarjoavat <span class='text-active-blue'>yksisuuntaisia jonopalveluja</span> yhdistääkseen käyttäjät, mutta niillä ei ole näkyvyyttä verkon yhteyskarttaan — ainoastaan käyttäjillä on.", + "comparison-section-header": "Vertailu muihin protokolliin", + "simplex-network-2-desc": "SimpleXin relea-palvelimet EIVÄT tallenna käyttäjäprofiileja, yhteystietoja ja toimitettuja viestejä, ne EIVÄT yhdisty toisiinsa, eikä ole OLE olemassa palvelinluetteloa.", + "simplex-network-3-header": "SimpleX-verkko", + "comparison-point-5-text": "Keskuskomponentti tai muu verkkoa koskeva hyökkäys", + "no-decentralized": "Ei - hajautettu", + "no-federated": "Ei - liitetty", + "comparison-section-list-point-1": "Yleensä pohjautuu puhelinnumeroon, joissain tapauksissa käyttäjänimiin", + "comparison-section-list-point-2": "DNS-pohjaiset osoitteet", + "comparison-section-list-point-3": "Julkinen avain tai jokin muu maailmanlaajuisesti uniikki tunniste", + "comparison-section-list-point-4a": "SimpleXin releapalvelimet eivät voi vaarantaa päästä päähän -salausta. Tarkista turvakoodi hyödyntääksesi hyökkäyssuojaa ylitys-kanavalla", + "comparison-section-list-point-5": "Ei suojaa käyttäjien metatietojen yksityisyyttä", + "comparison-section-list-point-6": "Vaikka P2P-verkot ovat hajautettuja, ne eivät ole liitettäviä - ne toimivat yhtenä verkostona", + "guide-dropdown-5": "Datan hallinta", + "guide-dropdown-6": "Ääni- ja videopuhelut", + "comparison-section-list-point-4": "Jos operaattorin palvelimet ovat vaarantuneet. Tarkista turvakoodi Signalissa ja joissakin muissa sovelluksissa suojautuaksesi hyökkäykseltä", + "guide": "Opas", + "docs-dropdown-1": "SimpleX-alusta", + "docs-dropdown-2": "Android-tiedostoihin pääseminen", + "docs-dropdown-3": "Keskustelutietokantaan pääseminen", + "docs-dropdown-4": "SMP-palvelimen isännöiminen", + "newer-version-of-eng-msg": "Tästä sivusta on uudempi versio englanniksi.", + "click-to-see": "Näytä napsauttamalla", + "menu": "Valikko", + "simplex-chat-via-f-droid": "SimpleX Chat F-Droidin kautta", + "stable-and-beta-versions-built-by-developers": "Kehittäjien luomat vakaat ja beta-versiot", + "f-droid-page-simplex-chat-repo-section-text": "Lisätäksesi sen F-Droid-asiakkaaseesi, <span class='hide-on-mobile'>skannaa QR-koodi tai</span> käytä tätä URL-osoitetta:" +} diff --git a/website/langs/fr.json b/website/langs/fr.json index ab29ca7c6..5d261d69a 100644 --- a/website/langs/fr.json +++ b/website/langs/fr.json @@ -205,8 +205,8 @@ "comparison-section-list-point-1": "Généralement basé sur un numéro de téléphone, dans certains cas sur des noms d'utilisateur", "comparison-section-list-point-2": "Adresses basées sur le DNS", "comparison-section-list-point-3": "Clé publique ou tout autre identifiant global unique", - "comparison-section-list-point-4": "Si les serveurs de l'opérateur sont compromis", - "comparison-section-list-point-5": "Ne protège pas les métadonnées des utilisateurs", + "comparison-section-list-point-4": "Si les serveurs de l'opérateur sont compromis. Vérifier les codes de sécurités sur Signal et d'autres applications pour limiter les risques", + "comparison-section-list-point-5": "Ne protège pas la confidentialité des métadonnées des utilisateurs", "comparison-section-list-point-6": "Bien que les P2P soient distribués, ils ne sont pas fédérés - ils fonctionnent comme un seul réseau", "comparison-section-list-point-7": "Les réseaux P2P ont soit une autorité centrale, soit l'ensemble du réseau peut être compromis", "voir-ici": "voir ici", @@ -240,8 +240,9 @@ "simplex-chat-via-f-droid": "SimpleX Chat via F-Droid", "simplex-chat-repo": "Dépot SimpleX Chat", "stable-and-beta-versions-built-by-developers": "Versions stables et bêta crées par les développeurs", - "f-droid-page-simplex-chat-repo-section-text": "Pour l'ajouter à votre client F-Droid <span class='hide-on-mobile'>scannez le code QR ou</span> utilisez cette URL:", + "f-droid-page-simplex-chat-repo-section-text": "Pour l'ajouter à votre client F-Droid <span class='hide-on-mobile'>scannez le code QR ou</span> utilisez cette URL :", "signing-key-fingerprint": "Empreinte de signature numérique (SHA-256)", "f-droid-org-repo": "Dépot F-Droid.org", - "stable-versions-built-by-f-droid-org": "Versions stables créées par F-Droid.org" -} \ No newline at end of file + "stable-versions-built-by-f-droid-org": "Versions stables créées par F-Droid.org", + "comparison-section-list-point-4a": "Les relais SimpleX ne peuvent pas compromettre le chiffrement e2e. Vérifier le code de sécurité pour limiter les attaques sur le canal hors bande" +} diff --git a/website/langs/he.json b/website/langs/he.json new file mode 100644 index 000000000..c814e405c --- /dev/null +++ b/website/langs/he.json @@ -0,0 +1,78 @@ +{ + "home": "מסך הבית", + "developers": "מפתחים", + "reference": "הפניה", + "blog": "בלוג", + "features": "מאפיינים", + "why-simplex": "למה SimpleX", + "simplex-privacy": "פרטיות SimpleX", + "simplex-network": "רשת SimpleX", + "simplex-explained": "תיאור SimpleX", + "simplex-explained-tab-1-text": "1. חוויית משתמש", + "simplex-explained-tab-2-text": "2. איך זה עובד", + "simplex-chat-protocol": "פרוטוקול SimpleX Chat", + "terminal-cli": "ממשק שורת פקודה", + "terms-and-privacy-policy": "תנאים ומדיניות פרטיות", + "hero-header": "פרטיות מוגדרת מחדש", + "hero-subheader": "מערכת העברת ההודעות הראשונה<br>ללא מזהי שתמש", + "hero-overlay-1-textlink": "מדוע מזהי משתמש מזיקים לפרטיות?", + "hero-overlay-2-textlink": "איך SimpleX עובד?", + "hero-2-header": "יצירת חיבור פרטי", + "hero-2-header-desc": "הסרטון מראה כיצד אתם יוצרים קשר עם חברכם באמצעות קוד QR חד פעמי, באופן אישי או באמצעות קישור וידאו. באפשרותכם גם להתחבר על-ידי שיתוף קישור ההזמנה.", + "hero-overlay-1-title": "איך SimpleX עובד?", + "feature-1-title": "הודעות מוצפנות מקצה לקצה עם סימונים ואפשרויות עריכה", + "feature-2-title": "תמונות וקבצים<br>מוצפנים מקצה לקצה", + "feature-3-title": "קבוצות סודיות מבוזרות —<br>רק המשתמשים יודעים שהן קיימות", + "simplex-private-3-title": "תעבורת TLS<br>מאובטחת ומאומתת", + "simplex-private-card-1-point-2": "תיבת הצפנה NaCL בכל תור כדי למנוע קורלציית תעבורה בין תורי הודעות במקרה שאבטחת TLS נפגעה.", + "simplex-private-6-title": "החלפת מפתחות<br>מחוץ לרשת", + "simplex-private-7-title": "בדיקת תקינות<br>ההודעה", + "simplex-private-8-title": "ערבוב הודעות<br>לשם הפחתת קורלציה", + "simplex-private-9-title": "תורי הודעות<br>חד-כיווניים", + "simplex-private-10-title": "מזהים זמניים אנונימיים בזוגות", + "simplex-private-card-2-point-1": "שכבה נוספת של הצפנת שרת למסירה לנמען, כדי למנוע קורלציה בין תעבורת השרת המתקבלת ונשלחת במקרה שאבטחת TLS נפגעה.", + "simplex-private-card-3-point-1": "עבור חיבורי שרת-לקוח, נעשה שימוש רק ב-TLS 1.2/1.3 עם אלגוריתמים חזקים.", + "simplex-private-card-3-point-2": "טביעת אצבע של שרת ואיגוד ערוצים מונעים התקפת אדם בתווך (MITM) והתקפת שליחה מחדש (Replay attack).", + "simplex-private-card-5-point-1": "SimpleX משתמש בריפוד תוכן עבור כל שכבת הצפנה כדי לסכל התקפות בגודל הודעה.", + "simplex-private-card-5-point-2": "זה גורם להודעות בגדלים שונים להיראות זהים לשרתים ולמשקיפים ברשת.", + "simplex-private-card-6-point-1": "פלטפורמות תקשורת רבות חשופות להתקפות אדם בתווך (MITM) על ידי שרתים או ספקי רשת.", + "simplex-private-card-9-point-2": "זה מפחית את וקטורי ההתקפה, בהשוואה למתווכי הודעות מסורתיים, ואת המטא-נתונים הזמינים.", + "simplex-private-card-10-point-2": "זה מאפשר להעביר הודעות ללא מזהי פרופיל משתמש, ומספק פרטיות מטא-נתונים טובה יותר מאשר חלופות אחרות.", + "privacy-matters-1-title": "פרסום ואפליית מחירים", + "privacy-matters-1-overlay-1-linkText": "פרטיות חוסכת לכם כסף", + "privacy-matters-2-title": "מניפולציה בבחירות", + "privacy-matters-2-overlay-1-title": "פרטיות מעניקה לכם עוצמה", + "privacy-matters-3-overlay-1-title": "פרטיות מגנה על החופש שלכם", + "simplex-explained-tab-3-text": "3. מה השרתים רואים", + "simplex-explained-tab-2-p-1": "עבור כל חיבור, שני תורי העברת הודעות נפרדים משמשים לשליחה וקבלה של הודעות דרך שרתים שונים.", + "simplex-explained-tab-2-p-2": "שרתים מעבירים הודעות רק בכיוון אחד, מבלי לקבל את התמונה המלאה של השיחה או החיבורים של המשתמש.", + "simplex-explained-tab-3-p-1": "לשרתים יש אישורים אנונימיים נפרדים לכל תור, ואינם יודעים לאילו משתמשים הם שייכים.", + "simplex-explained-tab-1-p-2": "איך זה יכול לעבוד עם תורים חד-כיווניים וללא מזהי פרופיל משתמש?", + "simplex-explained-tab-3-p-2": "משתמשים יכולים לשפר עוד יותר את פרטיות המטא-נתונים על ידי שימוש ב- Tor כדי לגשת לשרתים, ולמנוע קורלציה לפי כתובת IP.", + "chat-bot-example": "דוגמה לצ'אט בוט", + "smp-protocol": "פרוטוקול SMP", + "chat-protocol": "פרוטוקול צ'אט", + "donate": "תרומה", + "copyright-label": "© 2020-2023 SimpleX | פרויקט קוד פתוח", + "hero-p-1": "לאפליקציות אחרות יש מזהי משתמש: Signal, Matrix, Session, Briar, Jami, Cwtch וכו'.<br> ל-SimpleX אין, <strong>אפילו לא מספרים אקראיים</strong>.<br> זה משפר באופן קיצוני את הפרטיות שלך.", + "hero-overlay-2-title": "מדוע מזהי משתמש מזיקים לפרטיות?", + "feature-6-title": "שיחות שמע ווידאו<br>מוצפנות מקצה לקצה", + "feature-4-title": "הודעות קוליות מוצפנות מקצה לקצה", + "feature-5-title": "הודעות נעלמות", + "feature-7-title": "מסד נתונים מוצפן נייד — העברת הפרופיל שלכם למכשיר אחר", + "feature-8-title": "מצב זהות נסתרת —<br>ייחודי ל-SimpleX Chat", + "simplex-private-4-title": "אופציונלי<br>גישה דרך Tor", + "simplex-network-overlay-1-title": "השוואה לפרוטוקולי העברת הודעות P2P", + "simplex-private-2-title": "שכבה נוספת של<br>הצפנת שרת", + "simplex-private-1-title": "2 שכבות של<br>הצפנה מקצה לקצה", + "simplex-private-5-title": "שכבות מרובות של<br>ריפוד תוכן", + "simplex-private-card-3-point-3": "חידוש החיבור מושבת כדי למנוע התקפות הפעלה.", + "simplex-private-card-4-point-1": "כדי להגן על כתובת ה-IP שלכם, אתם יכולים לגשת לשרתים דרך Tor או רשת שכבת-על אחרת של תעבורה.", + "simplex-private-card-4-point-2": "כדי להשתמש ב-SimpleX דרך Tor, התקן את <a href=\"https://guardianproject.info/apps/org.torproject.android/\" target=\"_blank\">אפליקציית Orbot</a> והפעל את SOCKS5 proxy (או VPN <a href=\"https://apps.apple.com/us/app/orbot/id1609461599?platform=iphone\" target=\"_blank\">ב-iOS</a>).", + "simplex-private-card-7-point-2": "אם הודעה כלשהי תתווסף, תוסר או תשתנה, הנמען יקבל התראה.", + "simplex-private-card-9-point-1": "כל תור הודעות מעביר הודעות בכיוון אחד, עם כתובות השליחה והקבלה השונות.", + "privacy-matters-1-overlay-1-title": "פרטיות חוסכת לכם כסף", + "privacy-matters-2-overlay-1-linkText": "פרטיות מעניקה לכם עוצמה", + "privacy-matters-3-overlay-1-linkText": "פרטיות מגנה על החופש שלכם", + "simplex-explained-tab-1-p-1": "אתם יכולים ליצור אנשי קשר וקבוצות, ולנהל שיחות דו-כיווניות, כמו בכל תוכנה אחרת לשליחת הודעות." +} diff --git a/website/langs/it.json b/website/langs/it.json index 41ea654b2..d7ca0d9a3 100644 --- a/website/langs/it.json +++ b/website/langs/it.json @@ -207,9 +207,9 @@ "simplex-network-1-desc": "Tutti i messaggi vengono inviati tramite i server, garantendo una migliore privacy dei metadati e una consegna asincrona dei messaggi affidabile, evitando molti", "simplex-network-3-desc": "i server forniscono <span class='text-active-blue'>code unidirezionali</span> per connettere gli utenti, ma non hanno visibilità del grafo delle connessioni di rete — solo gli utenti.", "comparison-point-5-text": "Componente centrale o altro attacco a livello di rete", - "comparison-section-list-point-5": "Non protegge i metadati degli utenti", + "comparison-section-list-point-5": "Non protegge la privacy dei metadati degli utenti", "comparison-section-list-point-3": "Chiave pubblica o altro ID univoco globale", - "comparison-section-list-point-4": "Se i server dell'operatore sono compromessi", + "comparison-section-list-point-4": "Se i server dell'operatore sono compromessi. Verifica il codice di sicurezza in Signal e alcune altre app per mitigarlo", "guide-dropdown-1": "Avvio rapido", "guide-dropdown-2": "Inviare messaggi", "guide-dropdown-3": "Gruppi segreti", @@ -242,5 +242,6 @@ "simplex-chat-via-f-droid": "SimpleX Chat via F-Droid", "simplex-chat-repo": "Repo di SimpleX Chat", "stable-and-beta-versions-built-by-developers": "Versioni stabili e beta compilate dagli sviluppatori", - "f-droid-page-simplex-chat-repo-section-text": "Per aggiungerlo al tuo client F-Droid <span class='hide-on-mobile'>scansiona il codice QR o</span> usa questo URL:" -} \ No newline at end of file + "f-droid-page-simplex-chat-repo-section-text": "Per aggiungerlo al tuo client F-Droid <span class='hide-on-mobile'>scansiona il codice QR o</span> usa questo URL:", + "comparison-section-list-point-4a": "I relay di SimpleX non possono compromettere la crittografia e2e. Verifica il codice di sicurezza per mitigare gli attacchi sul canale fuori banda" +} diff --git a/website/langs/ja.json b/website/langs/ja.json index 9fc795659..f77f0619c 100644 --- a/website/langs/ja.json +++ b/website/langs/ja.json @@ -26,5 +26,32 @@ "simplex-unique-card-1-p-1": "SimpleXは、SimpleXプラットフォームのサーバやその他の観察者から隠すことで、あなたのプロフィール、連絡先やメタデータのプライバシーを守ります。", "simplex-unique-overlay-card-4-p-3": "例えば、SimpleXアプリユーザへのチャットボットやSimpleX Chatライブラリーの携帯アプリへの統合など、SimpleXプラットフォームに関する開発を検討してくださっているようでしたら、どのようなアドバイスや支援のことでも<a href='https://simplex.chat/contact#/?v=1&smp=smp%3A%2F%2FPQUV2eL0t7OStZOoAsPEV2QYWt4-xilbakvGUGOItUo%3D%40smp6.simplex.im%2FK1rslx-m5bpXVIdMZg9NLUZ_8JBm8xTt%23MCowBQYDK2VuAyEALDeVe-sG8mRY22LsXlPgiwTNs9dbiLrNuA7f3ZMAJ2w%3D' target='_blank'>ご連絡ください</a> 。", "simplex-unique-overlay-card-4-p-2": "SimpleXプラットフォームは、SimpleX Chatアプリを介してユーザが交流するサービスを実装させつつ<a href='https://github.com/simplex-chat/simplexmq/blob/stable/protocol/overview-tjr.md' target='_blank'>オープンプロトコル</a>を使い、<a href='https://github.com/simplex-chat/simplex-chat/tree/stable/packages/simplex-chat-client/typescript' target='_blank'>チャットボットを作成するためにSDK</a>を提供します—私たちはあなた達がどのようなSimpleXのサービスを築くか本当に楽しみです。", - "simplex-unique-overlay-card-4-p-1": "あなたが、<strong>自分自身のサーバでSimpleXを使っても</strong>、私たちが提供する事前に構築されたサーバを使う方々と連絡を取ることができます。" + "simplex-unique-overlay-card-4-p-1": "あなたが、<strong>自分自身のサーバでSimpleXを使っても</strong>、私たちが提供する事前に構築されたサーバを使う方々と連絡を取ることができます。", + "reference": "参考文献", + "simplex-explained-tab-1-text": "1. ユーザーが経験すること", + "simplex-explained-tab-1-p-2": "ユーザー プロファイル識別子なしで単方向キューをどのように処理できるのでしょうか?", + "simplex-chat-protocol": "SimpleX チャットプロトコル", + "terminal-cli": "ターミナル CLI", + "terms-and-privacy-policy": "利用規約とプライバシーポリシー", + "hero-header": "プライバシーの基準を新境地に", + "hero-subheader": "<br>ユーザーIDを持たない最初のメッセンジャー", + "hero-overlay-1-textlink": "ユーザー ID がプライバシーに悪影響を与えるのはなぜですか?", + "hero-overlay-2-textlink": "SimpleXの仕組みは?", + "hero-2-header": "プライベートな接続をする", + "hero-2-header-desc": "このビデオでは、1回限りのQRコード、対面、またはビデオリンクを通じて友人と接続する方法を紹介しています。招待リンクを共有することでも接続できます。", + "simplex-network": "SimpleXネットワーク", + "simplex-explained": "SimpleXの説明", + "simplex-explained-tab-1-p-1": "他のメッセンジャーと同様に、連絡先やグループを作成し、双方向の会話を行うことができます。", + "simplex-explained-tab-2-text": "2. 仕組み", + "simplex-explained-tab-3-text": "3. サーバーが認識するもの", + "smp-protocol": "SMPプロトコル", + "simplex-explained-tab-2-p-1": "接続ごとに 2 つの個別のメッセージング キューを使用して、異なるサーバー経由でメッセージを送受信します。", + "simplex-explained-tab-2-p-2": "サーバーは、ユーザーの会話や接続の全体像を把握することなく、メッセージを一方向に渡すだけです。", + "simplex-explained-tab-3-p-1": "サーバーはキューごとに個別の匿名認証情報を持っており、どのユーザーに属しているかはわかりません。", + "simplex-explained-tab-3-p-2": "ユーザーは、Tor を使用してサーバーにアクセスし、IP アドレスによる相関を防ぐことで、メタデータのプライバシーをさらに向上させることができます。", + "chat-protocol": "チャットプロトコル", + "chat-bot-example": "チャットボットの例", + "donate": "寄付", + "copyright-label": "© 2020-2023 SimpleX | Open-Source Project", + "hero-p-1": "他のアプリにはユーザー ID があります: Signal、Matrix、Session、Briar、Jami、Cwtch など。<br> SimpleX にはありません。<strong>乱数さえもありません</strong>。<br> これにより、プライバシーが大幅に向上します。" } diff --git a/website/langs/pl.json b/website/langs/pl.json index 3c3dd26e1..0ca0363ae 100644 --- a/website/langs/pl.json +++ b/website/langs/pl.json @@ -232,5 +232,15 @@ "menu": "Menu", "on-this-page": "Na tej stronie", "back-to-top": "Powrót do góry", - "glossary": "Słowniczek" + "glossary": "Słowniczek", + "f-droid-page-simplex-chat-repo-section-text": "Aby dodać do Twojego klienta F-Droid, <span class='hide-on-mobile'>zeskanuj kod QR lub</span> użyj tego URL:", + "f-droid-page-f-droid-org-repo-section-text": "Repozytoria SimpleX Chat i F-Droid.org mają podpisane budowy z innymi kluczami. Aby zmienić, proszę <a href='/docs/guide/chat-profiles.html#move-your-chat-profiles-to-another-device'>wyeksportuj</a> bazę czatu i przeinstaluj aplikację.", + "docs-dropdown-8": "Serwis katalogowy SimpleX", + "simplex-chat-via-f-droid": "SimpleX Chat na F-Droid", + "simplex-chat-repo": "Repo SimpleX", + "stable-and-beta-versions-built-by-developers": "Wersje stabilne i beta zbudowane przez deweloperów", + "signing-key-fingerprint": "Odcisk klucza podpisu (SHA-256)", + "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" }